固定运行顺序
比如,先打印2再打印1
Wait-Notify版本
public class TestShunXu {
static final Object lock = new Object();//锁对象
//表示t2是否运行过
static boolean t2runned = false;
public static void main(String[] args) {
Thread t1 = new Thread(()->{
synchronized (lock){
while(!t2runned){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("1");
}
},"t1");
Thread t2 = new Thread(()->{
synchronized (lock){
t2runned=true;
lock.notify();
}
},"t2");
}
}
实际上线程1中wait等待的条件就是线程2是否运行,在编写线程2同步代码块中的代码时,逻辑为:进入同步代码块即已经拿到了锁,则代表线程2正在访问,即将t2ruuner置为true即可,如果线程1处于等待状态notify唤醒,如果线程1还没获得锁处于阻塞状态(没能进入同步代码块中),线程2的notify方法即相当于无效,不会产生问题。
Park-Unpark版本
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
public class TestShunXu {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
LockSupport.park();//等待
System.out.println("1");
},"t1");
Thread t2 = new Thread(()->{
System.out.println("2");
LockSupport.unpark(t1);
},"t2");
t1.start();
t2.start();
}
}
最简单的版本,但是也需注意,与同步块控制不同,这里未加锁所以两个线程实际是并行运行,但也要考虑语句的执行先后。
例如t1先调用park则会处于等待状态,线程2执行完毕后再回来唤醒则没有问题;
例如t2线程先调用unpark则也没有问题,这里参考park-unpark原理;
交替输出(核心例子,面向对象的多线程编程思想)
问题描述:三个线程,交替输出abc,一共输出5次。
输出效果:abcabcabcabcabc
Wait-Notify版本
public class TestTurnPrint {
public static void main(String[] args) {
WaitNotify wn= new WaitNotify(1,5);//多个线程共享这一份对象
new Thread(()->{
try {
wn.print("a",1,2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
wn.print("b",2,3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
wn.print("c",3,1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
/*
输出内容 等待标记 下一个标记
a 1 2
b 2 3
c 3 1
*/
class WaitNotify{
//等待标记
private int flag;
//循环次数
private int loopNumber;
//构造方法
public WaitNotify(int flag,int loopNumber){
this.flag=flag;
this.loopNumber=loopNumber;
}
//核心方法-打印
public void print(String str,int waitFlag,int nextFlag) throws InterruptedException {
for(int i=0;i<loopNumber;i++){
synchronized (this){
while(flag!=waitFlag){
this.wait();
}
System.out.println(str);
flag=nextFlag;
this.notifyAll();
}
}
}
}
Await-Signal版本
import javax.sound.midi.Soundbank;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TesAwaitAndSignal {
public static void main(String[] args) throws InterruptedException {
AwaitSignal awaitSignal = new AwaitSignal(5);//创建ReentrantLock对象
Condition a =awaitSignal.newCondition();
Condition b = awaitSignal.newCondition();
Condition c = awaitSignal.newCondition();
new Thread(()->{
awaitSignal.print("a",a,b);
}).start();
new Thread(()->{
awaitSignal.print("b",b,c);
}).start();
new Thread(()->{
awaitSignal.print("c",c,a);
}).start();
//因为三个线程都会进入到【WAITING】状态中,所以需要手动唤醒a线程
Thread.sleep(1000);
System.out.println("开始运行......");
awaitSignal.lock();
try{
a.signal();//唤醒a休息室中的中线程,因为只有一个线程,所以是精准唤醒
}finally {
awaitSignal.unlock();
}
}
}
class AwaitSignal extends ReentrantLock{
private int loopNumber;
public AwaitSignal(int loopNumber){
this.loopNumber=loopNumber;
}
//参数1打印内容,参数2进入哪一间休息室,参数3进入下一间休息室
public void print(String str, Condition current,Condition next){
for(int i=0;i<loopNumber;i++){
this.lock();//上锁
try {
current.await();
System.out.println(str);
next.signal();//唤醒下一间休息室等待的线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
this.unlock();
}
}
}
}
这个版本比较清晰,线程均执行await方法进入等待状态,当线程被唤醒的时候,执行代码输出字符并将下一个线程唤醒,循环五次即可实现线程的循环输出,本例中也用到了多条件变量,所以能够精准唤醒,没有了虚假唤醒所以不需要在await方法外加入while循环,这是与wait-notify方法的区别。
Park-Unpark版本
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
public class TestParkAndUnpark {
static Thread t1;
static Thread t2;;
static Thread t3;
public static void main(String[] args) {
ParkUnpark pu =new ParkUnpark(5);
t1 = new Thread(()->{
pu.print("a",t2);
});
t2 = new Thread(()->{
pu.print("b",t3);
});
t3 = new Thread(()->{
pu.print("c",t1);
});
t1.start();
t2.start();
t3.start();
//主线程启动
LockSupport.unpark(t1);
}
}
class ParkUnpark{
private int loopNumber;
public ParkUnpark(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String str,Thread next){
for (int i=0;i<loopNumber;i++){
LockSupport.park();//进入线程则使得线程停下来
System.out.println(str);
LockSupport.unpark(next);
}
}
}
park&unpark方法没有加锁的限制,没有同步代码块,与前两种方法的思路一致,首先三个线程均进入等待状态,由唤醒顺序来决定线程的执行顺序。