常见面试题之两个线程交替打印奇偶数
题目:起两个线程交替打印0~100的奇偶数
这个问题大家可能在面试的时候遇到过,虽然学过多线程相关的知识,可能当时一时半会还写不出来,现在就让我带大家写一遍吧!
方法一
首先,我们可以观察到打印的是奇数和偶数,那么我们就可以通过这个特点去写代码。假如我们有一个全局变量 count,当 count 等于偶数的时候,我们就让线程1打印,当 count 等于奇数的时候,我们就让线程2打印。那如何实现呢?这里我们就可以用到锁了,来保证同一时刻只有一个线程在执行。但是如果同一个线程一直抢到锁,而另一个线程一直没有拿到,就会导致线程做很多无谓的空转,效率非常低下,不可能得到面试官的青睐。
代码如下:
public class Test {
// 全局变量 count
private int count = 0;
// 锁
private final Object lock = new Object();
public static void main(String[] args) {
Test test = new Test();
test.turning();
}
public void turning() {
Thread even = new Thread(() -> {
while (count < 100) {
// 获取锁
synchronized (lock) {
// 只处理偶数
if ((count & 1) == 0) {
System.out.println(Thread.currentThread().getName() + ": " + count++);
}
}
}
}, "偶数");
Thread odd = new Thread(() -> {
while (count < 100) {
// 获取锁
synchronized (lock) {
// 只处理奇数
if ((count & 1) == 1) {
System.out.println(Thread.currentThread().getName() + ": " + count++);
}
}
}
}, "奇数");
even.start();
odd.start();
}
}
方法二
思路:这种实现方式的原理就是线程1打印之后唤醒其他线程,然后让出锁,自己进入休眠状态。因为进入了休眠状态就不会与其他线程抢锁,此时只有线程2在获取锁,所以线程2必然会拿到锁。线程2以同样的逻辑执行,唤醒线程1并让出自己持有的锁,自己进入休眠状态。这样来来回回,持续执行直到任务完成。就达到了两个线程交替获取锁的效果了。
代码如下:
public class Test {
// 全局变量 count
private int count = 0;
// 锁
private final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
test.turning();
}
public void turning() throws InterruptedException {
new Thread(new TurningRunner(), "偶数").start();
// 确保偶数线程线先获取到锁
Thread.sleep(1);
new Thread(new TurningRunner(), "奇数").start();
}
class TurningRunner implements Runnable {
@Override
public void run() {
while (count <= 100) {
// 获取锁
synchronized (lock) {
// 拿到锁就打印
System.out.println(Thread.currentThread().getName() + ": " + count++);
// 唤醒其他线程
lock.notifyAll();
try {
if (count <= 100) {
// 如果任务还没有结束,则让出当前的锁并休眠
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
方式三
利用信号量 Semaphore 实现
代码如下:
public class PrintNum {
private int n;
private Semaphore odd = new Semaphore(1);
private Semaphore even = new Semaphore(0);
public PrintNum(int n) {
this.n = n;
}
public void printOdd() {
for (int i=1; i<=n; i+=2) {
try {
odd.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("奇数" + i);
even.release();
}
}
public void printEven() {
for(int i=2; i<=n; i+=2) {
try {
even.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("偶数" + i);
odd.release();
}
}
public static void main(String[] args){
PrintNum printNum = new PrintNum(100);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(() -> {
printNum.printOdd();
});
executorService.submit(() -> {
printNum.printEven();
});
executorService.shutdown();
}
}
小贴士:
acquire():获取许可证
release():释放许可证
方式四
利用 contition 实现,大家看代码吧,代码比较直观
代码如下:
public class PrintNumThreadTest {
public static void main(String[] args) {
PrintNumOpt printNumOpt = new PrintNumOpt();
new Thread(() -> printNumOpt.print0()).start();
new Thread(() -> printNumOpt.print1()).start();
}
}
class PrintNumOpt {
int num = 0;
int opt = 0;
int maxNum = 100;
ReentrantLock lock = new ReentrantLock();
/**
* 偶数
*/
Condition condition0 = lock.newCondition();
/**
* 奇数
*/
Condition condition1 = lock.newCondition();
public void print0() {
while (this.opt == 0) {
lock.lock();
try {
for (; num < maxNum; num++) {
if (num % 2 == 0) {
System.out.println("偶数:" + num);
this.opt = 1;
condition1.signal();
condition0.await();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public void print1() {
while (this.opt == 1) {
lock.lock();
try {
for (; num < maxNum; num++) {
if (num % 2 == 1) {
System.out.println("奇数:" + num);
this.opt = 0;
condition0.signal();
condition1.await();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
小贴士:
public void await()
使当前线程等待,直到发出信号或中断信号
public void signal()
唤醒一个等待线程
public void signalAll()
唤醒所有等待线程
扩展
题目:3个线程交替打印1、2、3;4、5、6…
大家先想想,我有时间在写一篇吧 🤣🤣🤣
引用:
https://blog.csdn.net/dadiyang/article/details/88315124