Java并发——两个线程交替打印两个数组中的元素 | 多个线程按顺序输出数字

有两个字符数组

  • number = {‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’}
  • letter = {‘A’,‘B’,‘C’,‘D’,‘E’,‘F’,‘G’,‘H’,‘I’}

要求启动2个线程,交替打印其中元素,输出结果为

  • 1 A 2 B 3 C 4 D 5 E 6 F 7 G 8 H 9 I

本文给出类LockSupport,自旋锁,wait/notify,ReentrantLock实现该功能的方式。还有一些方法比如Semaphore(使用信号量实现限流,通过acquire和release方式每次只允许一个线程执行)、BlockingQueue(需要用两个BlockingQueue,通过两个BlockingQueue之间的信息交互和put、take方法达成功能)、管道流(PipedStream),这些方法也可以完成功能,请读者自行考虑实现。

LockSupport的方法

最简单的的一种方式,使用park和unpark的方式完成两个线程之间的通信

public static void main(String[] args) {
        char[] number = {'1','2','3','4','5','6','7','8','9'};
        char[] letter = {'A','B','C','D','E','F','G','H','I'};
         t1 = new Thread(() ->{
            for(char num : number){
                System.out.print(num + " ");
                LockSupport.unpark(t2);  //唤醒t2线程
                LockSupport.park();  //将本线程阻塞
            }
        });

         t2 = new Thread(() ->{
            for(char let : letter){
                LockSupport.park();  //防止t2线程先执行从而先输出A
                System.out.print(let + " ");
                LockSupport.unpark(t1);  //唤醒t1线程
            }
        });

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

模拟自旋锁的方式

使用一个原子类型的值当作自旋的标示,如果线程要执行时发现标示不对,则一直while自旋等待

static final AtomicInteger threadNo = new AtomicInteger(1);

public static void main(String[] args) {
        char[] number = {'1','2','3','4','5','6','7','8','9'};
        char[] letter = {'A','B','C','D','E','F','G','H','I'};

        new Thread(() ->{
            for(char num : number){
                while(threadNo.get() != 1){}
                System.out.print(num + " ");
                threadNo.set(2);
            }
        }).start();

        new Thread(() -> {
            for(char let : letter){
                while(threadNo.get() != 2){}
                System.out.print(let + " ");
                threadNo.set(1);
            }
        }).start();
}

wait/notify的方式

最经典的方式,使用wait/notify的方式完成线程通信,注意这里的synchronized不能使用this,因为这是在静态方法中,这里除了可以使用自定义的监视器,还可以使用类的Class对象。
下列写法无法正确保证是先输出1还是A,读者可自行添加CountDownLatch完成按指定顺序输出的功能。

public static void main(String[] args) {
        final Object obj = new Object();
        char[] number = {'1','2','3','4','5','6','7','8','9'};
        char[] letter = {'A','B','C','D','E','F','G','H','I'};

        new Thread(() -> {
            synchronized (obj){
                for(char num : number){
                    System.out.print(num + " ");
                    try {
                        obj.notify(); //叫醒其他线程,这里就是t2
                        obj.wait(); //让自己阻塞,让出锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                obj.notify(); //必须要有,因为两个线程的try里面的最后一步是阻塞,如果线程执行完了还在阻塞肯定不对,必须要唤醒,才能正确结束程序
            }
        }).start();

        new Thread(() -> {
            synchronized (obj){
                for(char let : letter){
                    System.out.print(let + " ");
                    try {
                        obj.notify(); //叫醒其他线程,这里是t1
                        obj.wait(); //让自己阻塞,让出锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                obj.notify(); //同上
            }
        }).start();
}

ReentrantLock——单condition方式

这种方式和wait/notify方式一样,并不能体现出使用ReentrantLock可针对不同线程定制condition的优势

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

        char[] number = {'1','2','3','4','5','6','7','8','9'};
        char[] letter = {'A','B','C','D','E','F','G','H','I'};

        new Thread(() -> {
            lock.lock();
            try{
                for(char num : number){
                    System.out.print(num + " ");
                    condition.signal();
                    condition.await();
                }
                condition.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }).start();

        new Thread(() -> {
            lock.lock();
            try{
                for(char let : letter){
                    System.out.print(let + " ");
                    condition.signal();
                    condition.await();
                }
                condition.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }).start();
}

ReentrantLock——多condition方式

使用两个condition,从而实现对不同线程的监视,体现出ReentrantLock对比wait/notify的优势特性

public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition conditionNum = lock.newCondition();
        Condition conditionLet = lock.newCondition();

        char[] number = {'1','2','3','4','5','6','7','8','9'};
        char[] letter = {'A','B','C','D','E','F','G','H','I'};

        new Thread(() -> {
            lock.lock();
            try{
                for(char num : number){
                    System.out.print(num + " ");
                    conditionLet.signal();
                    conditionNum.await();
                }
                conditionLet.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }).start();

        new Thread(() -> {
            lock.lock();
            try{
                for(char let : letter){
                    System.out.print(let + " ");
                    conditionNum.signal();
                    conditionLet.await();
                }
                conditionNum.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
            }
        }).start();
}

启动三个线程,每个线程输出一个数字,要求按顺序输出1,2,3

可以采用上文中最简单的LockSupport的方式,将线程逐一唤醒执行

//思路:由于t2和t3一开始是park(阻塞)的,所以一定会先输出1,然后逐一唤醒对应线程
static  Thread t11 = null, t22 = null, t33 = null;
public static void main(String[] args) {
        t11 = new Thread(() -> {
            System.out.println(1);
            LockSupport.unpark(t22);
        });

        t22 = new Thread(() -> {
            LockSupport.park();
            System.out.println(2);
            LockSupport.unpark(t33);
        });

        t33 = new Thread(() -> {
            LockSupport.park();
            System.out.println(3);
        });

        t11.start();
        t22.start();
        t33.start();
}

你可能感兴趣的:(Java并发——两个线程交替打印两个数组中的元素 | 多个线程按顺序输出数字)