1. 介绍
本文介绍Java中的同步synchronized关键字及其使用。
在多线程环境下,如果多个线程同时对变量进行更新操作,可能会造成并发操作的不一致问题,具体原因分析请参考我之前文章《
Java并发编程~并发更新的一致性
》。Java提供同步synchronized机制来解决这个问题。
当一个方法或者一个代码块被标记为synchronized,则同一时间只能有一个线程能够进入执行这个方法或代码块。
2. 不同步的问题
下面样例代码中的 方法未加同步,多线程并发操作可能会造成数据不一致:
我们利用一个可以同时运行3个线程的 ,以多线程方式同时执行 1000次,结果输出常常不是1000(具有随机性,每次结果值不同),这个就是多线程并发更新数据不一致造成。
3. synchronized关键字
Java中的synchronized关键字可以解决上述问题,synchronized可以有多种用法,可以分别应用在:
实例方法级别
静态方法级别
代码块上
Java内部使用monitor(也称monitor lock或intrinsic lock)机制实现synchronized同步机制,线程运行同步方法或者代码块,必须先获取相应的monitor锁。
monitor分为两类:
对象实例级别,每个对象实例有一把monitor锁
类级别,每个类有一把monitor锁
3.1 对实例方法使用synchronized关键字
上面的 使用synchronized关键字同步,同时只能有一个线程获得实例锁并进入方法执行。锁在实例级别,有多少个实例就有多少把锁。最后测试程序结果正确是1000。
3.2 对静态方法使用synchronized关键字
上面的 使用synchronized关键字同步,同时只能有一个线程获取类锁并进入方法执行。锁在类级别,因为每个类仅被JVM加载一次,不管类的实例有多少个,类锁只有一把。最后测试程序结果正确是1000。
3.3 对代码块使用synchronized关键字
对代码块同步也分实例级和类级。
有的时候我们不想对整个方法进行同步,则可以对部分代码块进行同步。上面的代码中,我们使用 对代码块进行同步,这个锁也是在 实例级别的。
上面的代码也是同步代码块,但是使用 实现,表示利用类锁进行同步。
4. 结论和注意点
Java中的同步synchronized保证:同一时间只有一个线程能够进入同步块,不可能两个或多个线程同时进入同步块。
同步关键字只能用在方法和代码块上。方法和代码块可以是静态的,也可以是非静态的。
当一个线程进入Java同步块,它需要先获取锁;当一个线程离开同步块(正常结束或者异常退出),它会释放锁。
Java的同步synchronized关键字是可重入的,如果线程已经获取了对象锁并执行某个同步方法时,在该方法内它仍然可以调用该对象上的其它同步方法,不需要再获取对象锁。
Java同步机制是有性能开销的,同步方法(或块)相当于一座独木桥,每次只能有一个线程可以过。建议在绝对必要时才考虑同步。另外,尽量缩小锁的作用域(scope),能够用同步块的地方就不要用同步方法。
静态同步方法和非静态同步方法可以并发运行互不干扰,因为锁的级别不同,一个是类锁,一个是实例锁,不是同一把锁。
根据Java语言规范,你不能对构造函数使用synchronized关键字,这种做法编译器通不过。
在Java同步块中,不要synchronize在非final的字段上,非final的字段有可能变化,可能造成不同线程同步在不同实例上,相当于没有同步。建议同步在String类上,它immutable不可变并且是final的。
5. 参考
本文代码已经上传githubhttps://github.com/spring2go/tutorials/tree/master/core-java-concurrency
领取专属 10元无门槛券
私享最新 技术干货