1 悲观锁和乐观锁
1.1 悲观锁
定义
- 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
- synchronized关键字和Lock的实现类都是悲观锁
适合场景
- 适合写操作多的场景,先加锁可以保证写操作时数据正确。
代码
//=============悲观锁的调用方式
public synchronized void m1()
{
//加锁后的业务逻辑......
}
// 保证多个线程使用的是同一个lock对象的前提下
ReentrantLock lock = new ReentrantLock();
public void m2() {
lock.lock();
try {
// 操作同步资源
}finally {
lock.unlock();
}
}
1.2 乐观锁
定义
- 乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。
- 如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
- 乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
适合场景
适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
代码
//=============乐观锁的调用方式
// 保证多个线程使用的是同一个AtomicInteger
private AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.incrementAndGet();
2 Monitor类
2.1 定义
monitor相当于一个对象的钥匙,只有拿到此对象的monitor,才能访问该对象的同步代码。相反未获得monitor的只能阻塞来等待持有monitor的线程释放monitor。打个比方,monitorenter 和monitorexit 对应的就是拿钥匙和还钥匙
2.2 Monitor与java对象以及线程是如何关联
每一个对象都有一个属于自己的monitor,其次如果线程未获取到singal (许可),则线程阻塞。
两个线程同时过来访问共享数据,两个线程都去抢占Monitor(monitor相当于一个对象的钥匙),假设线程1抢到了,那么线程1去操作共享数据,线程2由于没有抢到只能阻塞主,等到线程1执行完毕后,释放Monitor,线程2抢占Monitor,线程2去操作共享资源。
通过上面描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来 完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorState Exception的异常的原因
2.3 monitorenter
-
1 每一个对象都会和一个监视器monitor关联。监视器被占用时会被锁住,其他线程无法来获取该monitor。当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。其过程如下:
1 若monior的进入数为0,线程可以进入monitor,并将monitor的进数置为1。当前线程成为monitor的owner(所有者)
2 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1
3 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直到monitor的进入数变为0,才能重新尝试获取monitor的所有权 -
2 synchronized的锁对象会关联一个monitor,这个monitor不是我们主动创建的,是JVM的线程执行到这个同步代码块,发现锁对象没有monitor就会创建monitor,monitor内部有两个重要的成员变量owner:拥有这把锁的线程,recursions会记录线程拥有锁的次数,当一个线程拥有monitor后其他线程只能等待
2.4 monitorexit
- 1 能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程
- 2 执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权
- 3 monitorexit释放锁
monitorexit,指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁)monitorexit插入在方法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit
3 Synchronizd
3.1 三种加锁方式
- 作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
- 作用于代码块,对括号里配置的对象加锁。
- 作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
3.2 class文件的编译和反编译
javac ***.java 文件编译
javap -c ***.class 文件反编译
javap -v ***.class 文件反编译
3.3 synchronized作用于同步代码块
代码
public class ThreadDemo1 {
private final Object objectLock = new Object();
public void m1(){
synchronized (objectLock){
System.out.println("hello synchronized");
}
}
}
编译
[root@linux1 ~]# javac ThreadDemo1.java
反编译
[root@linux1 ~]# javap -c ThreadDemo1.class
Compiled from "ThreadDemo1.java"
public class cn.itcast.juc.ThreadDemo1 {
public cn.itcast.juc.ThreadDemo1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."<init>":()V
12: putfield #3 // Field objectLock:Ljava/lang/Object;
15: return
public void m1();
Code:
0: aload_0
1: getfield #3 // Field objectLock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String hello synchronized
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1
16: monitorexit
17: goto 25
20: astore_2
21: aload_1
22: monitorexit
23: aload_2
24: athrow
25: return
Exception table:
from to target type
7 17 20 any
20 23 20 any
}
问题
理论上monitorenter和monitorexit是一一对应的,为什么这个不对应呢,monitorenter有1处,monitorexit有2处?
回答
最后一个monitorexit为了防止程序抛异常所设定的
在m1方法里面自己添加一个异常试试
public class ThreadDemo1 {
private final Object objectLock = new Object();
public void m1(){
synchronized (objectLock){
System.out.println("hello synchronized");
throw new RuntimeException("error");
}
}
}
编译该java文件,生成.class文件
[root@linux1 ~]# javac ThreadDemo1.java
反编译
[root@linux1 ~]# javap -c ThreadDemo1.class
Compiled from "ThreadDemo1.java"
public class cn.itcast.juc.ThreadDemo1 {
public cn.itcast.juc.ThreadDemo1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."<init>":()V
12: putfield #3 // Field objectLock:Ljava/lang/Object;
15: return
public void m1();
Code:
0: aload_0
1: getfield #3 // Field objectLock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String hello synchronized
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: new #7 // class java/lang/RuntimeException
18: dup
19: ldc #8 // String error
21: invokespecial #9 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
24: athrow
25: astore_2
26: aload_1
27: monitorexit
28: aload_2
29: athrow
Exception table:
from to target type
7 28 25 any
}
如果方法中直接抛出了异常处理,那么就是一个monitorenter和一个monitorexit
3.4 synchronized作用于普通同步方法
代码
public class ThreadDemo1 {
private final Object objectLock = new Object();
public synchronized void m1() {
System.out.println("hello synchronized");
}
}
编译转化为字节码
[root@linux1 ~]# javap -v ThreadDemo1.class
{
public cn.itcast.juc.ThreadDemo1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."<init>":()V
12: putfield #3 // Field objectLock:Ljava/lang/Object;
15: return
LineNumberTable:
line 3: 0
line 4: 4
public synchronized void m1();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String hello synchronized
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
}
调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程会将先持有monitor然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放minotor
3.5 synchronized作用于静态同步方法
代码
public class ThreadDemo1 {
public static synchronized void m1() {
System.out.println("hello synchronized");
}
}
编译转化为字节码
{
public cn.itcast.juc.ThreadDemo1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static synchronized void m1();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello synchronized
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
}
ACC_STATIC、ACC_SYNCHRONIZED访问标志区分该方法是否静态同步方法
总结
- 1 同步代码块
显示的使用monitorenter和monitorexit
- 2 普通同步方法
调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程会将先持有monitor然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放minotor
- 3 静态同步方法
ACC_STATIC、ACC_SYNCHRONIZED访问标志区分该方法是否静态同步方法
4 八锁演示
4.1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail
class Phone {
public synchronized void sendSMS() throws Exception {
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
public class Lock8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
4.2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail
class Phone {
public synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
public class Lock8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
4.3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS
class Phone {
public synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
public class Lock8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone.getHello();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
4.4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS
class Phone {
public synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
public class Lock8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
4.5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail
class Phone {
public static synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public static synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
public class Lock8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
4.6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail
class Phone {
public static synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public static synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
public class Lock8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
4.7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS
class Phone {
public static synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
public class Lock8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
4.8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS
class Phone {
public static synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
public class Lock8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
总结
- synchronized锁的非静态同步方法是实例对象this
- synchronized锁的静态同步方法是类对象本身
5 公平锁和非公平锁
5.1 定义
- ⽣活中,排队讲求先来后到视为公平。程序中的公平性也是符合请求锁的绝对时间的,其实就是 FIFO,否则视为不公平
5.2 源码查看
具体分析在后面的AQS原理时在具体讲解
5.3 代码
// 默认非公平锁
// 非公平锁
private final ReentrantLock lock = new ReentrantLock(false);
// 公平锁
private final ReentrantLock lock = new ReentrantLock(true);
6 可重入锁
6.1 定义
-
是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
-
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
-
所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
6.2 代码演示synchronized
同步块
public class ReEntryLockDemo{
public static void main(String[] args){
final Object objectLockA = new Object();
new Thread(() -> {
synchronized (objectLockA){
System.out.println("-----外层调用");
synchronized (objectLockA){
System.out.println("-----中层调用");
synchronized (objectLockA){
System.out.println("-----内层调用");
}
}
}
},"a").start();
}
}
同步方法
public class ReEntryLockDemo{
public synchronized void m1(){
System.out.println("-----m1");
m2();
}
public synchronized void m2(){
System.out.println("-----m2");
m3();
}
public synchronized void m3(){
System.out.println("-----m3");
}
public static void main(String[] args){
ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
reEntryLockDemo.m1();
}
}
原理
- 每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
- 当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
- 在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
- 当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
6.3 代码演示ReentrantLock
public class ReEntryLockDemo{
static Lock lock = new ReentrantLock();
public static void main(String[] args){
new Thread(() -> {
lock.lock();
try{
System.out.println("----外层调用lock");
lock.lock();
try{
System.out.println("----内层调用lock");
}finally {
// 这里故意注释,实现加锁次数和释放次数不一样
// 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
lock.unlock(); // 正常情况,加锁几次就要解锁几次
}
}finally {
lock.unlock();
}
},"a").start();
new Thread(() -> {
lock.lock();
try{
System.out.println("b thread----外层调用lock");
}finally {
lock.unlock();
}
},"b").start();
}
}
7 死锁及排查
代码
public class DeadLockDemo{
public static void main(String[] args){
final Object objectLockA = new Object();
final Object objectLockB = new Object();
new Thread(() -> {
synchronized (objectLockA){
System.out.println(Thread.currentThread().getName()+"\t"+"自己持有A,希望获得B");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLockB){
System.out.println(Thread.currentThread().getName()+"\t"+"A-------已经获得B");
}
}
},"A").start();
new Thread(() -> {
synchronized (objectLockB){
System.out.println(Thread.currentThread().getName()+"\t"+"自己持有B,希望获得A");
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectLockA){
System.out.println(Thread.currentThread().getName()+"\t"+"B-------已经获得A");
}
}
},"B").start();
}
}
排查方式
jps -l
jstack 进程编号
注意:不可以String作为同一把锁