简介
synchronized在JDK5.0的早期版本中是重量级锁,效率很低,但从JDK6.0开始,JDK在关键字synchronized上做了大量的优化,如偏向锁、轻量级锁等,使它的效率有了很大的提升。
synchronized的作用是实现线程间的同步,当多个线程都需要访问共享代码区域时,对共享代码区域进行加锁,使得每一次只能有一个线程访问共享代码区域,从而保证线程间的安全性。
因为没有显式的加锁和解锁过程,所以称之为隐式锁,也叫作内置锁、监视器锁。
如下实例,在没有使用synchronized的情况下,多个线程访问共享代码区域时,可能会出现与预想中不同的结果。
public class Apple implements Runnable { private int appleCount = 5; @Override public void run() { eatApple(); } public void eatApple(){ appleCount--; System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果"); } public static void main(String[] args) { Apple apple = new Apple(); Thread t1 = new Thread(apple, "小强"); Thread t2 = new Thread(apple, "小明"); Thread t3 = new Thread(apple, "小花"); Thread t4 = new Thread(apple, "小红"); Thread t5 = new Thread(apple, "小黑"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); }}
可能会输出如下结果:
小强吃了一个苹果,还剩3个苹果小黑吃了一个苹果,还剩3个苹果小明吃了一个苹果,还剩2个苹果小花吃了一个苹果,还剩1个苹果小红吃了一个苹果,还剩0个苹果
输出结果异常的原因是eatApple方法里操作不是原子的,如当A线程完成appleCount的赋值,还没有输出,B线程获取到appleCount的最新值,并完成赋值操作,然后A和B同时输出。(A,B线程分别对应小黑、小强)
如果改下eatApple方法如下,还会不会有线程安全问题呢?
public void eatApple(){System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + --appleCount + "个苹果");}
还是会有的,因为--appleCount不是原子操作,--appleCount可以用另外一种写法表示:appleCount = appleCount - 1,还是有可能会出现以上的异常输出结果。
synchronized的使用
synchronized分为同步方法和同步代码块两种用法,当每个线程访问同步方法或同步代码块区域时,首先需要获得对象的锁,抢到锁的线程可以继续执行,抢不到锁的线程则阻塞,等待抢到锁的线程执行完成后释放锁。
1.同步代码块
锁的对象是object:
public class Apple implements Runnable { private int appleCount = 5; private Object object = new Object(); @Override public void run() { eatApple(); } public void eatApple(){//同步代码块,此时锁的对象是object synchronized (object) { appleCount--; System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果"); } } //...省略main方法}
2.同步方法,修饰普通方法
锁的对象是当前类的实例对象:
public class Apple implements Runnable { private int appleCount = 5; @Override public void run() { eatApple(); } public synchronized void eatApple() { appleCount--; System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果"); } //...省略main方法}
等价于以下同步代码块的写法:
public void eatApple() {synchronized (this) {appleCount--;System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");}}
3.同步方法,修饰静态方法
锁的对象是当前类的class对象:
public class Apple implements Runnable { private static int appleCount = 5; @Override public void run() { eatApple(); } public synchronized static void eatApple() { appleCount--; System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果"); } //...省略main方法}
等价于以下同步代码块的写法:
public static void eatApple() {synchronized (Apple.class) {appleCount--;System.out.println(Thread.currentThread().getName() + "吃了一个苹果,还剩" + appleCount + "个苹果");}}
4.同步方法和同步代码块的区别
a.同步方法锁的对象是当前类的实例对象或者当前类的class对象,而同步代码块锁的对象可以是任意对象。
b.同步方法是使用synchronized修饰方法,而同步代码块是使用synchronized修饰共享代码区域。同步代.........