Synchronized
Synchronized 简介
同步方法(Synchronized)支持一种简单的策略来防止线程干扰、内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。
能够保证在同一时刻最多只有一个线程执行同步方法中的代码,以达到保证并发安全的效果。
被 Synchronized 修饰的方法具备原子性
JVM 会自动通过 monitor 来加锁和解锁,保证了同一时刻只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入和不可中断的性质。
- Synchronized 是 Java 的关键字,被 Java 语言原生支持。
- 是最基本的互斥同步手段
- 是并发编程中的元老级角色
不使用并发控制的安全隐患
示例:两个线程同时 i++ ,最后结果会比预计的少。
1# i++ 原理解析
21. 读取 i
32. i = i +1
43. 将 i 的值写入内存中
1package com.xdclass.couponapp.test.SynchronizedDemo;
2
3/**
4 * 消失的请求数
5 * 控制台输出:144313 理想结果应该是 200000
6 */
7public class DisappearRequest1 implements Runnable{
8 static DisappearRequest1 instance = new DisappearRequest1();
9
10 static int i = 0;
11
12 /**
13 * 两个线程共同操作一个实例
14 * @param args
15 */
16 public static void main(String[] args) throws InterruptedException {
17 Thread t1 = new Thread(instance);
18 Thread t2 = new Thread(instance);
19 t1.start();
20 t2.start();
21 t1.join();
22 t2.join();
23 System.out.println(i);
24 }
25
26 @Override
27 public void run() {
28 for (int j = 0; j < 100000; j++) {
29 i++;
30 }
31 }
32}
Synchronized 的两种用法
对象锁
- 同步代码块锁:手动指定锁对象,锁对象默认为 this。
- 方法锁:synchronized 修饰普通方法,锁对象默认为 this。
类锁
Java 类会有多个对象,但只有一个 Class 对象。
类锁的本质就是 Class 对象的锁。
多个线程去访问用类锁修饰的方法的时候,他们所获取到的锁其实是 Class 对象,由于 Class 对象只有一个,所以不同的线程之间(无论该线程诞生于类的哪个实例,都只能获取到这个类唯一的 Class 对象,基于对 Class 对象的操作,形成了互斥性,即“类锁”)。
类锁只能在同一时刻被一个对象拥有。(对象锁如果是不同实例创建出来的,相互之间是没有影响的,可以同时运行)
- synchronized 修饰静态方法(静态锁)
- 同步代码块的锁对象为 ClassName.class
synchronized (Object.class)
同步代码块锁
示例:同步代码块默认使用的锁对象是 this。
synchronized 代码块
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3public class SynchronizedObjectCodeBlock implements Runnable {
4
5 static SynchronizedObjectCodeBlock instance = new SynchronizedObjectCodeBlock();
6 static int num = 0;
7
8 @Override
9 public void run() {
10 synchronized (this){
11 System.out.println("我是对象锁的代码块形式。我叫" + Thread.currentThread().getName() );
12 for (int i = 0; i < 1000000; i++) {
13 num++;
14 }
15 try {
16 Thread.sleep(1);
17 } catch (InterruptedException e) {
18 e.printStackTrace();
19 }
20 System.out.println(Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
21 }
22 }
23
24 public static void main(String[] args) throws InterruptedException {
25 Thread t1 = new Thread(instance);
26 Thread t2 = new Thread(instance);
27 t1.start();
28 t2.start();
29// t1.join();
30// t2.join();
31 while (t1.isAlive() || t2.isAlive()) {
32 }
33 System.out.println(num);
34 }
35}
36
1我是对象锁的代码块形式。我叫Thread-0
2Thread-0运行结束!!!!!!!!!!!!!!!!!
3我是对象锁的代码块形式。我叫Thread-1
4Thread-1运行结束!!!!!!!!!!!!!!!!!
52000000
示例:同步代码块:对象锁无法作用于不同的实例,不同实例之间的对象锁是互不影响的。
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3/**
4 * 对象锁无法作用于不同的实例
5 * 不同实例之间的对象锁是互不影响的
6 */
7public class SynchronizedObjectCodeBlockOnDifferentInstance implements Runnable {
8
9 static SynchronizedObjectCodeBlockOnDifferentInstance instance1 = new SynchronizedObjectCodeBlockOnDifferentInstance();
10 static SynchronizedObjectCodeBlockOnDifferentInstance instance2 = new SynchronizedObjectCodeBlockOnDifferentInstance();
11
12 Object lock1 = new Object();
13 Object lock2 = new Object();
14
15 @Override
16 public void run() {
17 synchronized (lock1){
18 System.out.println("我是lock1同步代码块。我叫" + Thread.currentThread().getName() );
19 try {
20 Thread.sleep(3000);
21 } catch (InterruptedException e) {
22 e.printStackTrace();
23 }
24 System.out.println("lock1-" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
25 }
26 synchronized (lock2){
27 System.out.println("我是lock2同步代码块。我叫" + Thread.currentThread().getName() );
28 try {
29 Thread.sleep(3000);
30 } catch (InterruptedException e) {
31 e.printStackTrace();
32 }
33 System.out.println("lock2-" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
34 }
35 }
36
37 public static void main(String[] args) throws InterruptedException {
38 Thread t1 = new Thread(instance1);
39 Thread t2 = new Thread(instance2);
40 t1.start();
41 t2.start();
42// t1.join();
43// t2.join();
44 while (t1.isAlive() || t2.isAlive()) {
45 }
46 System.out.println("运行结束");
47 }
48}
1synchronized 修饰普通方法,我叫Thread-1
2synchronized 修饰普通方法,我叫Thread-0
3Thread-1运行结束!!!!!!
4Thread-0运行结束!!!!!!
5运行结束
示例:多个同步代码块,可以使用不同的锁对象,更细粒度的锁。提高整个业务方法中代码的并行比例。更有效的利用硬件资源。
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3/**
4 * 同一个业务方法中 可以存在多个同步代码块
5 * 多个同步代码块根据特务特性可以使用不同的锁对象进行细粒度的锁
6 * 提高整个业务方法中代码的并行比例
7 * 更有效的利用硬件资源
8 */
9public class SynchronizedObjectCodeBlock implements Runnable {
10
11 static SynchronizedObjectCodeBlock instance = new SynchronizedObjectCodeBlock();
12 Object lock1 = new Object();
13 Object lock2 = new Object();
14
15 @Override
16 public void run() {
17 synchronized (lock1){
18 System.out.println("我是lock1同步代码块。我叫" + Thread.currentThread().getName() );
19 try {
20 Thread.sleep(3000);
21 } catch (InterruptedException e) {
22 e.printStackTrace();
23 }
24 System.out.println("lock1-" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
25 }
26 synchronized (lock2){
27 System.out.println("我是lock2同步代码块。我叫" + Thread.currentThread().getName() );
28 try {
29 Thread.sleep(3000);
30 } catch (InterruptedException e) {
31 e.printStackTrace();
32 }
33 System.out.println("lock2-" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
34 }
35 }
36
37 public static void main(String[] args) throws InterruptedException {
38 Thread t1 = new Thread(instance);
39 Thread t2 = new Thread(instance);
40 t1.start();
41 t2.start();
42// t1.join();
43// t2.join();
44 while (t1.isAlive() || t2.isAlive()) {
45 }
46 System.out.println("运行结束");
47 }
48}
1我是lock1同步代码块。我叫Thread-0
2lock1-Thread-0运行结束!!!!!!!!!!!!!!!!!
3我是lock2同步代码块。我叫Thread-0
4我是lock1同步代码块。我叫Thread-1
5lock2-Thread-0运行结束!!!!!!!!!!!!!!!!!
6lock1-Thread-1运行结束!!!!!!!!!!!!!!!!!
7我是lock2同步代码块。我叫Thread-1
8lock2-Thread-1运行结束!!!!!!!!!!!!!!!!!
9运行结束
方法锁
synchronized 修饰普通方法,锁对象默认为 this。
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3public class SynchronizedObjectMethod implements Runnable {
4 static SynchronizedObjectMethod instance = new SynchronizedObjectMethod();
5
6 @Override
7 public void run() {
8 method();
9 }
10
11 public synchronized void method(){
12 System.out.println("synchronized 修饰普通方法,我叫" + Thread.currentThread().getName());
13 try {
14 Thread.sleep(3000);
15 } catch (InterruptedException e) {
16 e.printStackTrace();
17 }
18 System.out.println(Thread.currentThread().getName() + "运行结束!!!!!!");
19 }
20
21
22 public static void main(String[] args) {
23 Thread t1 = new Thread(instance);
24 Thread t2 = new Thread(instance);
25 t1.start();
26 t2.start();
27// t1.join();
28// t2.join();
29 while (t1.isAlive() || t2.isAlive()) {
30 }
31 System.out.println("运行结束");
32 }
33}
1synchronized 修饰普通方法,我叫Thread-0
2Thread-0运行结束!!!!!!
3synchronized 修饰普通方法,我叫Thread-1
4Thread-1运行结束!!!!!!
5运行结束
静态锁(synchronized 修饰静态方法)
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3/**
4 * 类锁第一形式:static synchronized 返回值类型 方法名(){ }
5 */
6public class SynchronizedClassStaticMethod implements Runnable {
7 static SynchronizedClassStaticMethod instance1 = new SynchronizedClassStaticMethod();
8 static SynchronizedClassStaticMethod instance2 = new SynchronizedClassStaticMethod();
9
10 @Override
11 public void run() {
12 method();
13 }
14
15 public static synchronized void method(){
16 System.out.println("我是类锁的第一种形式:static method" + Thread.currentThread().getName() );
17 try {
18 Thread.sleep(3000);
19 } catch (InterruptedException e) {
20 e.printStackTrace();
21 }
22 System.out.println(Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
23 }
24
25
26 public static void main(String[] args) {
27 //不同的实例:会争抢唯一的类锁
28 Thread t1 = new Thread(instance1);
29 Thread t2 = new Thread(instance2);
30 t1.start();
31 t2.start();
32// t1.join();
33// t2.join();
34 while (t1.isAlive() || t2.isAlive()) {
35 }
36 System.out.println("运行结束");
37 }
38}
1我是类锁的第一种形式:static methodThread-0
2Thread-0运行结束!!!!!!!!!!!!!!!!!
3我是类锁的第一种形式:static methodThread-1
4Thread-1运行结束!!!!!!!!!!!!!!!!!
5运行结束
同步代码块的锁对象为 ClassName.class synchronized (Object.class)
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3import static com.xdclass.couponapp.test.synchronizeddemo.SynchronizedClassStaticMethod.instance1;
4
5/**
6 * 类锁第二种形式(同步代码块锁对象是 ClassName.class):synchronized (Object.class){}
7 */
8public class SynchronizedClass4Class implements Runnable {
9 static SynchronizedClass4Class instance1 = new SynchronizedClass4Class();
10 static SynchronizedClass4Class instance2 = new SynchronizedClass4Class();
11
12 @Override
13 public void run() {
14 method();
15 }
16
17 private void method(){
18 synchronized (SynchronizedClass4Class.class){
19 System.out.println("我是类锁的第二种形式:同步代码块的锁对象为 ClassName.class,我叫" + Thread.currentThread().getName() );
20 try {
21 Thread.sleep(3000);
22 } catch (InterruptedException e) {
23 e.printStackTrace();
24 }
25 System.out.println(Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
26 }
27 }
28
29 public static void main(String[] args) {
30 //不同的实例:会争抢唯一的类锁
31 Thread t1 = new Thread(instance1);
32 Thread t2 = new Thread(instance2);
33 t1.start();
34 t2.start();
35// t1.join();
36// t2.join();
37 while (t1.isAlive() || t2.isAlive()) {
38 }
39 System.out.println("运行结束");
40 }
41}
1我是类锁的第二种形式:同步代码块的锁对象为 ClassName.class,我叫Thread-0
2Thread-0运行结束!!!!!!!!!!!!!!!!!
3我是类锁的第二种形式:同步代码块的锁对象为 ClassName.class,我叫Thread-1
4Thread-1运行结束!!!!!!!!!!!!!!!!!
5运行结束
多线程访问同步方法的 7 种情况
- 两个线程同时访问一个对象的同步方法。
线程安全的,锁对象为this,交替执行
- 两个线程访问的是两个对象的同步方法。
两个线程的锁对象分别是各自实例(this),互不影响,线程不安全
- 两个线程访问的是 synchronized 的静态方法。
synchronized 修饰静态方法属于类级别的锁,锁对象是该类的 ClassName.class 实例,锁的作用范围是该类的所有实例。多个实例抢占同一把锁,线程安全。
- 两个线程同时访问同一个对象同步方法与非同步方法。
两个线程可以同时运行,synchronized 关键字只作用于被修饰的方法,非同步方法则失效
1/**
2 * 多线程同时访问 同步方法 和 非同步方法
3 */
4public class SynchronizedYesAndNo implements Runnable {
5 static SynchronizedYesAndNo instance = new SynchronizedYesAndNo();
6 @Override
7 public void run() {
8 if (Thread.currentThread().getName().equals("Thread-0")) {
9 method1();
10 } else {
11 method2();
12 }
13 }
14
15 public synchronized void method1(){
16 System.out.println("我是同步方法。我叫" + Thread.currentThread().getName() );
17 try {
18 Thread.sleep(3000);
19 } catch (InterruptedException e) {
20 e.printStackTrace();
21 }
22 System.out.println("同步方法" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
23 }
24
25 public void method2(){
26 System.out.println("我是非同步方法。我叫" + Thread.currentThread().getName() );
27 try {
28 Thread.sleep(3000);
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32 System.out.println("非同步方法" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
33 }
34
35 public static void main(String[] args) {
36 Thread t1 = new Thread(instance);
37 Thread t2 = new Thread(instance);
38 t1.start();
39 t2.start();
40 while (t1.isAlive() || t2.isAlive()) {
41
42 }
43 System.out.println("finished");
44 }
45}
1我是同步方法。我叫Thread-0 .......同时打印出
2我是非同步方法。我叫Thread-1 .......同时打印出
3非同步方法Thread-1运行结束!!!!!!!!!!!!!!!!!
4同步方法Thread-0运行结束!!!!!!!!!!!!!!!!!
5finished
- 多线程访问同一个对象的不同的同步方法
不同同步方法使用同一个锁对象this,因此无法并行。
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3public class SynchronizedYesAndYesAndDiff implements Runnable {
4 static SynchronizedYesAndYesAndDiff instance = new SynchronizedYesAndYesAndDiff();
5 @Override
6 public void run() {
7 if (Thread.currentThread().getName().equals("Thread-0")) {
8 method1();
9 } else {
10 method2();
11 }
12 }
13
14 public synchronized void method1(){
15 System.out.println("我是同步方法1。我叫" + Thread.currentThread().getName() );
16 try {
17 Thread.sleep(3000);
18 } catch (InterruptedException e) {
19 e.printStackTrace();
20 }
21 System.out.println("同步方法1" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
22 }
23
24 public synchronized void method2(){
25 System.out.println("我是同步方法2。我叫" + Thread.currentThread().getName() );
26 try {
27 Thread.sleep(3000);
28 } catch (InterruptedException e) {
29 e.printStackTrace();
30 }
31 System.out.println("同步方法2" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
32 }
33
34 public static void main(String[] args) {
35 Thread t1 = new Thread(instance);
36 Thread t2 = new Thread(instance);
37 t1.start();
38 t2.start();
39 while (t1.isAlive() || t2.isAlive()) {
40
41 }
42 System.out.println("finished");
43 }
44}
1我是同步方法1。我叫Thread-0
2同步方法1Thread-0运行结束!!!!!!!!!!!!!!!!!
3我是同步方法2。我叫Thread-1
4同步方法2Thread-1运行结束!!!!!!!!!!!!!!!!!
5finished
- 多线程同时访问一个对象静态 synchronized 和 非静态 synchronized 方法
可以同时运行:不同方法使用的锁对象不同,前者的锁对象是类锁,后者的锁对象是this,前者的作用范围是类的所以实例(仅作用于该方法),后者的作用域是当前实例(仅作用于该方法),两个不同的方法拥有各自的锁对象,所以互不影响。
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3public class SynchronizedDiffMethod implements Runnable {
4 static SynchronizedDiffMethod instance = new SynchronizedDiffMethod();
5
6 @Override
7 public void run() {
8 if (Thread.currentThread().getName().equals("Thread-0")) {
9 method1();
10 } else {
11 method2();
12 }
13
14 }
15
16 static synchronized void method1() {
17 System.out.println("我是static同步方法。我叫" + Thread.currentThread().getName());
18 try {
19 Thread.sleep(3000);
20 } catch (InterruptedException e) {
21 e.printStackTrace();
22 }
23 System.out.println("static同步方法" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
24 }
25
26 synchronized void method2() {
27 System.out.println("我是同步方法。我叫" + Thread.currentThread().getName());
28 try {
29 Thread.sleep(3000);
30 } catch (InterruptedException e) {
31 e.printStackTrace();
32 }
33 System.out.println("我是同步方法" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
34 }
35
36 public static void main(String[] args) throws InterruptedException {
37 Thread t1 = new Thread(instance);
38 Thread t2 = new Thread(instance);
39 t1.start();
40 t2.start();
41 t1.join();
42 t2.join();
43 System.out.println("finished");
44 }
45}
1我是static同步方法。我叫Thread-0
2我是同步方法。我叫Thread-1
3我是同步方法Thread-1运行结束!!!!!!!!!!!!!!!!!
4static同步方法Thread-0运行结束!!!!!!!!!!!!!!!!!
5finished
- 方法抛出异常、会释放锁。
抛出异常之后,JVM会帮忙释放当前线程持有的锁对象:throw new RuntimeException();
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3/**
4 * 方法抛出异常,会释放锁。
5 */
6public class SynchronizedException implements Runnable {
7 static SynchronizedException instance = new SynchronizedException();
8
9 @Override
10 public void run() {
11 if (Thread.currentThread().getName().equals("Thread-0")) {
12 method1();
13 } else {
14 method2();
15 }
16 }
17
18 public synchronized void method1(){
19 System.out.println("我是同步方法1。我叫" + Thread.currentThread().getName() );
20 try {
21 Thread.sleep(3000);
22
23 } catch (InterruptedException e) {
24 e.printStackTrace();
25 }
26 if (Thread.currentThread().getName().equals("Thread-0")) {
27 throw new RuntimeException(); //抛出异常之后,JVM会帮忙释放当前线程持有的锁对象
28 }
29 System.out.println("同步方法1" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
30 }
31
32 public synchronized void method2(){
33 System.out.println("我是同步方法2。我叫" + Thread.currentThread().getName() );
34 try {
35 Thread.sleep(3000);
36 } catch (InterruptedException e) {
37 e.printStackTrace();
38 }
39 System.out.println("同步方法2" + Thread.currentThread().getName() + "运行结束!!!!!!!!!!!!!!!!!");
40 }
41
42 public static void main(String[] args) {
43 Thread t1 = new Thread(instance);
44 Thread t2 = new Thread(instance);
45 t1.start();
46 t2.start();
47 while (t1.isAlive() || t2.isAlive()) {
48
49 }
50 System.out.println("finished");
51 }
52}
1我是同步方法1。我叫Thread-0
2我是同步方法2。我叫Thread-1
3Exception in thread "Thread-0" java.lang.RuntimeException
4 at com.xdclass.couponapp.test.synchronizeddemo.SynchronizedException.method1(SynchronizedException.java:27)
5 at com.xdclass.couponapp.test.synchronizeddemo.SynchronizedException.run(SynchronizedException.java:12)
6 at java.lang.Thread.run(Thread.java:748)
7同步方法2Thread-1运行结束!!!!!!!!!!!!!!!!!
8finished
同步方法核心思想总结
- 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应 1、5 种情况)。
- 每个实例对应有自己的一把锁,不同实例之间互不影响;例外:锁对象是 ClassName.class 以及 synchronized 修饰的是 static 方法时,类的所有实例公用同一把类锁(对应 2、3、4、6 种情况)。
- 无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应 7 种情况)。
Synchronized 的性质
- 可重入(锁)
- 不可中断
什么是可重入
指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。
好处:避免死锁、提升封装性(简化并发编程的难度)。
粒度:线程范围,而非调用范围。
可重入性
- 同一个方法是可重入的
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3public class SynchronizedRecursion1 {
4 int num = 0;
5
6 /**
7 * 可重入:同一个方法是可重入的,避免死锁的发生。
8 */
9 private synchronized void method1(){
10 System.out.println("method1(),a = " + num);
11 if (num == 0) {
12 num++;
13 method1();
14 System.out.println("method1()运行结束, a = " + num);
15 }
16 }
17
18 public static void main(String[] args) {
19 SynchronizedRecursion1 instance = new SynchronizedRecursion1();
20 instance.method1();
21 }
22}
1method1(),a = 0
2method1(),a = 1
3method1()运行结束, a = 1
- 可重入不要求是同一个方法
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3public class SynchronizedRecursion2 {
4
5 /**
6 * 可重入:可重入不要求是同一个方法,调用类内另外一个方法。
7 * 同一个对象两个同步方法的锁对象都是this,method2()可重入
8 */
9 private synchronized void method1(){
10 System.out.println("method1()");
11 method2();
12 System.out.println("method1() finished");
13 }
14
15 private synchronized void method2(){
16 System.out.println("method2()");
17 }
18
19 public static void main(String[] args) {
20 SynchronizedRecursion2 instance = new SynchronizedRecursion2();
21 instance.method1();
22 }
23}
1method1()
2method2()
3method1() finished
- 可重入不要求是同一个类中的方法
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3public class SynchronizedRecursion3 {
4
5 /**
6 * 可重入:可重入不要求是同一个类中的方法
7 */
8 public synchronized void method1(){
9 System.out.println("我是SuperClass method()");
10 }
11}
12
13class SonClass extends SynchronizedRecursion3 {
14 @Override
15 public synchronized void method1() {
16 System.out.println("我是SonClass method()");
17 super.method1();
18 System.out.println("SonClass finished.");
19 }
20
21 public static void main(String[] args) {
22 SonClass sonClass = new SonClass();
23 sonClass.method1();
24 }
25}
1我是SonClass method()
2我是SuperClass method()
3SonClass finished.
不可中断
一旦这个锁已经被别人获得,如果我还想获得,只能选择等待或者阻塞,直到持有锁的线程释放这个锁,如果持有锁的线程一直不释放锁,只能永远等待下去。
相比之下,Lock 类拥有中断能。
1* 有权中断当前持有锁的线程的执行。
2* 如果等待时间太长,可以选择退出等待。
深入 synchronized 底层原理
加锁和释放锁的原理
现象
所有对象都含有互斥锁,一个线程想执行一个实例的同步方法必须先获取该实例的锁方可进入方法执行,否则需要阻塞直接获取该实例的锁。
获取和释放锁的时机(内置锁)
每一个 Java 对象都可以用于一个内置锁(monitor lock),线程进入同步代码块之前获得内置锁,退出同步代码块前释放锁(包括正常退出和抛出异常退出)。
1package com.xdclass.couponapp.test.synchronizeddemo;
2
3import javax.sound.midi.Soundbank;
4import java.util.concurrent.locks.Lock;
5import java.util.concurrent.locks.ReentrantLock;
6
7/**
8 * 两个方法功能等价
9 */
10public class SynchronnizedToLock {
11 Lock lock = new ReentrantLock();
12
13 public synchronized void method1(){
14 System.out.println("我是 synchronized 锁");
15 }
16
17 public synchronized void method2(){
18 lock.lock();
19 try {
20 System.out.println("我是 lock 形式的锁");
21 } finally {
22 lock.unlock();
23 }
24 }
25
26 public static void main(String[] args) {
27 SynchronnizedToLock instance = new SynchronnizedToLock();
28 instance.method1();
29 instance.method2();
30 }
31}
JVM 字节码
Java 对象头的区域中有一个字段用于标识锁状态。
每个对象都与一个 monitor 相关联,一个 monitor 的 lock 只能被一个线程在同一时间获得。
一个线程在尝试获得与对象关联的 monitor 的所有权的时候会发生以下三种情况之一(monitorenter)
monitorexit 指令是释放对于 monitor 的所有权(释放 monitor 的 锁的所有权)。
1# monitorenter 指令的功能
2* monitor 计数器如果为0,意味着目前monitor还没有被获得,线程会立刻获得monitor,并且把计数器加一。
3* monitor 已经拿到拿到 lock的所有权,又重入了,这样会导致计数器再加一。
4* monitor 已经被其他线程持有,其他的线程尝试获取 monitor 则会被拒绝的信号,进入阻塞状态,直到 monitor 计数器变为0 ,才会再次尝试获取锁。
5
6# monitorexit 指令的功能
7* 将monitor 计数器减一,如果减一之后变为0,意味当前线程不再拥有对 monitor 的所有权,解锁。
8* 如果减一结束之后,计数器不是0,意味着当前线程发生过重入行为,当前线程继续持有monitor(锁)。
9* 当计数器最终减为0,意味着其他阻塞线程可以再次尝试获取 monitor(锁)。
1# 反编译 monitorenter monitorexit
2> javac .\Decompilation.java
3> javap -verbose .\Decompilation.class
4
5 Code:
6 stack=2, locals=4, args_size=2
7 0: aload_0
8 1: getfield #3 // Field object:Ljava/lang/Object;
9 4: dup
10 5: astore_2
11 6: monitorenter
12 7: aload_2
13 8: monitorexit
14 9: goto 17
15 12: astore_3
16 13: aload_2
17 14: monitorexit
18 15: aload_3
19 16: athrow
20 17: return
可重入原理
可重入的原理就是 利用 monitorenter 指令的功能,加锁次数计数器加一。
每个对象天生都持有一把锁,JVM 负责跟踪该对象被加锁的次数。
线程发生重入行为之后(被相同的线程再次获得锁的时候,只有首先获得该锁的线程,才能在该对象上多次的获取这把锁),会导致加锁次数计数器的自增。
每当任务离开时,加锁次数计数器会递减,当计数器为 0 的时候,当前线程失去对该对象 monitor 的所有权(锁)。
保证可见性的原理:内存模型
线程的本地内存是为了加快访问。
- A 线程将本地缓存中修改的共享变量副本同步到主内存中
- JMM 负责将主线程中共享变量的最新副本同步到 B 线程的本地内存中,作为 B 线程本地内存的共享变量的最新副本。
Synchronized 是如何实现可见性的?
被 synchronized 修饰的代码块或方法在执行完毕之前,基于对象的所有修改都要在释放锁之间从线程本地内存由 JMM 控制同步到主内存中。
线程在进入同步代码块得到锁之后,被锁定对象的数据必须直接从主内存中读取(以确保读取的数据是最新的)。
Synchronized 缺陷
效率低
- 锁的释放情况少:执行完才会释放锁、执行中发生异常 JVM 会帮忙释放锁。只有前面这两种情况才会释放。(如果同步方法中代码发生 I/O 阻塞会浪费 CPU 资源)
- 试图获得锁时不能设定超时:不撞南墙不回头。
- 不能中断一个正在试图获得锁的线程:不撞南墙不回头。
不够灵活(读写锁更灵活)
- 加锁和释放锁的时机单一,每个锁仅有单一的条件(某给对象),可能是不够的(有一种锁叫作读写锁,加锁和释放锁的时机更加灵活,读不加锁,写加锁)。
无法知道是否成功获取到锁
- Lock 可以知道是否成功获取锁,如果获取到做一些逻辑,如果没有获取到做另外一些逻辑。
Synchronized 总结
Synchronized 使用注意点
1* 锁对象不能为空:锁状态信息在对象头中,如果没有对象锁无法工作。不能是空对象,必须是new()过的。
2* 作用域不宜过大:synchronized包括的范围。多线程编程的根本目的是为了提高效率,如果同步范围过大,安全是安全,但是代码的并行比例太低,无法充分利用硬件资源提高程序执行效率,大部门代码都是串行化的多线程程序提升效果并不会很好。
3* 避免死锁:
如何选择 Lock 和 synchronized 关键字?
1* 如果可以的话,尽量不要使用 synchronized 和 Lock,尽量使用 java.util.concurrent 包中的类,使用这些类可以让开发者解脱同步的工作,更方便,也更不容易出错。
2* 如果 synchronized 关键字在程序中适用,应该优先使用 synchronized 关键字,这样可以减少同步锁需要编写的代码,提高封装性。
3* 在需要用到 Lock 的特性时候才使用 Lock。
多线程访问同步方法的各种情况?
7 种情况。