前言
考虑到之前写的博客太过于偏重理论性,这篇博客是Java线程的一个小的应用实例。我们的例子中会应用到线程中的wait,notifyAll,synchronized,volatile等等线程方法和关键字,来实现这个功能,也可以更好地让大家体会到这些线程相关的理论内容,在实际运行中有什么样子的表现。最后也会用Semaphore去从另一个思路实现相同的功能,用来提供另一种思路。更多线程知识内容请点击【Java 多线程和锁知识笔记系列】
场景
场景大意是:现在有三个线程,线程t1输出A,线程t2输出B,线程t3输出C。这个程序的目的:就是让这三个线程依次顺序输出其打印的内容10遍,最终交替顺序执行输出ABC这样一个任务。
普通例子
首先分析我们要怎么做:首先三个线程,交互或者交替执行其实并不难。如果要顺序执行就必须让三个线程感知到对方是否正在执行自己的内容,那么根据我们之前所说的内容很容易想到我们可以用volatile关键字去做一个感知。为了区分三个线程的执行,那么我们也给三个线程各自一个名字。区分线程可以用if条件语句或者switch语句加上volatile修饰的信号量去做,大概分析结果结束,代码例子如下:
public class OrderOutput {
// 设置线程名,用来在交互过程中区分线程
private static final String THREAD1="t1";
private static final String THREAD2="t2";
private static final String THREAD3="t3";
// 设置线程信号量,用来控制哪个线程需要被唤醒或等待
private static volatile int flag=1;
// 设置执行次数信号量,用来控制线程执行次数
private static volatile int stop=0;
public static void main(String[] args) {
InnerThread t=new InnerThread();
new Thread(t,THREAD1).start();
new Thread(t,THREAD2).start();
new Thread(t,THREAD3).start();
}
private static class InnerThread implements Runnable{
@Override
public void run() {
while (stop<30) {//控制线程执行次数、
//这里使用synchronized锁住当前对象,其实锁住的就是上面main方法中的t,
// 可以回忆下这里锁住的t,为什么能够锁住线程
synchronized (this) {
stop++; //volatile变量标记,由于volatile并不能保证原子性,因此我们在synchronized里面操作
switch (Thread.currentThread().getName()) {
case THREAD1:
while (flag!=1) { //当线程执行到这里发现信号量是t2或者t3时
try {
wait(0); //就把t1 wait住,等待t2或者t3先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case THREAD2:
while (flag!=2) { //同理
try {
wait(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
case THREAD3:
while (flag!=3) { //同理
try {
wait(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
try {
Thread.sleep(100); //设置一个间隔时间,方便观察结果
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//执行输出
switch (Thread.currentThread().getName()) {
case THREAD1:
if (flag==1) { //当发现是线程t1,执行输出A
System.out.print("A");
flag=2; //并且修改线程标记为了唤醒t2
}
break;
case THREAD2:
if (flag==2) { //当发现是线程t2,执行输出B
System.out.print("B");
flag=3; //并且修改线程标记为了唤醒t3
}
break;
case THREAD3:
if (flag==3) { //当发现是线程t3,执行输出C
System.out.print("C");
flag=1; //并且修改线程标记为了唤醒t1
}
break;
}
//唤醒所有线程,开始竞争资源
notifyAll();
}
}
}
}
}
-------------输出-------------------
ABCABCABCABCABCABCABCABCABCABC
上面程序的执行结果就是ABC这样交替顺序输出,由于用的print()不是println()所以就有了这样一个效果。这个例子其实非常的简单明了,只有一点要再次说下:怎么控制第一个是谁输出的呢?就是我们初始化信号量flag,这里设置的。笔者默认的是1就会使得t1先去执行,同样的,当线程被wait住以后,调用notifyAll()方法时,也只有flag信号量的值对应的线程能够执行。
Semaphore 实现
说完普通的例子,咱们在看一个Semaphore的实现,同样我们需要一个信号量来控制输出,以及一个信号量来控制执行次数。由于Semaphore给我们提供了获取锁与释放锁的方法,因此我们就不需要在使用线程名字控制线程,直接使用Semaphore进行线程顺序控制即可,原理都是差不多的,只不过提供了另一个思路,例子如下:
public class OrderOutputSemaphore {
private Semaphore s1=new Semaphore(1);
private Semaphore s2=new Semaphore(1);
private Semaphore s3=new Semaphore(1);
// 设置线程信号量,用来控制哪个线程需要被唤醒或等待
private static volatile int flag=1;
// 设置执行次数信号量,用来控制线程执行次数
private static volatile int stop=0;
// 创建第一个线程
private Thread t1=new Thread(()->{
while (stop<30) {
stop++; //这里为了保证原子性,最好锁一下
try {
if (flag!=1) { //信号量flag不是1,加锁
s1.acquire();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (flag==1) { //信号量flag是1,执行逻辑
System.out.print("A");
flag=2; //修改信号量为2
s2.release(); //释放s2,让程序继续执行
}
}
});
private Thread t2=new Thread(()->{
while (stop<30) {
stop++;
try {
if (flag!=2) {
s2.acquire();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (flag==2) {
System.out.print("B");
flag=3;
s3.release();
}
}
});
private Thread t3=new Thread(()->{
while (stop<30) {
stop++;
try {
if (flag!=3) {
s3.acquire();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (flag==3) {
System.out.print("C");
flag=1;
s1.release();
}
}
});
public static void main(String[] args) {
OrderOutputSemaphore order=new OrderOutputSemaphore();
order.t1.start();
order.t2.start();
order.t3.start();
}
}
-------------输出-------------------
ABCABCABCABCABCABCABCABCABCABC