前言
synchronized的理解通俗的讲就是java中用来在多线程的情况下协调资源、协调工作的。你可以想象成在流水线上每一个工人都是一个线程。而一个工人拿起产品进行组装就等于给产品增加了锁定。其他工人是无法去抢夺他正在组装的产品。只有他组装完成了,下一道工序的工人才会从流水线上接过他处理过的产品。
使用情景
synchronized只有四种使用方式:
- 修饰代码块
- 修饰类
- 修饰方法
- 修饰静态方法
但是,说是有4种使用方式。但是其实真正的区别就2种(分别是实例与静态,怎么理解?看完此博客全部举例后你就会明白了),如下代码:
final Demo demo = new Demo(); //不加final 会提示警告“Synchronization on a non-final field 'demo'” 意思是demo有可能会重新赋值导致锁失效 private void t(){ //第一种传入对象参数 synchronized (demo){ } //第二种传入类型 synchronized (Demo.class){ } }
另外,你需要记住一点这两种锁形式是不会互相影响的。
修饰代码块
代码:
private static Integer sProduct = 0; public static void main(String[] args) { Object lockFlag = new Object(); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("T1 Start >>> "); synchronized (lockFlag) { for (int i = 0;i< 3;i++){ try { Thread.sleep(100); sProduct++; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1 >>> " + sProduct); } } System.out.println("T1 End >>> "); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("T2 Start >>> "); synchronized (lockFlag) { sProduct++; System.out.println("T2 >>> " + sProduct); } System.out.println("T2 End >>> "); } }); t1.start(); t2.start(); }
结果:
从上面的代码里可以看到,启动了2个线程。按理说线程是会同步执行去处理sProduct。 但是我们给t1线程增加了synchronized。 所以t2线程的锁的代码块与后续的代码,都必须等待t1的锁的代码块种完成才能执行。
T1 Start >>> T2 Start >>> T1 >>> 1 T1 >>> 2 T1 >>> 3 T1 End >>> T2 >>> 4 T2 End >>>
修饰类
代码:
private static Integer sProduct = 0; public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("T1 Start >>> "); synchronized (Demo.class) { for (int i = 0; i < 3; i++) { try { Thread.sleep(100); sProduct++; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1 >>> " + sProduct); } } System.out.println("T1 End >>> "); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("T2 Start >>> "); synchronized (Demo.class) { sProduct++; System.out.println("T2 >>> " + sProduct); } System.out.println("T2 End >>> "); } }); t1.start(); t2.start(); }
结果:
T1 Start >>> T2 Start >>> T1 >>> 1 T1 >>> 2 T1 >>> 3 T1 End >>> T2 >>> 4 T2 End >>>
修饰方法
private static Integer sProduct = 0; public static void main(String[] args) { Demo demo1 = new Demo(); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("T1 Start >>> "); demo1.runOne(); System.out.println("T1 End >>> "); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("T2 Start >>> "); demo1.runTwo(); System.out.println("T2 End >>> "); } }); t1.start(); t2.start(); } public synchronized void runOne(){ for (int i = 0;i< 3;i++){ try { Thread.sleep(100); sProduct++; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1 >>> " + sProduct); } } public synchronized void runTwo(){ sProduct++; System.out.println("T2 >>> " + sProduct); }
结果:
T1 Start >>> T2 Start >>> T1 >>> 1 T1 >>> 2 T1 >>> 3 T1 End >>> T2 >>> 4 T2 End >>>
修饰方法,只作用于当前类的实例,如果有多个类的实例(比如在上面的代码种new多个Demo类),不同的实例他们不会被互相影响。从上面的代码的运行结果我们可以知道,其实修饰方法的锁效果于在方法里添加synchronized(this){ } 一直,代码如下:
private static Integer sProduct = 0; public static void main(String[] args) { Demo demo1 = new Demo(); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("T1 Start >>> "); demo1.runOne(); System.out.println("T1 End >>> "); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("T2 Start >>> "); demo1.runTwo(); System.out.println("T2 End >>> "); } }); t1.start(); t2.start(); } public void runOne() { synchronized (this) { for (int i = 0; i < 3; i++) { try { Thread.sleep(100); sProduct++; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1 >>> " + sProduct); } } } public void runTwo() { synchronized (this) { sProduct++; System.out.println("T2 >>> " + sProduct); } }
修饰静态方法
private static Integer sProduct = 0; public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("T1 Start >>> "); Demo.runOne(); System.out.println("T1 End >>> "); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("T2 Start >>> "); Demo.runTwo(); System.out.println("T2 End >>> "); } }); t1.start(); t2.start(); } public synchronized static void runOne() { for (int i = 0; i < 3; i++) { try { Thread.sleep(100); sProduct++; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1 >>> " + sProduct); } } public synchronized static void runTwo() { sProduct++; System.out.println("T2 >>> " + sProduct); }
结果:
其实修饰静态方法就类似于 synchronized (Demo.class){ } 的形式。
T2 Start >>> T1 >>> 1 T1 >>> 2 T1 >>> 3 T1 End >>> T2 >>> 4 T2 End >>>
原理详解
synchronized 的原理关键是 锁的状态与线程id。 synchronized 一共有四种锁的状态,他们分别是:
-
无锁 对象头中锁的状态为001,并且没有线程id
-
偏向锁 对象头中锁的状态为101,有线程id。 偏向锁只有2种存在情况:第一种,类刚被实例这个时候会被默认设置为偏向锁。 第二种,只有一个线程使用当前类作为锁的情况下,锁的状态会修改为偏向锁
-
轻量锁 对象头中锁的状态为010,有线程id。轻量锁的出现情况:当有两个线程开始竞争这个锁对象,情况发生变化了,某个线程不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象,锁对象的 Mark Word 就指向哪个线程的栈帧中的锁记录。企图抢占的线程会通过自旋的形式尝试获取锁,不会阻塞抢锁线程,以便提高性能。自旋其实就是让CPU保持运作,等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
-
重量锁 重量级锁会让其他申请的线程之间进入阻塞,性能降低。重量级锁也就叫做同步锁,这个锁 对象 Mark Word 再次发生变化,会指向一个监视器(Monitor)对象,该监视器对象用集合的形 式,来登记和管理排队的线程。
锁的简易原理图如下:
现在需要一个方式来验证我们的理解。那就是查看Java类的对象头,我们以Android studio工程为例查看对象头,在项目的build里添加依赖:
implementation 'org.openjdk.jol:jol-core:0.11'
无锁
偏向锁
轻量锁
重量锁
End
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/16623893.html