来源于Java高并发之魂:synchronized深度解析
错误案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Demon1 implements Runnable{ static Demon1 instance = new Demon1(); static int i = 0;
public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance);
thread1.start(); thread2.start();
thread1.join(); thread2.join();
System.out.println(i); } @Override public void run() { for (int j = 0; j < 100000000; j++) { i++; } } }
|
对共享变量的操作不做同步处理就会导致错误,Java执行 i++
分为三步
- 从共享主内存中读取
i
至当前线程的内存
- 对
i
进行加一操作
- 将
i
写回共享主内存
所有,当线程1执行到第二步是,线程2执行第一步,那么必然导致获取的i
的值不一样,所有,必须要求同一时刻最多只能有一个线程执行该段代码,以达到保证并发安全的效
地位
- 是Java关键词,被Java原生支持
- 是最基本的同步互斥手段
两种用法
对象锁
填入Thread中的是同一个对象实例
同步代码块
同一个对象之间的
- 同步代码块锁(使用实例对象本身this作为加锁对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Demon2 implements Runnable { @Override public void run() { synchronized (this) { } }
static Demon2 instance = new Demon2();
public static void main(String[] args) { Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance);
thread1.start(); thread2.start();
while (thread1.isAlive() || thread2.isAlive()) { } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Demon3 implements Runnable{ static Object obj1 = new Object(); @Override public void run() { synchronized (obj1) { } }
static Demon3 instance = new Demon3(); public static void main(String[] args) { Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance);
thread1.start(); thread2.start();
while (thread1.isAlive() || thread2.isAlive()) { } } }
|
方法锁
默认锁定对象为this当前实例对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Demon4 implements Runnable { @Override public void run() { this.func(); } public synchronized void func() { }
static Demon4 instance = new Demon4(); public static void main(String[] args) { Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance);
thread1.start(); thread2.start();
while (thread1.isAlive() || thread2.isAlive()) { } } }
|
类锁
填入Thread中的是同一类的不同实例,同一个类之间的,只有一个类对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Demon5 implements Runnable { @Override public void run() { Demon5.func(); } public synchronized static void func() { }
static Demon5 instance1 = new Demon5(); static Demon5 instance2 = new Demon5(); public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(instance1); Thread thread2 = new Thread(instance2);
thread1.start(); thread2.start();
thread1.join(); thread2.join();
} }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class Demon6 implements Runnable{ @Override public void run() { this.func(); } public void func() {
synchronized (Demon6.class) { } }
static Demon6 instance1 = new Demon6(); static Demon6 instance2 = new Demon6();
public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(instance1); Thread thread2 = new Thread(instance2);
thread1.start(); thread2.start();
thread1.join(); thread2.join(); } }
|
其实,Java中几乎所有的类都是Class的实例,所有第二大类其实也是可以归到第一大类中的。也就是每一个实例都有一把锁
线程访问同步方法的七种情况
- 1 两个线程同时访问一个对象的同步方法 ✔️
- 2 两个线程同时访问两个对象的同步方法 ❌ 不同的实例
- 3 两个线程同时访问同步静态方法 ✔️
- 4 同时访问同步方法和非同步方法 ❌ 非同步方法不受影响
- 5 同一对象不同的普通同步方法 ✔️ 同一个类对象,synchronized默认加到this上,受影响
- 6 同时访问静态的synchronized方法和非静态的.. ❌ 前者加类锁,后者加对象锁,不是同一个锁
- 7 方法抛异常,会释放锁
- 核心思想
- 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况)
- 每个实例都对应有自己的一把锁,不同实例之前互不影响,所有的类都是Class类的实例
- 无论是方法正常执行完毕或者方法抛出异常,都会释放锁
一些理论
重入
同一线程外层函数获得锁之后,内层函数可以直接再次获取该锁,可以提升封装性,避免死锁
- 不要求是同一个方法
- 不要求为同一类
- 粒度:线程范围
不可中断
无法中途释放锁
等价形式
以下两者是等价的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Demon9 { Lock lock = new ReentrantLock();
public synchronized void func1() { System.out.println("Synchronized方法"); }
public void func2() { lock.lock(); try { System.out.println("Lock方法"); } finally { lock.unlock(); } }
public static void main(String[] args) { Demon9 demon9 = new Demon9(); demon9.func1(); demon9.func2(); } }
|
代码进行反编译转换成为字节码
先写一个简单的含有其关键词的类
1 2 3 4 5 6 7 8
| public class Demon10 { final Object obj = new Object();
public void func() { synchronized (obj) { } } }
|
1 2
| javac Demon10.java javap -verbose Demon10 > Demon10_decode.txt
|
打开文件 Demon10_decode.txt,就会发现其中有monitorenter
,monitorexit
,后者数量大于前者,因为可能有多种退出情况,对应于操作系统的进程同步,前者相当于是p()
,后者相当于是v()
1 2 3 4 5 6 7 8 9 10 11 12
| ... 6: monitorenter 7: aload_1 8: monitorexit 9: goto 17 12: astore_2 13: aload_1 14: monitorexit 15: aload_2 16: athrow 17: return ...
|
JVM追踪对象加锁次数,线程第一次给对象加锁时,计数变为1。之后每次加锁会递增每次任务完成计数减一,当计数器减为零时锁完全释放
缺陷
- 效率低 释放条件少结束,异常),无法设置超时,无法中断一个试图获取锁的线程
- 不够灵活 加减锁时机单一(读写锁更灵活)
- 无法知道释放成功获得该锁
面试问题
思考题
锁的升级降级,偏斜锁,轻量级锁,重量级锁