使用三个线程,线程一打印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();
}
}