synchronized在JDK1.5版本开始,尝试优化。到JDK1.7版本后,优化效率已经非常好了。在绝对效率上,不比reentrantLock差多少。
为什么要使用synchronize关键字?
答:这涉及到了多线程的线程同步问题
当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数据安全,即当两个或两个以上线程访问同一资源时,需要某种方式来确保资源在某一时刻只被一个线程使用只有多个 synchronized 代码块或同步方法使用的是同一个监视器对象,这些 synchronized 代码块之间才具有线程互斥的效果,这样可以保证代码块或同步方法内的代码的原子性,即从代码的开始到结束,不可分割。
synchronized锁什么?
答:锁对象。
可能锁对象包括: this, 临界资源对象(多个线程都可以访问到的对象),Class类对象。
其中,synchronized(this)代码块和synchronized方法都是锁当前对象。
synchronized的使用方法分为两种:
1、同步方法:
public void testSync2(){
synchronized (this) { //锁的是当前的类对象
System.out.println(Thread.currentThread().getName()
+ "count = " + count++);
}
}
2、同步代码块:
synchronized (o) { //锁的是object这个对象
System.out.println(Thread.currentThread().getName()
+ "count = " + count++);
}
注意:
大型的高并发的项目中,建议使用同步代码块,非常不建议使用同步方法,这是因为同步方法不管方法内是否需要同步,直接加锁,而同步代码块可以使同步局部化,要求同步的时候才去加锁。锁尽量是同步代码块外部传入的,多线程共享的临界资源对象。
多线程加锁的问题:
如上图所示。用堆空间中对象作为锁来实现多线程之间的互斥,从而达到线程多线程线程同步的要求。一个线程对应一个线程栈帧,代码在栈帧中运行,栈帧中存着对堆空间中对象的引用,如:int类型的count和Object类型的o,对count这种基本数据类型,引用和数据同步保存在栈帧中。而对象类型会在jvm的堆空间中再开辟一块空间存对象。
当synchronize的时候,如果使用了对象o作为锁对象,则当前线程会对堆空间中的对象上加锁,如栈帧01的线程对对象加锁,而多线程中默认一个对象只能被一个线程加锁,因此在栈帧01的线程释放锁之前,栈帧02的线程都无法对对象加锁,即无法访问synchronize同步方法或同步代码块,进入了阻塞状态。
注意:锁的是一个对象,只有当其他线程再去访问同步方法或同步代码块,对该对象加锁的时候,才会阻塞住其他线程
静态同步方法
* 静态同步方法,同步方法前加static关键字,锁的是当前类型的类对象。在本代码中就是Test_02.class
public static synchronized void testSync4(){
System.out.println(Thread.currentThread().getName()
+ "staticCount = " + staticCount++);
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
}
效果等同于,同步代码块锁当前类型的类对象
public static void main(String[] args) {
synchronized (Test02.class) {
System.out.println(Thread.currentThread().getName()
+ "staticCount = " + staticCount++);
}
}
锁的可重入问题:
* 锁可重入。 同一个线程,多次调用同步代码,锁定同一个锁对象,可重入。
如下代码同步方法m1()和m2()锁定的都是this,即当前对象。当一个线程调用m1()方法时,又在m1()方法中调用了m2()方法,这是允许的,不会被阻塞。
public class Test_06 {
synchronized void m1(){ // 锁this
System.out.println("m1 start");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
System.out.println("m1 end");
}
synchronized void m2(){ // 锁this
System.out.println("m2 start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2 end");
}
同步方法之继承:
* 子类同步方法覆盖父类同步方法。可以指定调用父类的同步方法。
* 相当于锁的重入,还是同一个线程。
如下代码所示,Sub_Test_07 类继承了 Test_07类,并重写m()方法,并在其中调用父类的m()方法,这相当于锁的重入。
public class Test_07 {
synchronized void m(){
System.out.println("Super Class m start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Super Class m end");
}
public static void main(String[] args) {
new Sub_Test_07().m();
}
}
class Sub_Test_07 extends Test_07{
synchronized void m(){
System.out.println("Sub Class m start");
super.m();
System.out.println("Sub Class m end");
}
}
同步方法之 锁与异常:
* 当同步方法中发生异常的时候,自动释放锁资源,即其他线程访问同步方法或同步代码块时,不会影响其他线程的执行可以对对象加锁,而不会进入阻塞状态。
* 注意,同步业务逻辑中,如果发生异常如何处理。
锁对象变更问题:
* 在锁未释放之前,修改锁对象引用,不会影响同步代码的执行,因为锁的是对象,不是引用。
如下代码所示:Thread1执行m()方法,对同步代码块中的对象加锁,进入了同步代码块并不断执行,然后主线程中new了一个新对象,并用引用o指向新对象,然后Thread2也执行m()方法,那么Thread1和Thread2会互斥而导致Thread2会被阻塞吗,答案是不会,因为Thread1线程锁的真实的对象,而非引用,同步代码一旦加锁后,那么会有一个临时的锁引用(在栈帧中)指向锁对象,和真实的引用(o)无直接关联。因此Thread1和Thread2都可正常执行m()中的同步代码块,因为他们锁的是不同的对象,不会因此产生互斥。
public class Test_13 {
Object o = new Object();
void m(){
System.out.println(Thread.currentThread().getName() + " start");
synchronized (o) {
while(true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + o);
}
}
}
public static void main(String[] args) {
final Test_13 t = new Test_13();
new Thread(new Runnable() {
@Override
public void run() {
t.m();
}
}, "thread1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
t.m();
}
}, "thread2");
/**
改变了t对象中的引用o所对应的新对象,改变的是t.o。
但是,thread1线程栈帧中的临时引用,仍然指向原来的对象。
所以thread1和thread2不互斥,他们锁的是不同对象,thread1也可以正常继续运行
*/
t.o = new Object();
thread2.start();
}
}
同步方法之 常量问题:
* 在定义同步代码块时,不要使用常量对象作为锁对象(你看到的是是多个引用,其实指向的都是同一个对象,常量池问题)。
String s1 = "hello";
String s2 = new String("hello"); // new关键字,一定是在堆中创建一个新的对象。
以上就是关于synchronize关键字和锁,我的一些认识和理解,希望对你有所帮助,欢迎留言、交流、点赞、关注。谢谢!