Java并发面试常见编程题:两个线程轮流打印(可扩展为N个线程轮流打印)

需求:要求两个线程轮流打印输出

方法一 synchronized

synchronized可以把任意非NULL的对象当做锁。
作用范围:

  • synchronized作用于对象时,锁住的是所有以该对象为锁的代码块。
  • 作用于方法时,锁住的是对象的实例(this)
  • 作用于静态方法时,锁住的是class对象。class对象存储在方法区中,而方法区时共享的,因此锁静态方法相当于一个类的全局锁。

Object对象的方法中:

  • void notify() 唤醒在此对象监视器上等待的单个线程
  • void wait() 导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法
public class PrintAB {
    private static Object obj;
    public static void main(String[] args) {
        obj = new Object();
        Thread1 thread1 = new Thread1();
        thread1.start();
        Thread2 thread2 = new Thread2();
        thread2.start();
    }
    static class Thread1 extends Thread{
        @Override
        public void run() {
            super.run();
            while(true) {
                synchronized (obj)
                {
                    System.out.println(this.getName()+"打印");
                    obj.notify();//通知另外一个线程继续执行。
                    try {
                        obj.wait();//强迫一个线程继续等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    static class Thread2 extends Thread{
        @Override
        public void run() {
            super.run();
            while(true){
                synchronized (obj)
                {
                    System.out.println(this.getName()+"打印");
                    obj.notify();
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

改为三个线程轮流打印:

public class PrintAB {
    private static Object obj;
    private static  int id;
    public static void main(String[] args) {
        obj = new Object();
        id = 1;
        Thread1 thread1 = new Thread1();
        thread1.start();
        Thread2 thread2 = new Thread2();
        thread2.start();
        Thread3 thread3 = new Thread3();
        thread3.start();
    }
    static class Thread1 extends Thread{
        @Override
        public void run() {
            super.run();
            while(true) {
                synchronized (obj)
                {
                    while (id!=1) {
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(this.getName()+"打印");
                    id = 2;
                    obj.notifyAll();//通知另外一个线程继续执行。
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    static class Thread2 extends Thread{
        @Override
        public void run() {
            super.run();
            while(true){
                synchronized (obj)
                {
                    while (id != 2) {

                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(this.getName()+"打印");
                    id = 3;
                    obj.notifyAll();
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    static class Thread3 extends Thread{
        @Override
        public void run() {
            super.run();
            while(true){
                synchronized (obj)
                {
                    while (id!=3) {
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(this.getName()+"打印");
                    id = 1;
                    obj.notifyAll();
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

方法二 Semaphore信号量

信号量(英语:semaphore)又称为信号标,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态.
创建两个信号量,类似于红绿灯。
semaphore1初始化为1,因此可供线程1获取,线程池打印完后释放semaphore2. 轮到线程2执行,线程二执行完唤醒semaphore1.

import java.util.concurrent.Semaphore;
public class PrintAB {
    private static Object obj;
    private static Semaphore semaphore1;
    private static Semaphore semaphore2;
    public static void main(String[] args) {
        semaphore1 = new Semaphore(1);
        semaphore2 = new Semaphore(0);
        Thread1 thread1 = new Thread1();
        thread1.start();
        Thread2 thread2 = new Thread2();
        thread2.start();
    }
    static class Thread1 extends Thread{
        @Override
        public void run() {
            super.run();
            while(true) {
                try {
                    semaphore1.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(this.getName()+"打印");
                semaphore2.release();
            }
        }
    }
    static class Thread2 extends Thread{
        @Override
        public void run() {
            super.run();
            while(true){
                try {
                    semaphore2.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(this.getName()+"打印");
                semaphore1.release();

            }
        }
    }

}
semaphore信号量的使用机制可以扩展为多个线程轮流打印。

方法三 Lock(公平锁)

公平锁(Fair):加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
非公平锁(Nonfair):加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待
Lock默认为不公平锁,但这里只有两个线程,一个线程在执行时只有一个线程在等待,因此当线程1释放锁时,线程2一定可以进去。

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PrintAB {
    private static Lock lock;
    private static boolean flag;
    public static void main(String[] args) {
        lock = new ReentrantLock(true);//将ReentrantLock设置为公平锁
        Thread1 thread1 = new Thread1();
        thread1.start();
        Thread2 thread2 = new Thread2();
        thread2.start();
    }
    static class Thread1 extends Thread{
        @Override
        public void run() {
            super.run();
            while(true) {
                lock.lock();
                    System.out.println(this.getName()+"打印");
//                    flag = false;
                lock.unlock();
            }
        }
    }
    static class Thread2 extends Thread{
        @Override
        public void run() {
            super.run();
            while(true){
                lock.lock();
                    System.out.println(this.getName()+"打印");
//                    flag = true;
                lock.unlock();

            }
        }
    }
}

方法四 无锁打印

维护一个访问标记位,提供两个线程进行访问判断
出现问题:该方法运行循环几十次就自动停止
原因???

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PrintAB {
    private static Lock lock;
    private static boolean flag;
    public static void main(String[] args) {
        flag = true;
        Thread1 thread1 = new Thread1();
        thread1.start();
        Thread2 thread2 = new Thread2();
        thread2.start();
    }
    static class Thread1 extends Thread {
        @Override
        public void run() {
            super.run();
            while (true) {
                if (flag) {
                    System.out.println(this.getName() + "打印");
                    flag = false;
                }
            }
        }
    }
    static class Thread2 extends Thread{
        @Override
        public void run() {
            super.run();
            while(true){
                if (!flag) {
                    System.out.println(this.getName() + "打印");
                    flag = true;
                }
            }
        }
    }
}

方法五 BlockingQueue 阻塞队列

利用阻塞队列的阻塞特性也可实现交替打印。
实现过程:
初始两个阻塞队列blockingDeque1与blockingDeque2,容量为1,blockingDeque1用来存储线程1的信息,blockingDeque2用来存储线程2的信息。
线程1负责消费blockingDeque2中的信息,然后往blockingDeque1中添加信息。
线程2负责消费blockingDeque1中的信息,然后往blockingDeque2中添加信息。
一开始时,线程1往blockingDeque1中添加信息,这样线程2才可以消费。线程二消费完就往线程1中添加信息,轮到消除1消费。
线程1,循环内到blockingDeque2队列取数据,如为空则等待,取到信息打印。然后往blockingDeque1中增加信息,如果满则阻塞等待。
线程2,循环内到blockingDeque1队列取数据,如为空则等待,取到信息打印。然后往blockingDeque2中增加信息,如果满则阻塞等待。
其实原理还是两线程阻塞,互相轮流唤醒的过程。只不过阻塞和唤醒,线程间间通信交给了阻塞队列。
阻塞队列底层是用到 ReentrantLock,ReentrantLock 底层是 AQS,AQS 底层也应用到CAS + LockSupport。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;

public class PrintAB{
    private static ArrayBlockingQueue<String> blockingDeque1;
    private static ArrayBlockingQueue<String> blockingDeque2;
    public static void main(String[] args) {
        blockingDeque1 = new ArrayBlockingQueue<String>(1);
        blockingDeque2 = new ArrayBlockingQueue<String>(1);
        Thread1 thread1 = new Thread1();
        thread1.start();
        Thread2 thread2 = new Thread2();
        thread2.start();
    }
    static class Thread1 extends Thread{
        @Override
        public void run() {
            super.run();
            try {
                blockingDeque1.put(this.getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            while(true) {
                try {
                    System.out.println(blockingDeque2.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    blockingDeque1.put(this.getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
    static class Thread2 extends Thread{
        @Override
        public void run() {
            while (true) {
                try {
                    System.out.println(blockingDeque1.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    blockingDeque2.put(this.getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值