提升--14---华为面试题: 交替输出,顺序打印
Posted 高高for 循环
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了提升--14---华为面试题: 交替输出,顺序打印相关的知识,希望对你有一定的参考价值。
文章目录
华为的一道面试题
需求
- 这个面试题是两个线程,第一个线程是从1到26,第二个线程是从A到一直到Z,然后要让这两个线程做到同时运行,交替输出,顺序打印。
方法1: wait、notify和notifyAll
package c_026_00_interview;
public class T06_00_sync_wait_notify {
public static void main(String[] args) {
final Object o = new Object();
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
new Thread(()->{
synchronized (o) {
for(char c : aI) {
System.out.print(c);
try {
o.notify();
o.wait(); //让出锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify(); //必须,否则无法停止程序
}
}, "t1").start();
new Thread(()->{
synchronized (o) {
for(char c : aC) {
System.out.print(c);
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t2").start();
}
}
注意:
- 最后 o.notify(); //必须,否则无法停止程序
如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
思考
如果我想保证t2在t1之前打印,也就是说保证首先输出的是A而不是1,这个时候该如何做?
- 使用自旋的方式,设置一个boolean类型的变量,t2刚开始不是static。如果说t2没有static的话,我这个t1线程就wait,要求t2必须先static才能执行我的业务逻辑
- 还有一种写法就是t2上来二话不说先wait,然后t1呢上来二话不说先输出,输出完了之后notify;
- 还有一种写法用CountDownLatch也可以;
public class T07_00_sync_wait_notify {
private static volatile boolean t2Started = false;
public static void main(String[] args) {
final Object o = new Object();
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
new Thread(()->{
synchronized (o) {
while(!t2Started) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//
for(char c : aI) {
System.out.print(c);
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t1").start();
new Thread(()->{
synchronized (o) {
for(char c : aC) {
System.out.print(c);
t2Started = true;
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t2").start();
}
}
package c_026_00_interview;
import java.util.concurrent.CountDownLatch;
public class T07_00_sync_wait_notify {
private static CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) {
final Object o = new Object();
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o) {
for (char c : aI) {
System.out.print(c);
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t1").start();
new Thread(() -> {
synchronized (o) {
for (char c : aC) {
System.out.print(c);
latch.countDown();
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t2").start();
}
}
方法2: condition.signal(); condition.await();
- 我们来看看它的第一种写法我用一个ReentrantLock,然后调用newCondition,上来之后先lock相当于synchronized了,打印,打印完之后signal叫醒另一个当前的等待,最后condition.signal()相当于notify(),然后之后另外一个也类似就完了,这种写法相当于synchronized的一个变种。
package c_026_00_interview;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T08_00_lock_condition {
public static void main(String[] args) {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
try {
lock.lock();
for(char c : aI) {
System.out.print(c);
condition.signal();
condition.await();
}
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t1").start();
new Thread(()->{
try {
lock.lock();
for(char c : aC) {
System.out.print(c);
condition.signal();
condition.await();
}
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t2").start();
}
}
两个Condition会更好
- 第一线程呢conditionT2.signal(),叫醒第二个那个里面的线程,然后我第一个线程让它等待,第二个就是我叫醒第一个线程,第二个让它等待放到这个等待队列里,相当于我放了两个等待队列,t1在这个等待队列里,t2在另一个等待队列里,在t1完成了之后呢叫醒t2是指定你这个队列的线程醒过来,所以永远都是t2。其实对于两个线程来讲区别不大,因为你叫醒的时候当前线程肯定是醒着的,叫醒的也就只有是你这个线程 ,不过对于写代码来说,写到这个样子面试官肯定是会高看你一眼。
/*
Condition本质是锁资源上不同的等待队列
*/
package com.mashibing.juc.c_026_00_interview.A1B2C3;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T09_00_lock_condition {
public static void main(String[] args) {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
Lock lock = new ReentrantLock();
Condition conditionT1 = lock.newCondition();
Condition conditionT2 = lock.newCondition();
new Thread(()->{
try {
lock.lock();
for(char c : aI) {
System.out.print(c);
conditionT2.signal();
conditionT1.await();
}
conditionT2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t1").start();
new Thread(()->{
try {
lock.lock();
for(char c : aC) {
System.out.print(c);
conditionT1.signal();
conditionT2.await();
}
conditionT1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t2").start();
}
}
方法3: LockSupport
- 用LockSupport其实是最简单的。你让一个线程输出完了之后停止,然后让另外一个线程继续运行就完了。我们定义了两个数组,两个线程,第一个线程拿出数组里面的每一个数字来,然后打印,打印完叫醒t2,然后让自己阻塞。另外一个线程上来之后自己先park,打印完叫醒线程t1。两个线程就这么交替来交替去,就搞定了。
import java.util.concurrent.locks.LockSupport;
//Locksupport park 当前线程阻塞(停止)
//unpark(Thread t)
public class T02_00_LockSupport {
static Thread t1 = null, t2 = null;
public static void main(String[] args) throws Exception {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
t1 = new Thread(() -> {
for(char c : aI) {
System.out.print(c);
LockSupport.unpark(t2); //叫醒T2
LockSupport.park(); //T1阻塞
}
}, "t1");
t2 = new Thread(() -> {
for(char c : aC) {
LockSupport.park(); //t2阻塞
System.out.print(c);
LockSupport.unpark(t1); //叫醒t1
}
}, "t2");
t1.start();
t2.start();
}
}
方法4: 自己写了一个自旋锁
在这里,用一个自旋式的写法,就是我们没有锁,相当于自己写了一个自旋锁。cas的写法.
- 这个写法用了enum,到底哪个线程要运行他只能取两个值,T1和T2,然后定义了一个ReadyToRun的变量,刚开始的时候是T1,这个意思呢就相当于是我有一个信号灯,这个信号灯要么就是T1要么就是T2,只能取这个两个值,不能取别的,当一开始的时候我在这个信号灯上显示的是T1,T1你可以走一步。看程序,第一上来判断是不是T1啊,如果不是就占用cpu在这循环等待,如果一看是T1就打印,然后把r值变成T2进行下一次循环,下一次循环上来之后这个r是不是T1,不是T1就有在这转圈玩儿,而第二个线程发现它变成T2了,变成T2了下面的线程就会打印A,打印完了之后有把这个r变成了T1,就这么交替交替,就是这个一种玩法,写volatile是保证线程的可见性。
- 为什么要用enum类型,就是防止它取别的值,用一个int类型或者布尔也都可以。
package c_026_00_interview;
public class T03_00_cas {
enum ReadyToRun {T1, T2}
static volatile ReadyToRun r = ReadyToRun.T1; //思考为什么必须volatile
public static void main(String[] args) {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
new Thread(() -> {
for (char c : aI) {
while (r != ReadyToRun.T1) {}
System.out.print(c);
r = ReadyToRun.T2;
}
}, "t1").start();
new Thread(() -> {
for (char c : aC) {
while 以上是关于提升--14---华为面试题: 交替输出,顺序打印的主要内容,如果未能解决你的问题,请参考以下文章