synchronized 同步方法
方法内的变量线程安全,实例变量非线程安全。调用关键字synchronized声明的方法一定是排队运行的,如果不是共享资源,那么根本就没有同步的需要。
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁。对于多线程访问同一对象,哪个对象先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,其他线程就只能等待;但是如果多个线程访问多个对象,则JVM会创建多个锁。
A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法;但B线程如果在这时调用object对象中的synchronized类型的方法需要等待,也就是同步。
脏读是指在读取实例变量时,此值已经被其他线程更改过了。对于实例变量的getValue和setValue方法都要是同步的才能保证不出现脏读。
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以得到该对象的锁的,即在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远能够得到锁的。可重入锁表示自己可以再次获取自己的内部锁,子类完全可以通过可重入锁调用父类的同步方法。
出现异常时,锁自动会释放。
同步不能继承,所以在子类的方法中添加synchronized关键字才能实现同步。
用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程必须等待比较长的时间。可以使用synchronized同步语句块了来解决,synchronized方法是对当前对象加锁,而synchronized同步语句块是对某一对象加锁。
synchronized 同步语句块
和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
当两个并发线程访问同一对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。即不在synchronized块中就是异步执行,在synchronized块就是同步执行。
将任意对象作为对象监视器
- synchronized同步方法对其他synchronized同步方法或synchronize(this)同步代码块调用呈阻塞状态;同一时间只有一个线程可以执行synchronized同步方法中的代码。
- synchronized(this)同步代码块对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态;同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。
- 在多个线程持有“对象监视器”为同一对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的。也就是持有不同的对象监视器是异步的效果。
将x(非this)对象本身作为“对象监视器”,这样就可以得出以下结论:
- 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
- 当其他线程执行x对象中synchronized同步方法时呈同步效果。
- 当其他线程执行x对象方法里面的synchronized(this)代码块时也呈同步效果。
- 当其他线程调用不加synchronized关键字的方法时,还是异步调用。
静态同步synchronized方法与synchronized(class)代码块,是对当前*.java文件对应的class进行持锁。
在JVM中具有String常量池缓存的功能。
String a = 'a';
String b = 'b';
System.out.println(a == b);
上面代码的运行结果为true。若要A、B两个线程要打印出的String都是aa,那么两个线程持有相同的锁,造成其中一个线程无法执行。在大多数情况下,同步synchronized代码块都不会使用String作为锁的对象。
同步方法中可能会出现无限等待,这时可使用同步块来解决。
不同线程都在等待不可能被释放的锁,从而导致所有的任务都无法继续完成,形成死锁。在多线程技术中,“死锁”是必须避免的,因为这会造成线程的假死。
内置类与静态内置类,在内置类中有两个同步方法,但使用的是不同的锁,是异步的;如果对class2上锁,其他线程只能以同步的方式调用class2中的静态同步方法。
如果多个线程持有相同的锁对象,则这些线程之间就是同步的;如果分别获得锁对象,那么这些线程间就是异步的。
volatile 关键字
关键字volatile的主要作用是使变量在多个线程间可见。
通过volatile强制从公共内存中读取变量的值。
synchronized与volatile的比较:
- volatile是线程同步的轻量级实现,性能要好一些;但随着Java的更新,synchronized关键字在执行效率上得到很大的提升,并且volatile只能修饰变量,synchronized可以修饰方法及代码块。
- 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
- volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
- 关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。
volatile主要使用的场合是在多个线程可以感知实例变量被更改了,并且可以获得最新的值使用,不具备同步性,即不具备原子性。
可以使用原子类进行i++的同步,一个原子类型就是一个原子操作的可用类型,它可以在没有锁的情况下做到线程安全。
import java.util.concurrent.atomic.AtomicInteger;
public class AddCountThread extends Thread{
private AtomicInteger count = new AtomicInteger(0);
@Override
public void run() {
for(int i = 0; i < 1000; i++)
System.out.println(count.incrementAndGet());
}
}
但原子类也并不完全安全,在具有逻辑性的情况下输出结果也具有随机性。比如addAndGet()是原子的,但方法和方法之间的调用却不是原子的。
synchronized不仅可以使多个线程访问同一个资源具有同步性,还具有将线程工作内存中变量同步的功能。它可以保证在同一时刻,只有一个线程可以执行某一个方法或某一个代码块,包含互斥性和可见性两个特征。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。
参考
《Java多线程编程核心技术》