线程交替执行经典问题(生产者和消费者问题)

让两个线程交替打印出1A2B3C4D5E6F?

一、使用LockSupport

如果只有两个线程,使用LockSupport实现比较简单快捷,但是当有多个线程的时候,LockSupport.unpark(指定线程),我们不知道那个指定线程需要唤醒。所以不适用多个线程的情况。

package com.itheima.security.springboot.juc;

import java.util.concurrent.locks.LockSupport;

/**
 * @Title: 模拟线程交替执行经典问题
 * @Description: TODO
 * @Params:
 * @return
 * @throws
 * @Author: Vector
 * @DateTime:
 */
public class T04_LockSupport {

    static Thread t1, t2;

    public static void main(String[] args) {
        char[] arrayA = "1234567".toCharArray();
        char[] arrayB = "ABCDEFG".toCharArray();

        t1 = new Thread(() -> {
            for (char temp : arrayA) {
                System.out.println(temp);
                LockSupport.unpark(t2); //唤醒t2线程
                LockSupport.park();//t1线程阻塞 todo 即让当前线程进入阻塞状态
            }

        }, "t1");

        t2 = new Thread(() -> {
            for (char temp : arrayB) {
                LockSupport.park();//t2线程阻塞 todo 即让当前线程进入阻塞状态
                System.out.println(temp);
                LockSupport.unpark(t1); //唤醒t1线程

            }

        }, "t2");

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

二、使用synchronized的wait 和notify

前提条件:只有已经获取锁的线程,才可以调用锁的wait()、notify()方法,否则会抛出异常IllegalMonitorStateException。

1.wait()的作用是让当前线程进入等待(阻塞)状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。

线程执行 Object.wait()方法时,会将当前线程加入到 _waitSet 这个双向链表中,然后再运行ObjectMonitor::exit() 方法来释放锁

wait()的正确使用姿势
while(条件不满足) {
 wait();
}

当某个线程获取到锁后,发现当前还不满足执行的条件,就可以调用对象锁的wait方法,进入等待状态。直到某个时刻,外在条件满足了,就可以由其他线程通过调用notify()或者notifyAll()方法,来唤醒此线程。

2.notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

当线程执行 Object.notify():方法时,从 _waitSet 头部拿线程节点,然后根据策略(QMode指定)决定将线程节点放在哪里,包括_cxq 或 _EntryList 的头部或者尾部,然后唤醒队列中的线程。

notify()和notifyAll()分别何时使用

  满足以下三个条件时,可以使用notify(),其余情况尽量使用notifyAll():

    1. 所有等待线程拥有相同的等待条件;
    2. 所有等待线程被唤醒后,执行相同的操作;
    3. 只需要唤醒一个线程。

  要知道notify()是随机唤醒一个,而notifyAll()则是唤醒全部。如果是要唤醒特定的线程,最好用notifyAll() + while(条件不满足)来保证指定线程会被唤醒。

3.wait(long timeout)让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。在指定时间内,如果没有其他线程唤醒自己,则主动唤醒自己。

package com.itheima.security.springboot.juc;

import java.util.concurrent.CountDownLatch;

/**
 * @Title: 模拟线程交替执行经典问题, 让两个线程交替打印出1A2B3C4D5E6F
 * @Description: TODO
 * @Params:
 * @return
 * @throws
 * @Author: Vector
 * @DateTime:
 */
public class T05_SyhcWaitNotify {

    static Thread t1, t2;
    private static Object o = new Object();
    private static CountDownLatch latch = new CountDownLatch(1);

    public static void main(String[] args) {
        char[] arrayA = "1234567".toCharArray();
        char[] arrayB = "ABCDEFG".toCharArray();

        t1 = new Thread(() -> {
            synchronized (o) {
                for (char temp : arrayA) {
                    System.out.println(temp);
                    latch.countDown();
                    try {
                        o.notify(); //唤醒唤醒单个线程
                        o.wait();//todo 让当前线程处于“等待(阻塞)状态,也会让当前线程释放它所持有的锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                o.notify();

            }

        }, "t1");

        t2 = new Thread(() -> {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o) {
                for (char temp : arrayB) {
                    System.out.println(temp);
                    try {
                        o.notify(); //唤醒唤醒单个线程
                        o.wait();//todo 让当前线程处于“等待(阻塞)状态,也会让当前线程释放它所持有的锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                o.notify();
            }

        }, "t2");

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

三、使用ReentrantLock的wait 和notify

package com.itheima.security.springboot.juc;

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

/**
 * @Title: 模拟线程交替执行经典问题, 让两个线程交替打印出1A2B3C4D5E6F
 * @Description: TODO
 * @Params:
 * @return
 * @throws
 * @Author: Vector
 * @DateTime:
 */
public class T05_ReentrantLockWaitSignal {

    public static void main(String[] args) {
        char[] arrayA = "1234567".toCharArray();
        char[] arrayB = "ABCDEFG".toCharArray();

        ReentrantLock lock = new ReentrantLock();
        //可以把Condition理解为队列
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();

        CountDownLatch latch = new CountDownLatch(1);

        new Thread(() -> {
            lock.lock();

            try {
                for (char temp : arrayA) {
                    System.out.println(temp);
                    //释放门闩
                    latch.countDown();

                    condition2.signal();
                    condition1.await();
                }
                condition2.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }, "t1").start();


        new Thread(() -> {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            lock.lock();

            try {
                for (char temp : arrayB) {

                    System.out.println(temp);
                    condition1.signal();
                    condition2.await();

                }
                condition1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }, "t2").start();

    }

}

四、巧妙使用TransferQueue

package com.itheima.security.springboot.juc;

import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;

/**
 * @Title: 模拟线程交替执行经典问题, 让两个线程交替打印出1A2B3C4D5E6F
 * @Description: TODO
 * @Params:
 * @return
 * @throws
 * @Author: Vector
 * @DateTime:
 */
public class T05_TransferQueue {

    public static void main(String[] args) {
        char[] arrayA = "1234567".toCharArray();
        char[] arrayB = "ABCDEFG".toCharArray();

        TransferQueue<Character> queue = new LinkedTransferQueue<>();

        new Thread(() -> {
            try {
                for (char temp : arrayA) {
                    queue.transfer(temp);
                    System.out.println(queue.take());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "t1").start();

        new Thread(() -> {
            try {
                for (char temp : arrayB) {
                    System.out.println(queue.take());
                    queue.transfer(temp);

                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "t2").start();


    }

}