实现两个线程交替运行(二)

接上文:实现两个线程交替运行(一)

 

4,Semaphore信号量

详情见随笔;

 https://www.cnblogs.com/CccccNun/p/15798454.html

 

5,volatile+非线程安全引用变量

笑不活了家人们,今天我发现其实不使用很复杂的锁相关内容,其实也能实现两个线程的交替运行,之前仿佛陷入了一个大大的思维定式。

 

使用List。此处一定要使用 volatile,否则A线程可能感应不到B线程的remove(0);,从而A线程一直处于while{};而此时B线程也处于while{};

程序一直运行,进入假死状态。

 

public class ThreadCommunicate_4 {
    
    public static void main(String[] args) {

        List<String> arrayList = new ArrayList<>();

        Thread A_Th = new A_Th_4(arrayList);
        Thread B_Th = new B_Th_4(arrayList);

        A_Th.start();
        B_Th.start();
    }
}

/**
 * 奇数线程
 */
class A_Th_4 extends Thread{

    //此处一定要volatile
    private volatile List<String> arrayList;

    public A_Th_4(List<String> arrayList) {
        this.arrayList = arrayList;
    }

    @Override
    public void run() {
        for(int i = 1; i < 100;){
            try {
                while (arrayList.size() == 1){

                 }
                System.out.println("线程A:==>" + i);
                i = i + 2;
                arrayList.add(i + "");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 偶数线程
 */
class B_Th_4 extends Thread{

    //此处一定要volatile
    private volatile List<String> arrayList;

    public B_Th_4(List<String> arrayList) {
        this.arrayList = arrayList;
    }

    @Override
    public void run() {
        for(int i = 2; i <= 100;){
            try {
                while (arrayList.isEmpty()){
 
                }
                System.out.println("线程B:==>" + i);
                i = i + 2;
                arrayList.remove(0);

                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

假死状态如图:

 

但是某个瞬间我又疑惑了,既然可以不使用锁相关(包含synchorined以及Lock),那么完全可以把使用的锁都去掉呀。

本着事事躬行的态度,我将之前的锁去掉之后运行,发现了程序会进入上图显示假死状态。

突然间我悟了,使用锁就是为了保证线程间共享变量之间的可见性(当然其实不仅仅保证了可见性),所以一般使用锁的时候不需要使用volatile。

 

6,使用阻塞队列BlockingQueue

思路类似于Semaphore,实现如下:

 

/**
 * @program:
 * @description:
 *
 *   阻塞队列 BlockingQueue
 */
public class ThreadCommunicate_5 {

    public static void main(String[] args) {

        BlockingQueue<String> queue = new LinkedBlockingQueue<String>(1);
        BlockingQueue<String> queue1 = new LinkedBlockingQueue<String>(1);

        new A_Th_5(queue, queue1).start();
        new B_Th_5(queue, queue1).start();
    }
}

/**
 * 奇数线程
 */
class A_Th_5 extends Thread {

    private final BlockingQueue<String> queue;

    private final BlockingQueue<String> queue2;

    public A_Th_5(BlockingQueue<String> queue, BlockingQueue<String> queue2) {
        this.queue = queue;
        this.queue2 = queue2;
    }

    @Override
    public void run() {

        for(int i = 1; i < 100; ){
            try {
                queue.put(i+"");
                System.out.println("线程A:==>" + i);
                i = i + 2;
                TimeUnit.SECONDS.sleep(1);

                queue2.put(i+"");

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

/**
 * 偶数线程
 */
class B_Th_5 extends Thread {

    private final BlockingQueue<String> queue;

    private final BlockingQueue<String> queue2;

    public B_Th_5(BlockingQueue<String> queue, BlockingQueue<String> queue2) {
        this.queue = queue;
        this.queue2 = queue2;
    }

    @Override
    public void run() {

        for(int i = 2; i <= 100; ){
            try {
                queue2.take();
                System.out.println("线程B:==>" + i);
                i = i + 2;
                TimeUnit.SECONDS.sleep(1);

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

 

   此代码的思路和Semaphore有点类似,利用长度为1的阻塞队列 put(),take()的单向执行顺序;否则会阻塞。

具体运行流程:

* A B线程肯定是 A线程先执行,B线程由于 queue2 是空的,take()阻塞。
*
* A 线程新增 queue1后,执行打印等逻辑后休眠;此时 B线程仍然阻塞;
* A 线程休眠完成后,queue2 队列新增;A 进入下一个循环 新增 queue1 阻塞(queue1 上个循环已经新增了);
* B 线程只有在 A线程运行到最后一步新增完queue2 之后,queue2.take();才能成功,后执行打印等具体逻辑,然后进入休眠;
* 此时 A线程仍旧阻塞,因为 queue1 此时长度仍为 1;
* B 线程休眠完成后,执行queue1.take();B 进入下一个循环,queue2.take();阻塞(queue2 上个循环已经take了);
* 此时A 线程 queue1.put();成功(由于B 线程结束时执行queue1.take();),执行打印等逻辑后休眠。。。
* 以此类推。

 

7,使用LockSupport.park();unpark();

首先了解LockSupport的基本使用:LockSupport相当于一个工具类,其构造方法为私有,所有方法都为static

  主要方法为LockSupport.park();,LockSupport.unpark(Thread thread);

  其实有点类似于信号量Semaphore,但是也有非常明显的区别。

  1,LockSupprot.park();会阻塞线程【类似于new Semaphore().acquire();的效果】,而unpark();并不会阻塞线程【类似于new Semaphore().release();的效果】;

  2,连续多次unpark();的效果与一次unpark();的效果一致,只会释放一个资源,

  【这是与Semaphore明显的区别,信号量release();N次会释放N个资源,随后也可以acquire(); (N+permits) 个资源】。

  3,LockSupport一定需要在unpark(Thread xx);之后才能成功park(); ,直接调用park();会阻塞当前线程。

  4,连续多次调用park();的情况,最多只有 第一个park();成功,其余都会阻塞。

  5,只有在 T线程 线程启动之后,unpark(Thread t);才会生效,否则效果未知。

 

代码实现如下:

   思路类似于Semaphore;

  个人觉得没必要使用此方法,此处仅提供一种思路。

/**
 * @program:
 * @description:
 *
 * LockSupport  park ; unpark
 *
 */
public class ThreadCommunicate_6 {

    public static void main(String[] args) {
        A_Th_6 a_Th = new A_Th_6();
        B_Th_6 b_Th = new B_Th_6();

        a_Th.setB_Th(b_Th);
        b_Th.setA_Th(a_Th);

        // 注意,此处开启并没有效果,一定要在 A线程 开启之后unpark
        // 只需要 A线程开启之后,不一定在所有线程开启之后。
        //LockSupport.unpark(a_Th);
        a_Th.start();
        b_Th.start();
        // 开启
        LockSupport.unpark(a_Th);
    }
    
}

class A_Th_6 extends Thread {

    private Thread b_Th;

    public void setB_Th(Thread b_Th) {
        this.b_Th = b_Th;
    }

    @Override
    public void run() {
        for (int i = 1; i < 100; ){
            try {
                LockSupport.park();
                System.out.println("线程A:==>" + i);
                i = i + 2;
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                LockSupport.unpark(b_Th);
            }
        }
    }
}

class B_Th_6 extends Thread {

    private Thread a_Th;

    public void setA_Th(Thread a_Th) {
        this.a_Th = a_Th;
    }

    @Override
    public void run() {
        for (int i = 2; i <= 100; ){
            try {
                LockSupport.park();
                System.out.println("线程B:==>" + i);
                i = i + 2;
                TimeUnit.SECONDS.sleep(1);

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                LockSupport.unpark(a_Th);
            }
        }
    }
}

 

注意:

此处main();的 A线程 unpark();的时机很重要,一定要在 A线程 开启之后,否则会无效。

 

posted @ 2022-01-24 14:12  豆浆不要糖  阅读(307)  评论(0)    收藏  举报