简介

synchronized在jdk5.0的早期版本中是重量级锁,效率很低,但从jdk6.0开始,jdk在关键字synchronized上做了大量的优化,如偏向锁、轻量级锁等,使它的效率有了很大的提升。

synchronized的作用是实现线程间的同步,当多个线程都需要访问共享代码区域时,对共享代码区域进行加锁,使得每一次只能有一个线程访问共享代码区域,从而保证线程间的安全性。

因为没有显式的加锁和解锁过程,所以称之为隐式锁,也叫作内置锁、监视器锁。

如下实例,在没有使用synchronized的情况下,多个线程访问共享代码区域时,可能会出现与预想中不同的结果。

可能会输出如下结果:

小强吃了一个苹果,还剩3个苹果
小黑吃了一个苹果,还剩3个苹果
小明吃了一个苹果,还剩2个苹果
小花吃了一个苹果,还剩1个苹果
小红吃了一个苹果,还剩0个苹果

输出结果异常的原因是eatapple方法里操作不是原子的,如当a线程完成applecount的赋值,还没有输出,b线程获取到applecount的最新值,并完成赋值操作,然后a和b同时输出。(a,b线程分别对应小黑、小强)

如果改下eatapple方法如下,还会不会有线程安全问题呢?

还是会有的,因为–applecount不是原子操作,–applecount可以用另外一种写法表示:applecount = applecount – 1,还是有可能会出现以上的异常输出结果。

synchronized的使用

synchronized分为同步方法和同步代码块两种用法,当每个线程访问同步方法或同步代码块区域时,首先需要获得对象的锁,抢到锁的线程可以继续执行,抢不到锁的线程则阻塞,等待抢到锁的线程执行完成后释放锁。

1.同步代码块

锁的对象是object:

2.同步方法,修饰普通方法

锁的对象是当前类的实例对象:

等价于以下同步代码块的写法:

3.同步方法,修饰静态方法

锁的对象是当前类的class对象:

等价于以下同步代码块的写法:

4.同步方法和同步代码块的区别

a.同步方法锁的对象是当前类的实例对象或者当前类的class对象,而同步代码块锁的对象可以是任意对象。

b.同步方法是使用synchronized修饰方法,而同步代码块是使用synchronized修饰共享代码区域。同步代码块相对于同步方法来说粒度更细,锁的区域更小,一般锁范围越小效率就越高。如下情况显然同步代码块更适用:

内置锁的可重入性

内置锁的可重入性是指当某个线程试图获取一个它已经持有的锁时,它总是可以获取成功。如下:

如果锁不是可重入的,那么假如某线程持有了该锁,然后又需要等待持有该锁的线程释放锁,这不就造成死锁了吗?

synchronized可以被继承吗?

synchronized不可以被继承,如果子类中重写后的方法需要实现同步,则需要手动添加synchronized关键字。

基于内置锁的等待和唤醒

基于内置锁的等待和唤醒是使用object类中的wait()和notify()或notifyall()来实现的。这些方法的调用前提是已经持有对应的锁,所以只能在同步方法或者同步代码块里调用。如果在没有获取到对应锁的情况下调用则会抛出illegalmonitorstateexception异常。下面介绍下相关的几个方法:

wait():使当前线程无限期地等待,直到另一个线程调用notify()或notifyall()。

wait(long timeout):指定一个超时时间,超时时间过后线程将会被自动唤醒。线程也可以在超时时间之前被notify()或notifyall()唤醒。注意,wait(0)等同于调用wait()。

wait(long timeout, int nanos):类似于wait(long timeout),主要区别是wait(long timeout, int nanos)提供了更高的精度。

notify():随机唤醒一个在相同锁对象上等待的线程。

notifyall():唤醒所有在相同锁对象上等待的线程。

一个简单的等待唤醒实例:

输出结果:

小明买了5个苹果
小红吃了1个苹果
小红吃了1个苹果
小红吃了1个苹果
小红吃了1个苹果
小红吃了1个苹果
小明买了5个苹果
小红吃了1个苹果
    ……

到此这篇关于java并发编程之内置锁(synchronized)的文章就介绍到这了,更多相关java内置锁内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!