写点什么

多线程交替输出

用户头像
愚者
关注
发布于: 2021 年 07 月 28 日

1. 概括

  • 多线程输出循环输出,是并发编程面试常问的问题之一,解决这类问题主要可以分成两种方案

  • ReentrantLock 方案

    注意点:一个线程一个 condition ,不要乱

    注意点: condition 的方法是 await()和 singal() 不是 wait()

  • syn 方案

    多线程的时候 notifyAll() 和 和 notify() 要注意区分

  • 需要有一个 who 的变量,标定当前是轮到谁执行了

  • 必须先进行阻塞, 然后输出, 最后唤醒,顺序不能变!!!

    如果是先输出,在阻塞,最后唤醒会导致第一次打印时候可能错误(具体看下面)

    reentrantLock 下, 阻塞判断使用 if 或者 while 都可以

    在 syn 下 3 线程的阻塞判断 阻塞只能使用 while(双线程可以用 if)这是由于 reentrantLock 可以让 唤醒指定的线程,而 syn 在三线程情况下只能notifyAll,进行唤醒,这时候需要将其中一个线程进行再次阻塞

2. 两个线程循环打印 AB

syn 法

​​

public class AB_syn {    static int count = 0;
public static void main(String[] args) { Object lock = new Object(); new Thread(() -> { // 先进入到锁里面,才能进行控制 synchronized (lock) { // 死循环,进行循环打印 while (true) { // 如果count 不等于 0, 就说明是另一个线程需要打印,自身需要wait(); while (count != 0) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("A"); // 唤醒线程2 count = 1; // 这时候唤醒了其他在的线程,会继续执行到 while(count!=0)里面去阻塞 lock.notify(); } } }).start(); new Thread(() -> { // 先都丢到锁里面,才能进行控制 synchronized (lock) { // 死循环,进行循环打印 while (true) { while (count != 1) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("B"); count = 0; lock.notify(); } } }).start(); }}复制代码
复制代码


reentrantlock


import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;
public class AB_reentrylock { static int who = 0;
public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Condition conditionA = lock.newCondition(); Condition conditionB = lock.newCondition();

new Thread(() -> { try { // 先进入锁里面,才能进行控制 lock.lock(); while (true) { while (who != 0) { conditionA.await(); } System.out.println("A"); // 先唤醒B, 让B 到 AQS中排队 conditionB.signal(); who = 1; } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }).start();
new Thread(() -> { try { // 先进入锁里面,才能进行控制 lock.lock(); while (true) { while (who != 1) { conditionB.await(); } System.out.println("B"); // 先唤醒A, 让A 到 AQS中排队 conditionA.signal(); who = 0; } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }).start(); }}复制代码
复制代码


3. 三线程打印 ABC

syn 法

​​

public class ABC_syn {    static int who = 0;
public static void main(String[] args) { Object lock = new Object(); new Thread(() -> { synchronized (lock) { while (true) { while (who != 0) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("A"); who = 1; lock.notifyAll(); } } }).start();
new Thread(() -> { synchronized (lock) { while (true) { while (who != 1) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("B"); who = 2; lock.notifyAll(); } } }).start();
new Thread(() -> { synchronized (lock) { while (true) { while (who != 2) { try { lock.wait();
} catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("C"); who = 0; lock.notifyAll(); } } }).start(); }}复制代码
复制代码


reentrantlock

​​

import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;
public class ABC_reentrylock { static int count = 0; public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); lock.notifyAll(); Condition conditionA = lock.newCondition(); Condition conditionB = lock.newCondition(); Condition conditionC = lock.newCondition(); new Thread(() -> { try { // 加锁,进入并发控制内部 lock.lock(); while (true) { while (count != 0) { conditionA.await(); } System.out.println("A"); // 将阻塞在 conditionB 的线程唤醒 conditionB.signal(); count = 1; } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }).start();

new Thread(() -> { try { // 加锁,进入并发控制内部 lock.lock(); while (true) { while (count != 1) { conditionB.await(); } System.out.println("B"); // 将阻塞在 conditionB 的线程唤醒 conditionC.signal(); count = 2; } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); }
}).start();
new Thread(() -> { try { // 加锁,进入并发控制内部 lock.lock(); while (true) { while (count != 2) { conditionC.await(); } System.out.println("C"); // 将阻塞在 conditionB 的线程唤醒 conditionA.signal(); count = 0; } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }).start(); }}复制代码
复制代码


4. 为什么需要先进行判断阻塞,在打印?


这种情况下,如果线程 2 先开启执行,就会导致第一次输出的时候 B 先进行输出,三个线程的时候,同样无法保证第一个输出的是 A

​​

public class AB_syn2 {    static int count = 0;
public static void main(String[] args) { Object lock = new Object(); new Thread(() -> { // 先都丢到锁里面,才能进行控制 synchronized (lock) { // 死循环,进行循环打印 while (true) { System.out.println("A"); // 唤醒线程2 count = 1; lock.notify(); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(() -> { // 先都丢到锁里面,才能进行控制 synchronized (lock) { // 死循环,进行循环打印 while (true) { System.out.println("B"); count = 0; lock.notify(); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); }}复制代码
复制代码


5. 写一个死锁


  • 死锁的四个要素:

  • 互斥访问

  • 请求和保持

  • 不剥夺

  • 循环等待


  • 简单来说就是,两个锁(AB),一个线程拿了 A,一个线程拿了 B, 互相请求对方的资源


​​

public class 死锁 {    public static void main(String[] args) {        Object A = new Object();        Object B = new Object();        new Thread(() -> {            synchronized (A) {                System.out.println("获得了A");                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println("100毫秒后尝试获取B");                synchronized (B){                    System.out.println("bbbb");                }            }        }).start();
new Thread(() -> { synchronized (B) { System.out.println("获得了B"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("100毫秒后尝试获取A"); synchronized (A){ System.out.println("aaaa"); } } }).start();
}}
复制代码


用户头像

愚者

关注

还未添加个人签名 2021.07.22 加入

还未添加个人简介

评论

发布
暂无评论
多线程交替输出