Java锁对象

Java锁

1. 对象头

1.1 简介

  • 以32位的 JVM 为例,每个Java对象的对象头都包含了如下信息
# 组成
Mark Word:      锁的信息,hashcode,垃圾回收器标志
Klass Word:     指针,包含当前对象的Class对象的地址,类对象来确定该对象是什么类型

# 普通对象,占用8个字节,64位
                  Object Header (64 bits)
 Mark Word(32 bits)        Klass Word(32 bits)

# 数组对象, 占用12个字节, 96位      包含额外的4个字节用来保存数组长度
                  Object Header (96 bits)
 Mark Word(32 bits)       Klass Word(32 bits)      Array Length (32 bits)

1.2 Mark Word

- 01/00/11:      代表是否加锁
- age:           垃圾回收器标记

image-20220926112525895

2. Monitor监视器

2.1 monitor

# 由 《操作系统》提供,又叫监视器或管程
# 操作系统可包含多个不同的Monitor
# 包含三部分
   1. Owner:      保存当前获取到java锁的,线程指针
   2. EntryList:   保存被java锁阻塞的,线程指针
   3. WaitSet:     保存被java锁等待的,线程指针

# synchronized 的java对象,该对象会被关联到一个monitor监视器,java对象头的Mark Word就被设置为 monitor监视器的地址
- 被Synchronized修饰的java对象, 重量级锁,不公平锁

image-20220926113801439

2.2 竞争步骤

  • java对象被synchronized修饰后
  • 当它获取到对象锁的时候,该对象就会被关联到monitor,java对象的Mark Word就会变为 prt_to_heavyweight_monitor, 即是用来保存重量级锁的地址,同时mark word中的01转变为10
1. thread-1 通过synchronized获取到一个obj对象
  1.1 obj对象头信息(hashcode,age等)变为prt_to_heavyweight_monitor(30 bit)(monitor指针)
  1.2 obj对象头的锁状态变为 10(重量级锁)
  1.3 根据monitor指针,找到monitor,将Owner设置为thread-1
   
2. thread-2 过来后,检查obj锁对象头
   2.1 发现该obj对象头的Mark Word的锁状态已经是重量级锁
   2.2 根据Mark Word中锁的地址检查到当Owner已经有其他线程了
   2.3 thread-2进入到EntryList,进行Block
  
3. thread-1 执行完临界区代码后,
      3.1 monitor的Owner进行清空
      3.2 将owner中的当前线程的owner和obj对象头中的monitor地址再次交换
      3.3 monitor唤醒EntryList中其他线程
      3.4 其他在 EntryList 中等待的线程, 再次竞争对象锁,再次设置monitor的Owner

a. synchronized(obj),就会有一个monitor监管该对象
b. 同步代码块如果发生异常时候,也会将锁释放
c. synchronized(obj), 必须关联到同一个obj,不然就不会指向同一个monitor

image-20220926120606596

3. 常见锁

3.1 轻量级锁

  • 锁对象虽被多个线程都来获取,但访问时间错开,不存在竞争
  • 轻量级锁对使用者 是透明的, 语法:syncronized
  • 当存在其他线程竞争的时候,自动升级为重量级锁

3.2 锁重入

  • 锁重入: 一个线程在调用一个方法的时候,在方法调用链中,多次使用同一个对象来加锁

image-20220928202550955

# 创建锁记录
- 线程在自己的工作内存内,创建栈帧,并在活动栈帧创建一个  《锁记录》  的结构
- 锁记录: lock record address: 加锁的信息,用来保存当前线程ID等信息, 同时后续会保存对像锁的Mark Word
          Object Reference:  用来保存锁对象的地址
          00: 表示轻量级锁, 01代表无锁
          
- 锁记录对象:是在JVM层面的,对用户无感知         
- Object Body: 该锁对象的成员变量

# 加锁cas-- compare and set
# cas成功
- 尝试cas交换Object中的 Mark Word和栈帧中的锁记录

# cas失败
- 情况一:锁膨胀,若其他线程持有该obj对象的轻量级锁,表明有竞争,进入锁膨胀过程,加重量级锁
- 情况二:锁重入,若本线程再次synchronized锁,再添加一个Lock Record作为重入计数
- 两种情况区分: 根据obj中保存线程的lock record地址来进行判断
- null: 表示重入了几次

# 解锁cas
- 退出synchronized代码块时,若为null的锁记录,表示有重入,这时清除锁记录(null清除)
- 退出synchronized代码块时,锁记录不为null,cas将Mark Word的值恢复给对象头
  同时obj头变为01无锁状态
- 成功则代表解锁成功; 失败说明轻量级锁进入了锁膨胀

3.3 锁膨胀

  • 在尝试轻量级加锁时,cas无法成功
  • 可能因为:其他线程为此对象加上了轻量级锁(有竞争),这时进行锁膨胀,锁变为重量级锁
  • 轻量级锁没有阻塞机制,重量级锁有阻塞机制

image-20220928205845097

# 加锁
- thread-0轻量级锁加锁成功
- 当thread-1进行轻量级加锁时,thread-0已经为该对象加了轻量级锁,对应的java object是00
- thread-1轻量级加锁失败,进入了锁膨胀流程

# 锁膨胀
- 为Object对象申请monitor锁,并让Object的mark word 指向重量级锁地址, 同时变为10(重量级锁)
- 然后自己进入monitor的EntryList 进行 Block

# 解锁
- 当Thread-0 退出同步块时,使用cas将Mark Word的值恢复给对象头,失败进入重量级解锁流程
- 按照Monitor地址找到Monitor,设置Owner为null,唤醒EntryList中BLOCKED线程

3.4 自旋优化-重量级锁

- 一个线程的重量级锁被其他线程持有时,该线程并不会直接进入阻塞
- 先本身自旋,同时查看锁资源在自旋优化期间是否能够释放   《避免阻塞时候的上下文切换》
- 若当前线程自旋成功(即此时持有锁的线程已经退出了同步块,释放了锁),这时线程就避免了阻塞
- 若自旋失败,则进入EntryList中
智能自旋: 
-  自适应的: Java6之后,对象刚刚的一次自旋成功,就认为自旋成功的概率大,就会多自旋几次
            反之,就少自旋几次甚至不自旋
- java7之后不能控制是否开启自旋功能
- 自旋会占用cpu时间,单核自旋就是浪费,多核自旋才有意义

4. 锁消除

- Java的 JIT, 即时编译器,对热点代码进行优化
- 逃逸分析: JVM  是根据锁对象是否可以发生逃逸分析来判断
- JVM默认开启锁消除机制
- Java中锁消除默认是打开的,会根据代码中锁关联的对象是否能够逃逸决定是否优化
- 关闭锁消除: java -XX: -EliminateLocks -jar demo.jar

4.1 消除

package com.nike.erick.d01;

public class Demo07 {
    
    public static void main(String[] args) {
        lockMethod();
        nonLockMethod();
    }

    /*虽然此时加了synchronized, 但是代码在执行的时候
     * 1. 并不存在多线程同步访问的场景,所以synchronized 被JIT优化掉了*/
    private static void lockMethod() {
        long startTime = System.currentTimeMillis();
        /*做成包装类,来增加时间*/
        Integer number = 0;
        for (int i = 0; i < 10000000; i++) {
            synchronized (new Object()) {
                number++;
            }
        }
        System.out.println("Lock Method Times: " + (System.currentTimeMillis() - startTime));
    }

    private static void nonLockMethod() {
        long startTime = System.currentTimeMillis();
        Integer number = 0;
        for (int i = 0; i < 10000000; i++) {
            number++;
        }
        System.out.println("Non Lock Method Times: " + (System.currentTimeMillis() - startTime));
    }
}

4.2 逃逸分析

  • 如果锁对象可能逃逸,那么就不会进行锁优化
private static void lockMethod() {
        boolean flag = true;
        Object lock = new Object();
        
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("逃逸代码块");
            }
        }).start();

        long start = System.currentTimeMillis();

        /**
         * 上面锁逃逸,所以并不会进行锁消除
         */
        for (int i = 0; i < 100000000; i++) {
            synchronized (lock) {
                flag = !flag;
            }
        }
        System.out.println("加锁:" + (System.currentTimeMillis() - start));
    }

5. 锁粒度细化

5.1 单锁

  • 一间屋子两个功能:睡觉,学习,互不影响(不同的方法不会访问同一个资源)
  • 如果用一个屋子(一个对象锁)的话,并发度很低
// 互不影响的功能

package com.dreamer.multithread.day04;

import java.util.concurrent.TimeUnit;

public class Demo02 {
    public static void main(String[] args) throws InterruptedException {

        long start = System.currentTimeMillis();
        BigRoom room = new BigRoom();

        // 睡觉的一类线程
        Thread firstSleep = new Thread(() -> room.sleep());
        Thread secondSleep = new Thread(() -> room.sleep());

        // 工作的一类线程
        Thread firstWork = new Thread(() -> room.work());
        Thread secondWork = new Thread(() -> room.work());

        firstSleep.start();
        secondSleep.start();
        firstWork.start();
        secondWork.start();

        firstSleep.join();
        secondSleep.join();
        firstWork.join();
        secondWork.join();

        // 一共需要 2*2+2*2 = 8s
        System.out.println("total time:" + (System.currentTimeMillis() - start));
    }
}

class BigRoom {

    /*下面两个方法,永远不会在同一个时间调用,因此用同一把锁,浪费资源
                  或者说不会使用同一个共享资源*/
    public void sleep() {
        synchronized (this) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void work() {
        synchronized (this) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

5.2 多锁

  • 一个对象中,如果不同种类方法只会被同一种线程调用,则可以进行锁粒度细化
  • 如果多把锁,被一个方法同时使用了,可能造成死锁
// 执行上面的方法,只需要4s
class BigRoom {

    private Object sleepLock = new Object();
    private Object workLock = new Object();

    /*下面两个方法,永远不会在同一个时间调用,因此用同一把锁,浪费资源*/
    public void sleep() {
        synchronized (sleepLock) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void work() {
        synchronized (workLock) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

6. 活跃性

  • 因为某种原因,多线程代码一直在执行,不能正常结束

6.1 死锁

- 线程一:持有a锁,等待b锁
- 线程二:持有b锁,等待a锁
- 互相等待引发的死锁问题
- 哲学家就餐问题
- 定位死锁: 可以借助jconsole来定位死锁
- 解决方法: 都按照相同顺序加锁就可以,但可能引发饥饿问题
package com.dreamer.multithread.day04;

import java.util.concurrent.TimeUnit;

public class Demo02 {
    public static void main(String[] args) {
        BigRoom room = new BigRoom();
        new Thread(() -> room.sleepAndWork()).start();
        new Thread(() -> room.workAndSleep()).start();
    }
}

class BigRoom {

    private final Object sleepLock = new Object();
    private final Object workLock = new Object();

    // 互相持有对方的锁
    public void sleepAndWork() {
        synchronized (sleepLock) {
            consumeTime();
            synchronized (workLock) {
                System.out.println("睡醒---工作啦");
            }
        }
    }

    public void workAndSleep() {
        synchronized (workLock) {
            consumeTime();
            synchronized (sleepLock) {
                System.out.println("工作后--要睡觉啦¬");
            }
        }
    }

    private void consumeTime() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

6.2 饥饿锁

  • 某个线程因为优先级太低,一直得不到cpu的执行

6.3 活锁

  • 两个线程中互相改变对方结束的条件,导致两个线程一直运行下去
  • 可能会结束,但是二者会交替进行
package com.dreamer.multithread.day04;

public class Demo04 {
    private static int counter = 10;

    public static void main(String[] args) {
        new Thread(() -> {
            while (counter < 20) {
                counter++;
                System.out.println(" ++ 操作:" + counter);
            }

        }).start();

        new Thread(() -> {
            while (counter > 0) {
                counter--;
                System.out.println(" -- 操作:" + counter);
            }
        }).start();
    }
}

Wait/Notify

  • 当线程执行任务的时候,发现条件不满足,则进行wait
  • 条件满足后,通过notify来再次执行任务

1. 基本使用

1.1 API

wait()  notify()  notifyAll()
Object类的方法,必须成为锁的owner时候才能使用

# 当前线程进入WaitSet, 一直等待
public final void wait() throws InterruptedException

# 当前线程只等待一定时间,然后从 WaitSet 重新进入EntryList来竞争锁资源
public final native void wait(long timeoutMillis) throws InterruptedException

# 随便唤醒一个线程,进入到EntryList
public final native void notify()

# 唤醒所有的线程,进入到EntryList
public final native void notifyAll()

1.2 原理

  • Owner线程发现条件不满足,调用wait,即进入WaitSet变为WAITING状态
  • wait会释放当前锁资源
1. BLOCK和WAITING的线程都处于阻塞状态,不占用cpu
2. BLOCK线程会在Owner线程释放锁时唤醒
3. WAITING线程会在Owner线程调用notify时唤醒,但唤醒后只是进入EntryList重新竞争锁

image-20220929091827634

1.3 Demo

package com.nike.erick.d02;

import java.util.concurrent.TimeUnit;

public class Demo01 {

    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread firstThread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("first thread coming");
                    /*进入WaitSet*/
                    lock.wait();
                    System.out.println("first thread ending....");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread secondThread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("second thread coming");
                    lock.wait();
                    System.out.println("second thread ending....");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        firstThread.start();
        secondThread.start();

        TimeUnit.SECONDS.sleep(2);

        /*唤醒线程必须也先获取到锁*/
        synchronized (lock) {
            /*唤醒多个线程*/
            lock.notifyAll();
        }
    }
}
  • 要wait或者notify,必须先获取到锁资源
package com.nike.erick.d02;

public class Demo02 {
    private static Object lock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                /*不能直接wait,要先获取到锁资源
                * IllegalMonitorStateException */
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

1.4 wait VS sleep

1. Wait 是Object的方法                     Sleep 是Thread 的静态方法
2. Wait 必须和synchronized结合使用          Sleep 不需要
3. Wait 会放弃当前线程的锁资源               Sleep 不会释放锁(如果工作时候带锁)
4. 都会让出cpu资源,状态都是Timed-Waiting

2.wait/notify正确使用

  • 循环wait: 防止虚假唤醒的问题,确保线程一定是执行完毕任务后才会结束
// 工作线程
synchronized(lock){
  while(条件不成立){
    lock.wait();
  }
  executeBusiness();
}//其他线程唤醒
synchronized(lock){
  // 实现上述条件
  lock.notifyAll();
}

2.1. 单个线程wait

package com.erick.multithread.d1;

import java.util.concurrent.TimeUnit;

public class Demo02 {
    private static final Object lock = new Object();

    private static boolean hasCigarette = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            synchronized (lock) {
                /*while循环: 解决虚假唤醒问题*/
                while (!hasCigarette) {
                    System.out.println("烟没到,休息会儿");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

                System.out.println("烟来了,开始干活");
            }
        }, "t1").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    System.out.println("其他人开始干活");
                }
            }).start();
        }

        TimeUnit.SECONDS.sleep(2);
        synchronized (lock) {
            hasCigarette = true;
            System.out.println("烟到了");
            lock.notify();
        }
    }
}

2.2 多个线程wait

  • java 11 中,谁先进入WaitSet, notify先唤醒谁
 package com.erick.multithread.d1;

import java.util.concurrent.TimeUnit;

public class Demo03 {
    private static final Object lock = new Object();

    private static boolean hasCigarette = false;

    private static boolean hasDinner = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            synchronized (lock) {
                while (!hasCigarette) {
                    System.out.println("烟没到,休息会儿");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("烟来了,开始干活");
            }
        }, "t1").start();

        new Thread(() -> {
            synchronized (lock) {
                while (!hasDinner) {
                    System.out.println("外卖没到,休息会儿");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("外卖来了,开始干活");
            }
        }, "t2").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (lock){
                    System.out.println("其他人开始干活");
                }
            }).start();
        }

        TimeUnit.SECONDS.sleep(2);
        synchronized (lock) {
            System.out.println("烟来了");
            hasCigarette = true;
            lock.notifyAll();
        }
    }
}

3. 保护性暂停模式

  • 一个线程等待另一个线程的一个执行结果
  • Guarded Suspension Design Pattern
- 一个结果需要从一个线程传递到另一个线程,让两个线程关联同一个GuardedObject
- JDK中, join的实现,Future的实现,就是采用Guarded Suspension
- 同步模式

image-20220929120241558

3.1 无限等待

package com.erick.multithread.d1;

import java.util.concurrent.TimeUnit;

public class Demo04 {

    private static GuardedResponse response = new GuardedResponse();

    public static void main(String[] args) {
        new Thread(() -> System.out.println(response.obtainResult())).start();

        new Thread(() -> response.populateResult()).start();
    }
}

class GuardedResponse {

    private Object result;

    public synchronized Object obtainResult() {
        while (null == result) {
            try {
                System.out.println("暂未获取到资源");
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("已经获取到资源");
        return result;
    }

    public synchronized void populateResult() {
        result = heavyWork();
        this.notifyAll();
    }

    private Object heavyWork(){
        try {
            TimeUnit.SECONDS.sleep(3);
            return new Object();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

3.2 超时等待

  • 如果获取结果不到,那么就返回
package com.nike.erick.d04;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

public class Demo06 {
    private static GuardResponse guardResponse = new GuardResponse();

    public static void main(String[] args) {
        new Thread(() -> System.out.println(guardResponse.getResponse(2000))).start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            guardResponse.setResponse(new ArrayList<>());
        }).start();
    }
}

class GuardResponse {
    private Object response;

    public Object getResponse(long timeoutMills) {
        synchronized (this) {
            /*开始时间*/
            long startTime = System.currentTimeMillis();
            /*经过了多长时间*/
            long passedTime = 0;

            while (response == null) {
                long leftTime = timeoutMills - passedTime;
                /*如果经过的时间大于了等待时间,则退出*/
                if (leftTime <= 0) {
                    break;
                }
                /*动态等待*/
                try {
                    this.wait(leftTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                /*因为在wait时候可能被虚假唤醒*/
                passedTime = System.currentTimeMillis() - startTime;
            }
            return response;
        }
    }

    public void setResponse(Object response) {
        synchronized (this) {
            this.response = response;
            this.notify();
        }
    }
}

3.3 Join原理

  • 底层就是使用了保护性暂停模式

4. 生产者消费者

  • 如果有结果从一类线程不断的传递到其他类线程,可以使用消息队列(生产者消费者)
  • 多个生产者及多个消费者, 阻塞队列, 异步消费
  • 消息队列,先入先得,有容量限制,满时不再添加消息,空时不再消费消息
  • JDK中各种阻塞队列,就是用的这种方式

image-20220929180221221

package com.erick.multithread.d1;

import java.util.LinkedList;

public class Demo05 {
    public static void main(String[] args) {
        MessageBroker<String> messageBroker = new MessageBroker<>(5);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> messageBroker.sendMessage("hello:" + Thread.currentThread().getName())).start();
        }

        for (int i = 0; i < 2; i++) {
            new Thread(() -> messageBroker.consumeMessage()).start();
        }
    }
}

class MessageBroker<T> {
    /*阻塞队列大小*/
    private int capacity;

    private LinkedList<T> blockingQueue = new LinkedList();

    public MessageBroker(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void sendMessage(T message) {
        while (blockingQueue.size() >= capacity) {
            try {
                System.out.println("队列已满,请稍后再发送消息");
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        blockingQueue.addFirst(message);
        System.out.println("添加成功:" + Thread.currentThread().getName());
        this.notifyAll(); // 唤醒所有的生产者和消费者线程,不用担心虚假唤醒问题
    }

    public synchronized T consumeMessage() {
        while (blockingQueue.size() <= 0) {
            try {
                System.out.println("当前暂无消息,请稍后再消费");
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        T message = blockingQueue.removeLast();
        this.notifyAll();
        return message;
    }
}

5. Park/Unpark

5.1. 基本使用

  • 先park,再unpark
  • park后是waiting状态,会释放锁
# 暂停当前线程
java.util.concurrent.locks.LockSupport

# 在哪个线程中使用,就暂停哪个线程
public static void park()

# 恢复一个线程
public static void unpark(Thread thread)
package com.dreamer.multithread.day04;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class Demo01 {
    public static void main(String[] args) throws InterruptedException {
        Thread slaveThread = new Thread("slave-thread") {
            @Override
            public void run() {
                System.out.println("prepare for PARK....");
                LockSupport.park();
                System.out.println("PARK ended");
            }
        };
        slaveThread.start();

        TimeUnit.SECONDS.sleep(2);

        LockSupport.unpark(slaveThread);
    }
}

5.2. 先unpark后park

  • 先unpark,再park,线程就不会停下来了
package com.dreamer.multithread.day04;

import java.util.concurrent.locks.LockSupport;

public class Demo01 {
    public static void main(String[] args) {
        Thread slaveThread = new Thread("slave-thread") {
            @Override
            public void run() {
                LockSupport.unpark(Thread.currentThread());
                System.out.println("prepare for PARK....");
                LockSupport.park();
                System.out.println("PARK ended");
            }
        };
        slaveThread.start();
    }
}

5.3 wait/park

# 二者都会使线程进入waitset等待,都会释放锁

wait/notify是Object的方法                    park/unpark是LockSupport
wait/notify 必须和synchronized结合使用        park/unpark不必
wait/notify 顺序不能颠倒                      park/unpark可以颠倒
wait/notify 只能随机唤醒一个或者全部唤醒         park/unpark可以指定一个线程唤醒

ReentryLock

1. 基本特性

1.1 可重入锁

  • 一个线程已经获取锁,因为该线程是该锁主人。第二次获取该锁时,依然可以获取到该锁
  • 不可重入:第二次获取就会造成死锁,部分锁就是这种情况
package com.erick.multithread.d2;

import java.util.concurrent.locks.ReentrantLock;

public class Demo01 {
    public static void main(String[] args) {
        FirstTest firstTest = new FirstTest();
        firstTest.firstMethod();
    }
}


class FirstTest{
    private ReentrantLock lock = new ReentrantLock();

    public void firstMethod(){
        try {
            lock.lock();
            System.out.println("first method coming");
            secondMethod();
        }finally {
            /*unlock必须放在finally中,保证锁一定可以释放*/
            lock.unlock();
        }
    }

    public void secondMethod(){
        try{
            lock.lock();
            System.out.println("second method coming");
        }finally {
            lock.unlock();
        }
    }
}

1.2 可打断锁

  • 被动的方式: 避免一直等待带来的死锁问题
- 没有其他线程争夺锁,则正常执行
- 有竞争时,线程就会进入EntryList,但是可以被打断
- 其他线程先获取锁,执行一段时间后,等待获取锁的线程 《打断等待》
无竞争
package com.erick.multithread.d2;

import java.util.concurrent.locks.ReentrantLock;

public class Demo02 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        new Thread(() -> {
            /*第一个try表示可以被打断*/
            try{
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                System.out.println("锁被打断了");
                throw new RuntimeException(e);
            }
            /*第二个try表示释放锁*/
            try{
                doBusiness();
            }finally {
                lock.unlock();
            }
        }).start();
    }

    private static void doBusiness(){
        System.out.println("do business logic");
    }
}
有竞争
package com.erick.multithread.d2;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class Demo03 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        Thread firstThread = new Thread(() -> {
            /*获取锁的时候,可以被打断,就结束当前等待过程*/
            try {
                System.out.println("first-thread 开始等待锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                System.out.println("first-thread 锁被打断");
                throw new RuntimeException(e);
            }

            try {
                doBusiness();
            } finally {
                lock.unlock();
            }
        });

        Thread secondThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    /*不会释放锁*/
                    sleep(2);
                    firstThread.interrupt();
                    System.out.println("second-thread完成业务");
                } finally {
                    lock.unlock();
                }
            }
        });

        secondThread.start();
        sleep(1);
        firstThread.start();
    }

    private static void doBusiness() {
        System.out.println("do business logic");
    }

    private static void sleep(int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

1.3 公平锁

# 1. 不公平锁
- 当一个线程持有锁的时候,其他线程进入锁的 EntryList
- 当线程释放锁的时候,其他线程一拥而上,而不是按照进入的顺序先到先得

# 2. 公平锁: 通过ReentranLock实现
- 默认是非公平锁,传参为true,公平锁
package com.erick.multithread.d2;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class Demo04 {

    private static ReentrantLock lock = new ReentrantLock(true);

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                lock.lock();
                sleep(5);
            } finally {
                lock.unlock();
            }
        }).start();

        sleep(1);

        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + " running");
                } finally {
                    lock.unlock();
                }
            }).start();

            sleep(1);
        }
    }

    private static void sleep(int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

1.4 超时锁

  • 避免死锁: 主动方式来避免一个线程一直等待锁资源,带来的死锁问题
不带超时
  • 正常获取到锁
package com.erick.multithread.d2;

import java.util.concurrent.locks.ReentrantLock;

public class Demo05 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(() -> {
            boolean hasLock = lock.tryLock();
            if (!hasLock) {
                System.out.println("没有获取到锁,放弃");
                return;
            }

            try{
                executeBusiness();
            }finally {
                lock.unlock();
            }
        }).start();
    }

    private static void executeBusiness(){
        System.out.println("do business logic");
    }
}
  • 最终没获取到锁
package com.erick.multithread.d2;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class Demo06 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(() -> {
            try{
                lock.lock();
                sleep(5);
            }finally {
                lock.unlock();
            }
        }).start();

        new Thread(() -> {
            boolean hasLock = lock.tryLock();
            if (!hasLock){
                System.out.println("没有获取到锁,放弃等待");
                return;
            }

            try{
                executeBusiness();
            }finally {
                lock.unlock();
            }
        }).start();
    }

    private static void executeBusiness(){
        System.out.println("do business logic");
    }

    private static void sleep(int seconds){
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
带超时
  • 等待过程中,如果获取到了锁,则执行正常的业务流程
  • 等待一段时间后,如果没有获取到锁,则中断获取锁的竞争
  • 等待过程中,依然可以被打断
ReentrantLock        public boolean tryLock(long timeout, TimeUnit unit)
                             throws InterruptedException
package com.nike.erick.d03;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class Demo06 {

    private static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        Thread firstThread = new Thread(() -> {
            reentrantLock.lock();
            try {
                TimeUnit.SECONDS.sleep(2);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        });

        Thread secondThread = new Thread(() -> {
            try {
                /*最长等待3s*/
                boolean hasLock = reentrantLock.tryLock(3, TimeUnit.SECONDS);
                if (!hasLock) {
                    System.out.println("没有获取到锁");
                    return;
                }

                try {
                    System.out.println("Execution Business...");
                } finally {
                    reentrantLock.unlock();
                }

            } catch (InterruptedException e) {
                System.out.println("等待锁过程中被打断了。。。");
                e.printStackTrace();
            }
        });

        firstThread.start();
        TimeUnit.SECONDS.sleep(1);
        secondThread.start();
        secondThread.interrupt();
    }
}

2. 条件变量

  • 多WaitSet, 可以和wait/notify更好的结合
  • ReentrantLock: 可以将等待的不同队列分类,然后根据队列来进行唤醒
# 1. 创建一个等待的队列
ReentrantLock    public Condition newCondition()

# 2. 将一个线程在某个队列中进行等待
Condition        public final void await() throws InterruptedException

# 3. 去某个队列中唤醒等待的线程
Condition        public final void signal()
                 public final void signalAll()
package com.nike.erick.d03;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Demo07 {

    private static ReentrantLock reentrantLock = new ReentrantLock(true);

    private static Condition boyRoom = reentrantLock.newCondition();
    private static Condition girlRoom = reentrantLock.newCondition();

    private static boolean hasCigarette = false;
    private static boolean hasDinner = false;

    public static void main(String[] args) throws InterruptedException {
        /*抽烟线程*/
        for (int i = 0; i < 5; i++) {
            int boyNo = i;
            new Thread(() -> {
                reentrantLock.lock();
                while (true) {
                    if (!hasCigarette) {
                        try {
                            System.out.println("没有烟,女孩等会-" + boyNo);
                            boyRoom.await();// Condition的方法
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        break;
                    }

                    try {
                        System.out.println("男孩打仗-" + boyNo);
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            }).start();
        }

        for (int i = 0; i < 5; i++) {
            int girlNo = i;
            new Thread(() -> {
                reentrantLock.lock();
                while (true) {
                    if (!hasDinner) {
                        try {
                            System.out.println("没有外卖,女孩等会-" + girlNo);
                            girlRoom.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        break;
                    }
                }
                try {
                    System.out.println("女孩做鞋-" + girlNo);
                } finally {
                    reentrantLock.unlock();
                }
            }).start();
        }

        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            reentrantLock.lock();
            try {
                hasCigarette = true;
                boyRoom.signalAll(); // 唤醒,让男孩线程准备获取锁资源
            } finally {
                reentrantLock.unlock();
            }
        }).start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            reentrantLock.lock();
            try {
                hasDinner = true;
                girlRoom.signalAll(); // 唤醒,让女孩线程准备获取锁资源
            } finally {
                reentrantLock.unlock();
            }
        }).start();
    }
}

3. ReentrantLock vs Synchronized

可重入性:   Synchronized 和 ReentrantLock都支持
可打断性:   Synchronized锁不能被打断                 ReentrantLock可以被打断,防止死锁
超时性:     Synchronized锁获取时候会一直等待          ReentrantLock支持超时等待
公平性:     Synchronized的EntryList是不公平         ReentrantLock(true)公平锁
条件变量:   Synchronized的WaitSet只有一个            ReentrantLock支持不同的WaitSet

固定顺序输出

1. 先2后1

  • 线程2运行完毕后,线程1再开始运行

1.1 synchronized + wait + notify

package com.erick.multithread.d3;

public class Demo01 {
    private static Object lock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                System.out.println("first-thread running");
            }
        }).start();

        new Thread(() -> {
            synchronized (lock) {
                System.out.println("second-thread running");
                lock.notifyAll();
            }
        }).start();
         
    }
}

1.2 reentrylock + await + signal

package com.erick.multithread.d3;

import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Demo02 {
    private static ReentrantLock lock = new ReentrantLock();

    private static Condition room = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                lock.lock();
                room.await();
                System.out.println("线程一执行完毕");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }).start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            try{
                lock.lock();
                System.out.println("线程二执行完毕");
                room.signal();
            }finally {
                lock.unlock();
            }
        }).start();
    }
}

1.3 park + unpark

package com.erick.multithread.d3;

import java.util.concurrent.locks.LockSupport;

public class Demo03 {
    public static void main(String[] args) {
        Thread firstThread = new Thread(() -> {
            LockSupport.park();
            System.out.println("线程一运行完毕");
        });

        firstThread.start();

        new Thread(() -> {
            System.out.println("线程二运行完毕");
            LockSupport.unpark(firstThread);
        }).start();
    }
}

2. 交替输出

  • 五个线程交替输出abcde

2.1 synchronized + wait + notify

package com.erick.multithread.d3;

import java.util.Objects;

public class Demo04 {
    private static Object lock = new Object();

    private static String baseChar = "a";

    public static void main(String[] args) {
        new Thread(() -> printCharacter("a", "b")).start();
        new Thread(() -> printCharacter("b", "c")).start();
        new Thread(() -> printCharacter("c", "d")).start();
        new Thread(() -> printCharacter("d", "e")).start();
        new Thread(() -> printCharacter("e", "a")).start();
    }

    private static void printCharacter(String srcChar, String targetChar) {
        while (true) {
            synchronized (lock) {
                while (!Objects.equals(baseChar, srcChar)) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println(Thread.currentThread().getName() + ":" + baseChar);
                baseChar = targetChar;
                lock.notifyAll();
            }
        }
    }
}

2.2 reentrylock + await + signal

package com.erick.multithread.d3;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Demo05 {
    private static String baseChar = "a";
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition firstRoom = lock.newCondition();
    private static Condition secondRoom = lock.newCondition();
    private static Condition thirdRoom = lock.newCondition();
    private static Condition fourthRoom = lock.newCondition();
    private static Condition fifthRoom = lock.newCondition();

    public static void main(String[] args) {
        new Thread(() -> printCharacter("a", "b", firstRoom, secondRoom)).start();
        new Thread(() -> printCharacter("b", "c", secondRoom, thirdRoom)).start();
        new Thread(() -> printCharacter("c", "d", thirdRoom, fourthRoom)).start();
        new Thread(() -> printCharacter("d", "e", fourthRoom, fifthRoom)).start();
        new Thread(() -> printCharacter("e", "a", fifthRoom, firstRoom)).start();
    }

    private static void printCharacter(String printChar, String targetChar, Condition waitRoom, Condition signalRoom) {
        /*利用不同的condition,不用考虑虚假唤醒问题*/
        while (true) {
            try {
                lock.lock();
                if (baseChar != printChar) {
                    waitRoom.await();
                }

                System.out.println(Thread.currentThread().getName() + ":" + printChar);
                baseChar = targetChar;
                signalRoom.signal();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
    }
}

2.3 park + unpark

package com.erick.multithread.d3;

import java.util.concurrent.locks.LockSupport;

public class Demo06 {
    private static String baseChar = "a";
    private static Thread firstThread;
    private static Thread secondThread;
    private static Thread thirdThread;
    private static Thread fourthThread;
    private static Thread fifthThread;

    public static void main(String[] args) {
        firstThread = new Thread(() -> printCharacter("a", "b", secondThread));
        secondThread = new Thread(() -> printCharacter("b", "c", thirdThread));
        thirdThread = new Thread(() -> printCharacter("c", "d", fourthThread));
        fourthThread = new Thread(() -> printCharacter("d", "e", fifthThread));
        fifthThread = new Thread(() -> printCharacter("e", "a", firstThread));

        firstThread.start();
        secondThread.start();
        thirdThread.start();
        fourthThread.start();
        fifthThread.start();
    }

    private static void printCharacter(String printChar, String targetChar, Thread nextThread) {
        while (true){
            if (baseChar != printChar) {
                LockSupport.park();
            }
            System.out.println(Thread.currentThread().getName() + ":" + printChar);
            baseChar = targetChar;
            LockSupport.unpark(nextThread);
        }
    }
}

3. 交替输出奇偶数

  • 两个线程,交替输出奇偶数

3.1 synchronized + wait + notify

package com.erick.multithread.d3;

public class Demo07 {

    private static Object lock = new Object();
    private static int number = 0;

    public static void main(String[] args) {
        new Thread(() -> printNum(), "t1").start();
        new Thread(() -> printNum(), "t2").start();
    }

    private static void printNum() {
        for (int i = 0; i < 10; i++) {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + ":" + number);
                number++;
                lock.notify();
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

3.2 reentrylock + await + signal

package com.erick.multithread.d3;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Demo08 {
    private static int number = 0;
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition room = lock.newCondition();

    public static void main(String[] args) {
        new Thread(() -> printNum(), "t1").start();
        new Thread(() -> printNum(), "t2").start();
    }

    private static void printNum() {
        for (int i = 0; i < 10; i++) {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + ":" + number);
                number++;
                room.signal();
                try {
                    room.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

3.3 park + unpark

package com.erick.multithread.d3;

import java.util.concurrent.locks.LockSupport;

public class Demo09 {
    private static int number = 0;

    private static Thread firstThread;
    private static Thread secondThread;

    public static void main(String[] args) {
        firstThread = new Thread(() -> printNum(secondThread));
        secondThread = new Thread(() -> printNum(firstThread));

        firstThread.start();
        secondThread.start();

        // 触发
        LockSupport.unpark(firstThread);
    }

    private static void printNum(Thread nextThread) {
        for (int i = 0; i < 10; i++) {
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + ":" + number);
            number++;
            LockSupport.unpark(nextThread);
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值