Featured image of post java并发编程之锁

java并发编程之锁

由线程安全问题引出java中的锁,解决线程安全问题,讲解java中的各种锁

synchronized的使用

在上一篇的线程的安全问题提到了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
26
27
28
29
30
31
32
33
34
35
36
package com.msr.study.concurrent.threadsafe;

import java.util.concurrent.TimeUnit;
public class ThreadUnsafe {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(ticket);
            thread.start();
        }
    }
}

class Ticket implements Runnable {
    private static int ticketNum = 50;

    @Override
    public void run() {
        while (true) {
            if (ticketNum > 0) {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " sale a ticket,current:" + saleTicket());
            }else {
                break;
            }
        }
    }
	
    public synchronized int saleTicket(){
        return ticketNum--;
    }
}

synchronized是java中的锁,它修饰的方法或者代码块在同一时刻只能有一个线程去执行方法或代码块。synchronized修改之后会自动加锁,执行完毕之后,会自动解锁,是依靠底层JVM中的Monitor对象。

主要的用法:

  1. 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁。Thread2要等两秒才执行,说明要等Thread1释放锁,虽然他们调用的方法不一样但是,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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.msr.study.concurrent.lock;

import java.util.concurrent.TimeUnit;

/**
 * @author MaiShuRen
 * @version v1.0
 * @date 2020/6/2 13:27
 */

public class SynchronizedLock {

    public synchronized void send() {
        System.out.println(Thread.currentThread().getName()+":send email");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void phone() {
        System.out.println(Thread.currentThread().getName()+":phone");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        SynchronizedLock lock1 = new SynchronizedLock();
        SynchronizedLock lock2 = new SynchronizedLock();
        new Thread(()->{
            lock1.phone();
        },"Thread1").start();
        new Thread(()->{
            lock1.send();
        },"Thread2").start();
        
        
        new Thread(()->{
            lock1.phone();
        },"Thread1").start();
        //Thread2不用等两秒
        new Thread(()->{
            lock2.send();
        },"Thread2").start();
    }
}

2.静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁。同理

 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
27
28
29
30
31
package com.msr.study.concurrent.lock;
import java.util.concurrent.TimeUnit;
public class SynchronizedLock2 {
    public synchronized static void send() {
        System.out.println(Thread.currentThread().getName()+":send email");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized static void phone() {
        System.out.println(Thread.currentThread().getName()+":phone");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Thread(()->{
            SynchronizedLock2.phone();
        },"Thread1").start();

        new Thread(()->{
            SynchronizedLock2.send();
        },"Thread2").start();
    }
}

3.同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.msr.study.concurrent.lock;
import java.util.concurrent.TimeUnit;
public class SynchronizedLock3 {
    private final Object lock = new Object();
    public void send() {
        synchronized (lock){
            System.out.println(Thread.currentThread().getName()+":send email");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public void  phone() {
        synchronized (lock){
            System.out.println(Thread.currentThread().getName()+":phone");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        SynchronizedLock2 lock1 = new SynchronizedLock2();
        SynchronizedLock2 lock2 = new SynchronizedLock2();
        new Thread(()->{
            lock1.phone();
        },"Thread1").start();

        new Thread(()->{
            lock2.send();
        },"Thread2").start();
    }
}

简单地从字节码理解synchronized

使用javap工具: monitorenter—>monitorexit,所以说synchronized是JVM实现的,会自动释放锁。任何对象都可以作为锁,锁信息存在于对象头中的Mark Word

 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
27
28
29
30
31
public void phone();
    Code:
       0: aload_0
       1: getfield      #3                  // Field lock:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter
       7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: new           #5                  // class java/lang/StringBuilder
      13: dup
      14: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
      17: invokestatic  #7                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      20: invokevirtual #8                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      23: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      26: ldc           #19                 // String :phone
      28: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      34: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      37: getstatic     #13                 // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;
      40: ldc2_w        #14                 // long 2l
      43: invokevirtual #16                 // Method java/util/concurrent/TimeUnit.sleep:(J)V
      46: goto          54
      49: astore_2
      50: aload_2
      51: invokevirtual #18                 // Method java/lang/InterruptedException.printStackTrace:()V
      54: aload_1
      55: monitorexit
      56: goto          64
      59: astore_3
      60: aload_1
      61: monitorexit

锁分类

从宏观上分:悲观锁和乐观锁

悲观锁

就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会被阻塞直到拿到锁。java中的悲观锁就是Synchronized

乐观锁

就是一种乐观思想,与悲观锁相反,认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。如java中著名的CAS

JDK锁优化

高效并发是从JDK1.5升级到JDK1.6后的一项重要的改进项

Markword

在说偏向锁、轻量级锁和重量级锁之前,应该要先了解markword。markword是对象头中存储信息的一个字段,是java对象数据结构的一部分

markword

图出自《深入了解Java虚拟机》
状态 标志位 存储内容
未锁定 01 对象哈希码。对象分代年龄
轻量级锁定 00 指向锁记录
重量级锁定(锁膨胀) 10 执行重量级锁定的指针
GC标记 11 空(不需要记录信息)
可偏向 01 偏向线程ID、偏向时间戳、对象年龄分代

自旋锁和自适应自旋

TODO

锁消除

TODO

锁粗化

TODO

轻量级锁

TODO

偏向锁

TODO