java并发编程(3) 共享模型之管程 4


前言

这一系列资料基于黑马的视频:java并发编程,目前还没有看完,整体下来这是我看过的最好的并发编程的视频。下面是根据视频做的笔记。在下面的笔记中,还穿插 Java 并发编程这本书的讲解


1. 活跃性

1. 死锁问题

部分资料引用《Java并发编程的艺术》一书

在使用锁的时候,如果对于锁的使用不正确,那么容易造成死锁。比如线程 t1 获取到了 A 的锁,然后再想要获取 B 的锁,而线程 t2 获取到了 B 的锁,想要获取 A 的锁。这时候两个线程都无法运行,它们都需要对方所拿到的资源,这时候两个线程就无法继续运行,这就是死锁。

当然了,我们在平时的项目中是不会这么明显地写出这种死锁的,但是项目中难免会出现线程异常的情况,特别是一些复杂的项目中,有可能有这种问题, t1 线程拿到锁之后因为一些异常情况没有来得及释放,又或者是 t1 拿到一个数据库锁,释放锁的时候抛出了异常,没释放掉。

一旦出现了死锁,我们是可以感觉得到的,业务会停止不能运行下去,这时候要通过dump 线程来查看是哪个线程出现问题。比如下面的代码:线程 A 获取了锁 A,但是需要锁 B,线程 B 获取了锁 B,需要锁 A

public class DeadLock {

    private static String A = "A";
    private static String B = "B";

    private static void deadLock(){
        Thread t1 = new Thread(()->{
            synchronized (A){
                System.out.println("线程A拿到了锁A, 需要锁B");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B){

                }
            }
        }, "线程A");

        Thread t2 = new Thread(()->{
            synchronized (B){
                System.out.println("线程B拿到了锁B, 需要锁A");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A){

                }
            }
            //这里应该是 B 的,知道就行
        }, "线程A");

        t1.start();
        t2.start();
    }


    public static void main(String[] args) {
        deadLock();
        //线程A拿到了锁A, 需要锁B
        //线程B拿到了锁B, 需要锁A
    }
}

我们通过 dump 线程来查看死锁:

  1. 使用 jps 查看死锁线程
		D:\javaCode\JULLearn>jps
		56160 Launcher
		36504 DeadLock
		36648 Jps
		49304

  1. 使用 jstack 命令输出死锁消息到文件中,可以看到在 40 行和 26 行出现了死锁了。也就是线程 A 中获取 B 锁那段,线程 B 中获取 A锁 那段。
 jstack 36504 > deadLock.log
  1. 打开文件,找到死锁信息, 两个线程 A 是因为上面复制的时候忘记改名字了
Found one Java-level deadlock:
=============================
"线程A":
  waiting to lock monitor 0x000001ad809541b8 (object 0x000000076ba739d8, a java.lang.String),
  which is held by "??程A"
"线程A":
  waiting to lock monitor 0x000001ad80956af8 (object 0x000000076ba73a08, a java.lang.String),
  which is held by "线程A"

Java stack information for the threads listed above:
===================================================
"线程A":
	//40行
	at com.jianglianghao.HeiMaJUC.Unit4.DeadLock.lambda$deadLock$1(DeadLock.java:40)
	- waiting to lock <0x000000076ba739d8> (a java.lang.String)
	- locked <0x000000076ba73a08> (a java.lang.String)
	at com.jianglianghao.HeiMaJUC.Unit4.DeadLock$$Lambda$2/1607521710.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)
	
"线程A":
	//26行
	at com.jianglianghao.HeiMaJUC.Unit4.DeadLock.lambda$deadLock$0(DeadLock.java:26)
	- waiting to lock <0x000000076ba73a08> (a java.lang.String)
	- locked <0x000000076ba739d8> (a java.lang.String)
	at com.jianglianghao.HeiMaJUC.Unit4.DeadLock$$Lambda$1/1828972342.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.



避免死锁的几个常用方法:

  • 避免一个线程同时获取多个锁
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
  • 尝试使用定时锁,使用 lock.tryLock( timeout ) 方法来替代内部的锁机制,好处就是获取不到锁可以即使放弃,避免死锁
  • 对于数据库锁,加锁和解锁必须在一个数据库连接池中,否则会出现解锁失败的情况。



2. 哲学家就餐问题

在这里插入图片描述
有五位哲学家,围坐在圆桌旁。 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。 如果筷子被身边的人拿着,自己就得等待

当每个哲学家即线程持有一根筷子时,他们都在等待另一个线程释放锁,因此造成了死锁。这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情况.

我们可以通过 tryLock() 来解决这个问题:流程,每个人都先用 tryLock 获取另一边手的筷子,获取不了就释放锁,这样就能保证即使自己不用也能及时释放掉

public class TestDeadLock {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("苏格拉底", c1, c2).start();
        new Philosopher("柏拉图", c2, c3).start();
        new Philosopher("亚里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c1, c5).start();
    }
}

@Slf4j
class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            // 尝试获得左手筷子
            if(left.tryLock()) {
                //尝试获取做筷子锁
                try {
                    //尝试获取右筷子锁
                    if(right.tryLock()){
                        try{
                            eat();
                        }finally{
                            right.unlock();
                        }
                    }
                }finally {
                    //如果获取不到右手就会自己施放左手
                    left.unlock();
                }
            }
        }
    }

    Random random = new Random();
    private void eat() {
        log.debug("eating...");
        sleep.mySleep(1);
    }
}

class Chopstick extends ReentrantLock {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

DEBUG [亚里士多德] (22:28:53,840) (TestDeadLock.java:67) - eating...
DEBUG [苏格拉底] (22:28:53,840) (TestDeadLock.java:67) - eating...
DEBUG [亚里士多德] (22:28:54,860) (TestDeadLock.java:67) - eating...
DEBUG [苏格拉底] (22:28:54,863) (TestDeadLock.java:67) - eating...
DEBUG [亚里士多德] (22:28:55,860) (TestDeadLock.java:67) - eating...
DEBUG [阿基米德] (22:28:55,876) (TestDeadLock.java:67) - eating...
DEBUG [柏拉图] (22:28:56,866) (TestDeadLock.java:67) - eating...
DEBUG [赫拉克利特] (22:28:56,882) (TestDeadLock.java:67) - eating...
DEBUG [苏格拉底] (22:28:57,872) (TestDeadLock.java:67) - eating...
DEBUG [赫拉克利特] (22:28:57,887) (TestDeadLock.java:67) - eating...
DEBUG [苏格拉底] (22:28:58,877) (TestDeadLock.java:67) - eating..



3. 活锁

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如 定义一个 count,初始值是10,然后线程1每一秒 + 1,线程2 每一秒 -1, 线程1的结束条件是 count 变成 20,线程2 的结束条件是 count 变成 0,而这时候这两个线程永远都无法结束。

@Slf4j
public class TestLiveLock {

    static volatile AtomicInteger count = new AtomicInteger(10);

    static final Object obj = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            while(true){
                try {
                    Thread.sleep(1000);
                    if(count.decrementAndGet() == 0){
                        break;
                    }
                    log.debug("线程 t1 ==> {}", count.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
        }, "t1").start();

        new Thread(()->{
            while(true){
                try {
                    Thread.sleep(1000);
                    if(count.getAndIncrement() == 0){
                        break;
                    }
                    log.debug("线程 t2 ==> {}", count.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                //"D:\Java\jdk-8u281-windows X64\bin\java.exe" "-javaagent:D:\IDAE\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar=61052:D:\IDAE\IntelliJ IDEA 2020.3.2\bin" -Dfile.encoding=UTF-8 -classpath "D:\java\jdk-8u281-windows X64\jre\lib\charsets.jar;D:\java\jdk-8u281-windows X64\jre\lib\deploy.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\access-bridge-64.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\cldrdata.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\dnsns.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\jaccess.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\jfxrt.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\localedata.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\nashorn.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\sunec.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\sunjce_provider.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\sunmscapi.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\sunpkcs11.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\zipfs.jar;D:\java\jdk-8u281-windows X64\jre\lib\javaws.jar;D:\java\jdk-8u281-windows X64\jre\lib\jce.jar;D:\java\jdk-8u281-windows X64\jre\lib\jfr.jar;D:\java\jdk-8u281-windows X64\jre\lib\jfxswt.jar;D:\java\jdk-8u281-windows X64\jre\lib\jsse.jar;D:\java\jdk-8u281-windows X64\jre\lib\management-agent.jar;D:\java\jdk-8u281-windows X64\jre\lib\plugin.jar;D:\java\jdk-8u281-windows X64\jre\lib\resources.jar;D:\java\jdk-8u281-windows X64\jre\lib\rt.jar;D:\javaCode\JULLearn\target\classes;D:\tools\java\maven\repository\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar;D:\tools\java\maven\repository\org\slf4j\slf4j-log4j12\1.7.21\slf4j-log4j12-1.7.21.jar;D:\tools\java\maven\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;D:\tools\java\maven\repository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;D:\tools\java\maven\repository\org\openjdk\jol\jol-core\0.16\jol-core-0.16.jar" com.jianglianghao.HeiMaJUC.Unit4.TestLiveLock
                //DEBUG [t1] (22:53:07,615) (TestLiveLock.java:30) - 线程 t1 ==> 10
                //DEBUG [t2] (22:53:07,615) (TestLiveLock.java:46) - 线程 t2 ==> 11
                //DEBUG [t2] (22:53:08,623) (TestLiveLock.java:46) - 线程 t2 ==> 10
                //DEBUG [t1] (22:53:08,623) (TestLiveLock.java:30) - 线程 t1 ==> 10
                //DEBUG [t2] (22:53:09,631) (TestLiveLock.java:46) - 线程 t2 ==> 11
                //DEBUG [t1] (22:53:09,631) (TestLiveLock.java:30) - 线程 t1 ==> 10
                //DEBUG [t2] (22:53:10,642) (TestLiveLock.java:46) - 线程 t2 ==> 11
                //DEBUG [t1] (22:53:10,642) (TestLiveLock.java:30) - 线程 t1 ==> 10
                //DEBUG [t1] (22:53:11,649) (TestLiveLock.java:30) - 线程 t1 ==> 10
                //DEBUG [t2] (22:53:11,649) (TestLiveLock.java:46) - 线程 t2 ==> 11
                //DEBUG [t1] (22:53:12,657) (TestLiveLock.java:30) - 线程 t1 ==> 10
                //DEBUG [t2] (22:53:12,657) (TestLiveLock.java:46) - 线程 t2 ==> 10
                //DEBUG [t2] (22:53:13,659) (TestLiveLock.java:46) - 线程 t2 ==> 10
                //DEBUG [t1] (22:53:13,659) (TestLiveLock.java:30) - 线程 t1 ==> 10
                //DEBUG [t1] (22:53:14,666) (TestLiveLock.java:30) - 线程 t1 ==> 10
                //DEBUG [t2] (22:53:14,666) (TestLiveLock.java:46) - 线程 t2 ==> 11
                //DEBUG [t1] (22:53:15,675) (TestLiveLock.java:30) - 线程 t1 ==> 9
                //DEBUG [t2] (22:53:15,675) (TestLiveLock.java:46) - 线程 t2 ==> 10
            }
        }, "t2").start();
    }
}

解决方法就是改变睡眠时间。



4. 饥饿

很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,读写锁时会涉及饥饿问题。

下面图片中,两个线程获取锁的顺序相同的,这时候就产生了死锁问题

解决方法:我们改变获取锁的顺序,比如线程 A 先获取锁 A,然后 B 线程尝试获取 锁 B,这时候发现获取不了,A 线程获取锁 B,B线程尝试获取锁 B,发现获取不了。这时就避免了死锁问题。
在这里插入图片描述



5. ReentrantLock

相对于 synchronized 它具备如下特点

  • 可中断
  • 可设置超时时间
  • 可以设置为公平锁,每个线程都有机会竞争、
  • 支持多个条件变量
    与 synchronized 一样,都支持可重入

基本语法:

// 获取锁
reentrantLock.lock();
try {
 // 临界区
} finally {
 // 释放锁
 reentrantLock.unlock();
}



1. 可重入

  • 可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
  • 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
  • 这里的再次进入的意思是:已经第一次获取锁了,可以再次获取
代码
@Slf4j
public class Test22 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try {
            log.debug("entry main");
            m1();
        }finally {
            lock.unlock();
        }
    }

    public static void m1(){
        lock.lock();
        try {
            log.debug("entry m1");
            m2();
        }finally {
            lock.unlock();
        }
    }

    public static void m2(){
        lock.lock();
        try {
            log.debug("entry m2");
        }finally {
            lock.unlock();
        }
    }
    //DEBUG [main] (13:11:56,073) (Test22.java:21) - entry main
    //DEBUG [main] (13:11:56,075) (Test22.java:31) - entry m1
    //DEBUG [main] (13:11:56,075) (Test22.java:41) - entry m2
}



2. 可打断

使用lock.lockInterruptibly();方法去获取锁,当别的线程获取了,lock 是可以被打断的,会抛出异常


@Slf4j
public class Test23 {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            //如果没有竞争那么次方法就会获取lock对象锁
            //如果有竞争就会进入阻塞队列,可以被其他线程使用interrupt方法打断
            try {
                log.debug("尝试获得锁");
                //lock.lockInterruptibly():lockInterruptibly()方法比较特殊,
                // 当通过这个方法去获取锁时,如果其他线程正在等待获取锁,则这个线程能够响应中断,
                // 即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()
                // 想获取某个锁时,假若此时线程A获取到了锁,而线程B只有等待,那么对线程B调用threadB.interrupt()
                // 方法能够中断线程B的等待过程。
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("被打断,没有获得锁,返回");
                return;
            }

            try {
                log.debug("获取到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        thread.start();
        sleep.mySleep(1);
        thread.interrupt();
        //DEBUG [t1] (13:22:49,872) (Test23.java:25) - 尝试获得锁
        //java.lang.InterruptedException
        //	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
        //	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
        //	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
        //	at com.jianglianghao.HeiMaJUC.Unit4.Test23.lambda$main$0(Test23.java:31)
        //	at java.lang.Thread.run(Thread.java:748)
        //DEBUG [t1] (13:22:50,876) (Test23.java:34) - 被打断,没有获得锁,返回
    }
}



3. 锁超时

可以给锁设置获取时间,在规定时间内获取不到就直接不获取了,不抛出异常,打断会抛出异常

@Slf4j
//可以设置获取不到锁就返回,不用一直等待
public class Test24 {

    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            //尝试获取锁,成功就获取,失败就不进入阻塞队列了
            boolean b = false;
            try {
                //尝试获取锁,1秒钟时候获取不到就直接失败return,避免一直等待
                //tryLock也可以使用interrupt打断,抛异常
                b = lock.tryLock(2, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("获取不到锁");
                return;
            }
            if(b == false){
                log.debug("获取不到锁");
                return;
            }

            try {
                log.debug("获取到了锁");
            }finally {
                lock.unlock();
            }
        }, "t1");
        
        //DEBUG [main] (00:22:09,185) (Test24.java:48) - 主线程获取到了锁
        //DEBUG [t1] (00:22:11,196) (Test24.java:35) - 获取不到锁

        lock.lock();
        log.debug("主线程获取到了锁");
        t1.start();
        sleep.mySleep(4);
        lock.unlock();
    }

}



4. 公平锁

ReentrantLock 默认是不公平的, 线程不用按顺序去抢锁,而是每个线程都可以去抢,都有机会拿到锁,公平锁一般没有必要,会降低并发度。

public class UnEqualLock {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock(false);
        lock.lock();
        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "t" + i).start();
        }
        // 1s 之后去争抢锁
        Thread.sleep(1000);
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " start...");
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " running...");
            } finally {
                lock.unlock();
            }
        }, "强行插入").start();
        lock.unlock();
    }

    //t0 running...
    //强行插入 start...
    //t1 running...
    //t2 running...
    //t3 running...
    //t6 running...
    //t4 running...
    //t5 running...
}



5. 条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待,等到田间满足之后再进入队列中去竞争锁的使用

ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,也就是说可以根据不同的变量来给不同的线程提供等待环境

  • synchronized 是那些不满足条件的线程都在一间休息室等消息
  • 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒,好处就是更加容易控制线程的执行

使用要点:

  • await 前需要获得锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行
@Slf4j
public class Test25 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    static ReentrantLock ROOM = new ReentrantLock();
    // 等待烟的休息室
    static Condition waitCigaretteSet = ROOM.newCondition();
    // 等外卖的休息室
    static Condition waitTakeoutSet = ROOM.newCondition();

    public static void main(String[] args) {


        new Thread(() -> {
            ROOM.lock();
            try {
                log.debug("有烟没?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            } finally {
                ROOM.unlock();
            }
        }, "小南").start();

        new Thread(() -> {
            ROOM.lock();
            try {
                log.debug("外卖送到没?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("没外卖,先歇会!");
                    try {
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            } finally {
                ROOM.unlock();
            }
        }, "小女").start();

        sleep.mySleep(1);

        //送外卖的
        new Thread(() -> {
            ROOM.lock();
            try {
                hasTakeout = true;
                log.debug("外卖送到了");
                waitTakeoutSet.signal();
            } finally {
                ROOM.unlock();
            }
        }, "送外卖的").start();

        sleep.mySleep(1);

        //送烟的
        new Thread(() -> {
            ROOM.lock();
            try {
                hasCigarette = true;
                log.debug("烟送到了");
                waitCigaretteSet.signal();
            } finally {
                ROOM.unlock();
            }
        }, "送烟的").start();
    }

}

结果:

DEBUG [小南] (00:44:42,937) (Test25.java:33) - 有烟没?[false]
DEBUG [小南] (00:44:42,939) (Test25.java:35) - 没烟,先歇会!
DEBUG [小女] (00:44:42,939) (Test25.java:51) - 外卖送到没?[false]
DEBUG [小女] (00:44:42,939) (Test25.java:53) - 没外卖,先歇会!
DEBUG [送外卖的] (00:44:43,947) (Test25.java:73) - 外卖送到了
DEBUG [小女] (00:44:43,947) (Test25.java:60) - 可以开始干活了
DEBUG [送烟的] (00:44:44,955) (Test25.java:87) - 烟送到了
DEBUG [小南] (00:44:44,956) (Test25.java:42) - 可以开始干活了

Process finished with exit code 0



2. 同步模式之控制

1. 固定运行顺序

1. 使用 wait - notify 达到线程间通信的效果

下面的代码使用 t2 来唤醒 t1

@Slf4j
public class Test26 {

    static final ReentrantLock lock = new ReentrantLock();
    //表示t2是否运行过了
    static boolean run2 = false;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock){
                //t2没有运行t1就等待
                while(run2 != true){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("1");
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (lock){
                log.debug("2");
                run2 = true;
                lock.notifyAll();
            }

        }, "t2");

        t1.start();
        t2.start();
    }
}

结果:

DEBUG [t2] (00:49:04,144) (Test26.java:38) - 2
DEBUG [t1] (00:49:04,146) (Test26.java:32) - 1



2. park-unpark:线程 t2 唤醒线程 t1

@Slf4j
public class Test27 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
            log.debug("1");
        }, "t1");
        t1.start();
        new Thread(()->{
            log.debug("2");
            LockSupport.unpark(t1);
        }, "t2").start();
    }
}

结果:

DEBUG [t2] (00:52:05,548) (Test27.java:24) - 2
DEBUG [t1] (00:52:05,550) (Test27.java:20) - 1



2. 交替输出

1. wait-notify

其实就是设置当前的标志位和下一个标志位,当前线程执行完成之后就设置下一个标志位为true

@Slf4j
public class Test28 {
    public static void main(String[] args) {
        waitNotify wn = new waitNotify(1, 5);
        new Thread(()->{
            wn.print("a", 1, 2);
        }, "t1").start();
        new Thread(()->{
            wn.print("b", 2, 3);
        }, "t2").start();
        new Thread(()->{
            wn.print("c", 3, 1);
        }, "t3").start();
    }
}

class waitNotify{
    //打印
    public void print(String str, int waitFlag, int nextFlag){
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this){
                while(flag != waitFlag){
                    //等待
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //输出当前的
                System.out.print(str);
                //设置下一个
                flag = nextFlag;
                //唤醒所有线程,其实只有下一个能执行
                this.notifyAll();
            }
        }
    }

    //标记
    private int flag;
    //循环次数
    private int loopNumber;

    public waitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }

    public waitNotify(int loopNumber) {
        this.loopNumber = loopNumber;
    }
}

输出结果:

abcabcabcabcabc



2. await-signal

使用方法:将三个线程装进不同的等待室,首先主线程唤醒 a,然后a 执行完成后唤醒 b 的等待室…

public class Test30 {
    public static void main(String[] args) throws InterruptedException {
        AwaitSingle awaitSingle = new AwaitSingle(5);
        Condition a = awaitSingle.newCondition();
        Condition b = awaitSingle.newCondition();
        Condition c = awaitSingle.newCondition();
        //其他线程一开始全部await
        new Thread(()->{
            awaitSingle.print("a", a, b);
        }, "t1").start();
        new Thread(()->{
            awaitSingle.print("b", b, c);
        }, "t2").start();
        new Thread(()->{
            awaitSingle.print("c", c, a);
        }, "t3").start();
        //主线程先唤醒a
        Thread.sleep(1000);
        awaitSingle.lock();
        try{
            //主线程开始
            System.out.println("开始...");
            // 先唤醒 a
            a.signal();
        }finally {
            awaitSingle.unlock();
        }
    }
}

class AwaitSingle extends ReentrantLock {
    private int loopNumber;

    public AwaitSingle(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    //参数1:打印的内容         参数2:进入哪一间休息室等待
    public void print(String str, Condition current, Condition next){
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                current.await();
                //打印当前休息室的
                System.out.print(str);
                //唤醒下一间休息室
                next.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                unlock();
            }
        }
    }
}


执行结果:

开始...
abcabcabcabcabc



3. park-unpark

全部线程使用 park 暂停,主线程唤醒 t1,t1执行完成之后唤醒 t2,一直循环下去

public class Test31 {
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) {
        ParkUnpark pk = new ParkUnpark(5);
        t1 = new Thread(()->{
            pk.print("a", t2);
        }, "t1");
        t2 = new Thread(()->{
            pk.print("b", t3);
        }, "t2");
        t3 = new Thread(()->{
            pk.print("c", t1);
        }, "t3");
        t1.start();
        t2.start();
        t3.start();

        //主线程发起
        LockSupport.unpark(t1);
    }
}

class ParkUnpark{
    private int loopNumber;

    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    //park直接对当前线程停止,只需要知道下一个线程是什么就行了
    public void print(String str, Thread next){
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            System.out.print(str);
            LockSupport.unpark(next);
        }
    }
}

结果输出:

abcabcabcabcabc






如有错误,欢迎指出!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值