多线程顺序执行多次,分别使用synchronized、ReentrantLock、CyclicBarrier、Semaphore实现

使用三个线程,线程一打印A、线程二打印B、线程三打印C,让三个线程顺序打印多次,其打印结果为:

A
B
C
A
B
C
A
B
C
...

这种形式的。

         这个题也是面试笔试比较喜欢考的题,如果你在笔试的时候,能够都写出来,那么就稳了,不行话记住两个常用的也行啊。

分为两种:

        1. 只执行一次     2. 执行多次

多线程编程时,需要特别注意以下几点:

多线程想要能顺序执行,必须要进行线程之间的通信,通信可以使用对于共享资源的抢占和修改。
线程会存在并行执行的情况,如果不干预肯定是不行的,所以,需要借助一些手段,才能达到线程顺序执行的目的。
最主要的是借助共享资源(共享变量)来实现。
像ReentrantLock、CyclicBarrier、CountDown都是通过对共享变量进行操作的。
在操作时,每个线程都需要清楚,是应该继续执行,还是说睡眠(等待),等待被唤醒,
清楚等待的条件和唤醒后继节点的条件,这几点清楚就没有问题了。

1.顺序执行多次

         只执行一次的比较简单,可以使用synchronized、ReentrantLock、CyclicBarrier、Semaphore、CountDown来实现,而对于顺序执行多次的话,CountDown是不满足条件的,因为CountDown是一次性的,使用之后,就能不再重复使用了。当然对于顺序执行也有其他的写法,如线程池提交,使用Future.get()方法去阻塞等待完成,那么本文不再介绍。

1.synchronized方法

/**
 * 注意点:
 *    多个线程共用一把锁,才能完成同步
 *    需要额外的共享变量,因为你执行完成后,发出的通知是通知所有等待的线程,所以你没法准确
 *    指出通知的谁,所有等待的线程都醒来后,再进行判断
 *    需要使用while(!judge(name, num))进行判断,理由同上,发出的通知是通知所有等待的线程,所以你没法准确
 *    指出通知的谁,所有等待的线程都醒来后,再进行判断
 * @author wangjie41
 * @version 2020/11/16 14:49
 */
public class SynchronizedTest {
    /**
     * 额外的共享变量,不能少
     */
    static volatile int num = 1;

    static class Task implements Runnable{
        String name;
        Object lock;

        public Task(String name, Object lock) {
            this.name = name;
            this.lock = lock;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                printNum();
            }
        }

        private void printNum() {
            synchronized (lock){
                /*
                    注意时while循坏,因为你唤醒的是所有的线程,如果不想用while循环,那么就使用多个锁,
                    形成一套通知唤醒机制
                 */
                while(!judge(name, num)){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                    }
                }
                num++;
                System.out.println(name);
                lock.notifyAll();
            }
        }

        /*
            判断当前执行的一个状态,也就是执行到哪了,会判断该谁打印了
         */
        private boolean judge(String name, int num) {
            if("A".equals(name) && num%3 == 1){
                return true;
            }else if("B".equals(name) && num%3 == 2){
                return true;
            }else if("C".equals(name) && num%3 == 0){
                return true;
            }else {
                return false;
            }

        }
    }

    public static void main(String[] args) {
        Object lock = new Object();

        new Thread(new Task("A", lock)).start();
        new Thread(new Task("B", lock)).start();
        new Thread(new Task("C", lock)).start();
    }
}
/*
    执行结果:
    A
    B
    C
    A
    B
    C
    A
    B
    C
    ...
 */

 2.使用ReentrantLock实现

/**
 * 注意点:
 *    Condition需要在Lock.lock()和lock.unlock()之间使用
 * @author wangjie41
 * @version 2020/11/16 13:02
 */
public class ReentrantLockTest {
    static volatile int num = 1;

    static class Task implements Runnable {
        /*
            不该自己输入则等待
            轮到自己输出,之后,唤醒下个打印的线程
         */
        String name;

        Lock lock;
        Condition awaitCondition;
        Condition notifyCondition;

        public Task(String name, Condition awaitCondition, Condition notifyCondition, Lock lock) {
            this.name = name;
            this.awaitCondition = awaitCondition;
            this.notifyCondition = notifyCondition;
            this.lock = lock;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                lock.lock();
                try {
                    /*
                        使用while是防止第一次时,由于C先执行完成的问题
                     */
                    while (!gudje(name, num)) {
                        awaitCondition.await();
                    }
                    num++;
                    System.out.println(name);
                    notifyCondition.signal();
                } catch (Exception e) {

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

        private boolean gudje(String name, int num) {
            if("A".equals(name) && num%3 == 1){
                return true;
            }else if("B".equals(name) && num%3 == 2){
                return true;
            }else if("C".equals(name) && num%3 == 0){
                return true;
            }else {
                return false;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();

        /*
            其相互等待的顺序不能变,如A会等待C的执行完毕(不是第一次时)
            使用线程睡眠是验证在任何时刻都会按照这种方式输出
         */
        new Thread(new Task("C", condition2, condition3, lock)).start();
        Thread.sleep(50);
        new Thread(new Task("B", condition1, condition2, lock)).start();
        Thread.sleep(50);
        new Thread(new Task("A", condition3, condition1, lock)).start();
    }
}
/*
    执行结果:
    A
    B
    C
    A
    B
    C
    A
    B
    C
    ...
 */

3.使用CyclicBarrier实现

/**
 * 注意:
 *    如果仅仅依次打印ABCABC。。。, 也是不需要共享变量的,只是这里有改动,
 *    打印内容如下:
 *    A  1
 *    B  2
 *    C  3
 *    A  4
 *    B  5
 *    C  6
 *    A  7
 *    B  8
 *    C  9
 *    线程A打印1、4、7,也是顺序打印,
 *
 *    注意new CyclicBarrier(2);使用的变量是2,
 *    CyclicBarrier就是让一组线程相互等待,等待所有线程都到达后,才全部执行,是可以重复使用的,
 *    思想:
 *       先指定当前线程的前驱等待和后继唤醒, 每个线程执行前,先获取调用await()进行等待,此时等待的线程需要是2,才能都执行,
 *       那么线程执行完成后,需要唤醒他的后继节后,也是调用await()进行等待,此时,下一个线程再执行await()进行等待时,发现两个
 *       线程在等待了,那么就可以继续执行了。
 *
 * @author wangjie41
 * @version 2020/11/16 11:22
 */
public class CyclicBarrierTest {
    /**
     * 如果执行依次打印ABCABCABC.... ,这个共享变量可以不要
     */
    private static volatile int num = 1;

    static class Task implements Runnable{
        /*
            用来判断开始执行的线程是哪一个
         */
        private boolean first = true;

        private String name;

        /*
            awaitBarrier,如不该自己执行,那么就等待
            notifyBarrier,执行完成后,通知下一节点
         */
        private CyclicBarrier awaitBarrier;
        private CyclicBarrier notifyBarrier;

        public Task(String name, CyclicBarrier awaitBarrier, CyclicBarrier notifyBarrier) {
            this.name = name;
            this.awaitBarrier = awaitBarrier;
            this.notifyBarrier = notifyBarrier;
        }

        @Override
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    if("A".equals(name) && first){
                        first = false;
                    }else {
                        awaitBarrier.await();
                    }
                    System.out.println(name + "  " + num++);
                    notifyBarrier.await();
                }
            }catch (Exception e){

            }
        }
    }

    public static void main(String[] args) {
        CyclicBarrier barrier1 = new CyclicBarrier(2);
        CyclicBarrier barrier2 = new CyclicBarrier(2);
        CyclicBarrier barrier3 = new CyclicBarrier(2);

        /*
            其相互等待的顺序不能变,如A会等待C的执行完毕(不是第一次时)
         */
        new Thread(new Task("A", barrier3, barrier1)).start();
        new Thread(new Task("B", barrier1, barrier2)).start();
        new Thread(new Task("C", barrier2, barrier3)).start();
    }
}

/*
    执行结果:
    A  1
    B  2
    C  3
    A  4
    B  5
    C  6
    A  7
    B  8
    C  9
    ...
 */

4.使用Semaphore实现

/**
 * 注意的点:
 *   new Semaphore(0); 创建的初始信号量为0,这样调用acquire方法就会被阻塞,因为acquire方法在有共享资源时,
 *   会立即返回的
 *   first属性标识是否为第一次执行,因为先让A先执行,那么就有了"A".equals(name) && first的判断,因为
 *   A第一次执行的话,可以直接获取到数据
 *   每个线程在执行前都会判断当前是否该执行,不该执行,就等待,执行之后,再唤醒其他的线程
 *   使用Semaphore进行多线程控制时,不需要额外的共享变量了,因为其通知机制的原因
 *
 * @author wangjie41
 * @version 2020/11/16 16:54
 */
public class SemaphoreTest {
    static class Task implements Runnable{
        /*
            用来判断开始执行的线程是哪一个
         */
        boolean first = true;
        String name;

        /*
            awaitSemaphore等待,如不该自己执行,那么就等待
            notifySemaphore通知,执行完成后,通知下一节点
         */
        Semaphore awaitSemaphore;
        Semaphore notifySemaphore;

        public Task(String name, Semaphore awaitSemaphore, Semaphore notifySemaphore) {
            this.name = name;
            this.awaitSemaphore = awaitSemaphore;
            this.notifySemaphore = notifySemaphore;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                if("A".equals(name) && first){
                    first = false;
                }else {
                    try {
                        awaitSemaphore.acquire();
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println(name);
                notifySemaphore.release();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore1 = new Semaphore(0);
        Semaphore semaphore2 = new Semaphore(0);
        Semaphore semaphore3 = new Semaphore(0);

        new Thread(new Task("C", semaphore2, semaphore3)).start();
        Thread.sleep(50);
        new Thread(new Task("B", semaphore1, semaphore2)).start();
        Thread.sleep(50);
        new Thread(new Task("A", semaphore3, semaphore1)).start();

    }
}
/*
    执行结果:
    A
    B
    C
    A
    B
    C
    A
    B
    C
    ...
 */

2.顺序执行一次

      顺序执行可以使用上面的实现,对此就不再叙述,主要看一下CountDown的实现。

1.使用 CountDown实现

/**
 * 注意:
 *    只适用与顺序执行,没有循坏执行的情况
 * @author wangjie41
 * @version 2020/11/16 13:47
 */
public class CountDownTest {

    static class Task implements Runnable{
        String name;
        /*
            awaitLatch,如不该自己执行,那么就等待
            notifyLatch,执行完成后,通知下一节点
         */
        CountDownLatch awaitLatch;
        CountDownLatch notifyLatch;

        public Task(String name, CountDownLatch awaitLatch, CountDownLatch notifyLatch) {
            this.name = name;
            this.awaitLatch = awaitLatch;
            this.notifyLatch = notifyLatch;
        }

        @Override
        public void run() {
            if(awaitLatch != null){
                try {
                    awaitLatch.await();
                } catch (InterruptedException e) {

                }
            }
            System.out.println(name);
            if(notifyLatch != null){
                notifyLatch.countDown();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        CountDownLatch latch2 = new CountDownLatch(1);

        /*
            new Task("C", latch2, null),一个线程的上游和下游一定要查看好
         */
        new Thread(new Task("A", null, latch)).start();
        new Thread(new Task("B", latch, latch2)).start();
        new Thread(new Task("C", latch2, null)).start();
    }
}

 

两个线程交替打印1A2B3C.......,该如何设计?

题解:

        使用ReentrantLock进行求解,需要两个等待列队,一个用于等待,一个用于唤醒。

public class LC {
    static class PrintNumber implements Runnable{
        private ReentrantLock lock;

        private Condition await;

        private Condition notify;

        public PrintNumber(ReentrantLock lock, Condition await, Condition notify){
            this.lock = lock;
            this.await = await;
            this.notify = notify;
        }

        @Override
        public void run(){
            int num = 1;

            lock.lock();
            try{
                for(int i=0; i<26; i++){
                    System.out.print(num++);
                    //唤醒在此notify队列等待的线程
                    notify.signal();
                    //自己则在await队列中进行等待
                    await.await();
                }
            }catch(Exception e){

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

    static class PrintChar implements Runnable{
        private ReentrantLock lock;

        private Condition await;

        private Condition notify;

        public PrintChar(ReentrantLock lock, Condition await, Condition notify){
            this.lock = lock;
            this.await = await;
            this.notify = notify;
        }

        @Override
        public void run(){
            char num = 65;

            lock.lock();
            try{
                for(int i=0; i<26; i++){
                    System.out.print(num+"");
                    num++;
                    //唤醒在此notify队列等待的线程
                    notify.signal();
                    //自己则在await队列中进行等待
                    await.await();
                }
            }catch(Exception e){

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

    public static void main(String[] args){
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Condition condition1 = lock.newCondition();

        //打印线程和打印数字所等待的队列是不同的
        new Thread(new PrintNumber(lock, condition, condition1)).start();
        new Thread(new PrintChar(lock, condition1, condition)).start();
    }
}

如果想要达到,无论先启动哪个线程都是按照1A2B3C.......,该如何设计?

题解:

       此时的话,需要先让不是先执行的进行等待,而第一个执行的线程不用等待

public class LC {
    static class PrintNumber implements Runnable{
        private ReentrantLock lock;

        private Condition await;

        private Condition notify;

        public PrintNumber(ReentrantLock lock, Condition await, Condition notify){
            this.lock = lock;
            this.await = await;
            this.notify = notify;
        }

        @Override
        public void run(){
            int num = 1;

            lock.lock();
            try{
                for(int i=0; i<26; i++){
                    System.out.print(num++);
                    //唤醒在此notify队列等待的线程
                    notify.signal();
                    //自己则在await队列中进行等待
                    await.await();
                }
            }catch(Exception e){

            }
            finally{
                lock.unlock();
                System.out.println("打印数字线程执行完毕");
            }
        }
    }

    static class PrintChar implements Runnable{
        private ReentrantLock lock;

        private Condition await;

        private Condition notify;

        public PrintChar(ReentrantLock lock, Condition await, Condition notify){
            this.lock = lock;
            this.await = await;
            this.notify = notify;
        }

        @Override
        public void run(){
            char num = 65;

            lock.lock();
            try{
                for(int i=0; i<26; i++){
                    //自己则在await队列中进行等待
                    await.await();

                    System.out.print(num+"");

                    num++;
                    //唤醒在此notify队列等待的线程
                    notify.signal();

                }
            }catch(Exception e){

            }
            finally{
                lock.unlock();
                System.out.println("打印字母线程执行完毕");
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Condition condition1 = lock.newCondition();

        //打印线程和打印数字所等待的队列是不同的
        new Thread(new PrintChar(lock, condition1, condition)).start();
        Thread.sleep(100);
        new Thread(new PrintNumber(lock, condition, condition1)).start();
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值