记给训练营师弟们的一道多线程题目
一、题目要求
Task <Execute by Order>
tips:
1、开三个线程,这三个线程在一个死循环中不断创建,执行。每个打印的数字的不同。
2、在不使用主线程休眠的情况下来保证三个线程的顺序不变。
3、效率第一、不考虑空间复杂度。
4、以下是问题代码模板:
/**
* @author linxu
* @date 2019/7/17
* <p>
* 测试三个线程在无限执行下,都按照顺序打印1,2,3
* </p>
*/
public class Problem {
public static void main(String[] args) throws Exception {
Runnable p1 = () -> {
//除了这句打印,其它可以修改
System.out.println("1");
};
Runnable p2 = () -> {
//除了这句打印,其它可以修改
System.out.println("2");
};
Runnable p3 = () -> {
//除了这句打印,其它可以修改
System.out.println("3");
};
//keep the new order.avoid new asyn.
while (true) {
//这部分代码不允许修改
Thread t1 = new Thread(p1);
Thread t2 = new Thread(p2);
Thread t3 = new Thread(p3);
Thread.sleep(1000);
//
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
System.err.println("************************");
Thread.sleep(1000);
}
}
}
二、解法一
- 通过共享量
//设置共享阈值
private static int threashold = 0;
public static void main(String[] args) throws Exception {
Runnable p1 = () -> {
while (threashold != 0) {
//空自旋
}
//除了这句打印,其它可以修改
System.out.println("1");
threashold++;
};
Runnable p2 = () -> {
while (threashold != 1) {
//空自旋
}
//除了这句打印,其它可以修改
System.out.println("2");
threashold++;
};
Runnable p3 = () -> {
while (threashold != 2) {
//空自旋
}
//除了这句打印,其它可以修改
System.out.println("3");
threashold = 0;
};
//keep the new order.avoid new asyn.以下代码不准修改
while (true) {
//这部分代码不允许修改
Thread t1 = new Thread(p1);
Thread t2 = new Thread(p2);
Thread t3 = new Thread(p3);
Thread.sleep(1000);
//尽可能保证同时启动
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
System.err.println("************************");
Thread.sleep(1000);
}
}
- 以上采用了自旋思想,让线程都及时去检测共享变量的变化,从而当判断到是自己的标志,则执行,这样可以保证执行的有序,但是,慢!且占用CPU高。
- 慢的原因:主要是在线程修改了共享变量,但是其它线程还未能够及时地知道共享变量的最新值
- 解决方案:添加volatile,保证可见性
//设置共享阈值
private static volatile int threashold = 0;
三、解法二
解法一中,通过volatile修饰共享量,加速了线程对于共享量的更新,但是,从本质上,并没有多大的改变。解法二主要讲另外一种思想,即重入锁。
static ReentrantLock lock = new ReentrantLock();
static Condition firstCondition = lock.newCondition();
static Condition secondCondition = lock.newCondition();
static Condition thirdCondition = lock.newCondition();
static int state = 1;
public static void main(String[] args) throws Exception {
Runnable p1 = () -> {
lock.lock();
try {
if (state != 1) {
firstCondition.await();
}
System.out.println("1");
secondCondition.signal();
state = 2;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
};
Runnable p2 = () -> {
lock.lock();
try {
if (state != 2) {
secondCondition.await();
}
System.out.println("2");
thirdCondition.signal();
state = 3;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
};
Runnable p3 = () -> {
lock.lock();
try {
if (state != 3) {
thirdCondition.await();
}
System.out.println("3");
firstCondition.signal();
state = 1;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
};
//keep the new order.avoid new asyn.
while (true) {
Thread t1 = new Thread(p1);
Thread t2 = new Thread(p2);
Thread t3 = new Thread(p3);
Thread.sleep(1000);
//
t1.start();
t2.start();
t3.start();
Thread.sleep(2000);
}
}
- 好处
- 避免不必要自旋,释放CPU占用粒度
- 可重入锁,加速线程的执行
- 顺序通知(P2P)每个执行片段,只有一个线程对另外一个线程的唤醒
- 缺点
- 占用多一小部分的内存