题目摘自:偏头痛杨
最近看了这位博主的文章 写的挺好的 跟着里面的线程 温习了一遍 结尾处有道题算是复习巩固吧
我是用ReentrantLock实现的 而不是synchronized
题目:
使用3个线程,要求三个线程顺序执行,不允许使用sleep()强制让线程有顺序。
线程A输出1
线程B输出2
线程C输出3
线程A输出4
线程B输出5
以此类推,一直输出到1000为止。
题目是不是看着不难 一开始我也是这么觉得的
后来我发现自己错了 还错了很离谱…… 也许是自己思维不够灵活 花了一下午时间解决……
不过也算是初步掌握了 ReentrantLock的运用
讲一下思路吧
题目要求是ABC顺序依次输出结果
可线程是抢占式的,鬼会听你安排 一个一个输出哦 肯定都是抢着去占CPU
线程是并发式的 意思就是 看起来像是一起执行 实际是一次只能执行一次线程
那怎么才可以让线程乖乖听话?
答:一个执行的时候 另外两个等待不就OK了
比如 A执行的时候BC等待 A执行完后唤醒B执行 B执行完后唤醒C执行 C执行完后唤醒A执行
这样一直循环 就是ABCABC的执行顺序了
过程:
思路有了,那我们如何来实现呢?
一开始 脑子一蹦出来就是用synchronized
来解决 后来想想不行啊
synchronized的唤醒方式有2种(学的不深 不知道是否还有其他方法)
1. notify() 随机唤醒一个线程
2. notifyAll() 唤醒全部线程
题目要求我们是按照顺序ABC来执行线程的 我滴个神啊 这tm不是断我路吗!
别急别急 还好有 ReentrantLock
题外话:
话说网上一直有争议 到底是ReentrantLock
处理并发好还是synchronized
好?
讲实话 我也很难说清楚 因为自己也是学的不精 不过大致可以这么理解:
在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,
其性能下降很严重,此时ReentrantLock是个不错的方案
回到刚才的问题:如何使用ReentrantLock
来解决顺序执行的问题?
首先我们得创建ReentrantLock
锁和Condition
小黑屋
private ReentrantLock r = new ReentrantLock(); //创建锁
private Condition c1 = r.newCondition(); //小黑屋C1
private Condition c2 = r.newCondition(); //小黑屋C2
private Condition c3 = r.newCondition(); //小黑屋C3
上面小黑屋什么意思呢?
其实小黑屋是我给它取得名字 因为它很符合盒子的形象:
A执行的时候让BC等待 B,C就被关到c2,c3这两间小黑屋里 没有人来开门 它们就永远都无法出来
(B,C等待唤醒)
第一条拦路虎:如何防止抢占?
多线程的常见问题 :A线程执行的时候B抢占CPU A停止手头工作等待 这会产生脏数据
等问题
所以得加锁呀
private ReentrantLock r = new ReentrantLock(); //创建了锁
r.lock(); //添加锁
//代码A
r.unlock(); // 释放锁
上面意思就是说 这块代码A被加锁了 无法被其他线程抢占
注意:不只是针对同一块代码啊!具体看下面这个栗子:
r.lock(); //添加锁
//代码A
r.unlock(); // 释放锁
r.lock(); //添加锁
//代码B
r.unlock(); // 释放锁
当线程A执行一个类中的 代码A时 线程B不能执行代码B 虽然他们不是同一个代码块
因为他们来自一个锁r(ReentrantLock)
所谓人一心不可二用 这里也是这个道理。一个锁 只能锁一个代码块
所以 有两种情况不行:
1. 线程A执行代码A 线程B执行代码A
2. 线程A执行代码A 线程B执行代码B
第二条拦路虎:如何让B被关进小黑屋(等待)?
.await()
函数派上用场了 这就是等待的意思 对应synchronized的wait()函数
线程执行 await() 等待后 当前位置(执行await())下面代码不会被执行
同时释放锁 如果被唤醒了 还是从之前被锁定的位置开始
也可以这么理解 :从哪里跌倒 ,从哪里爬起来
在B线程里执行 c2.await();即可
就是把B线程放进c2中等待唤醒
举个栗子:
线程B代码块{
r.lock(); //添加锁
c2.await(); //把B关进c2小黑屋 等待唤醒
r.unlock(); // 释放锁
}
注意了哦! 当线程等待的时候 自动释放锁 很关键 得记住:当调用await()时默认调用unlock()
第三条拦路虎:如何实现ABC线程顺序(这才是关键)
这个时候我们需要一个变量 来指示 当前线程到底时A还是B 1代表A
2代表B
3代表C
public int flag = 1;
线程A代码块{
if (flag != 1) {
c1.await(); //A关进小黑屋C1
}
c2.signal(); //唤醒小黑屋c2里的家伙(这里指B)
}
线程B代码块{
if (flag != 2) {
c2.await(); //B关进小黑屋C2
}
c3.signal(); //唤醒小黑屋C3里的家伙(这里指C)
}
线程C代码块{
if (flag != 3) {
c3.await(); //C关进小黑屋C3
}
c1.signal(); //唤醒小黑屋c1里的家伙(这里指A)
}
最后一条拦路虎:如何停止线程ABC
核心代码
举个栗子A:
if(i==1001) {
c2.signal(); //唤醒B
c3.signal(); //唤醒C
r.unlock(); //释放锁
}
r.unlock();
这个是关键 一定要写 不然到最后C线程无法停止
因为 虽然唤醒了C1小黑屋里的C
但是: r这个锁还锁着其他线程 所以线程C无法 执行被r锁住的代码 参考第一条拦路虎
还有哦! 我是用 静态变量 i
来记录这个递增的数 因为是三个线程共享嘛
提一下哦 !因为ABC线程只执行一次 我是直接采用 匿名对象来实现(实际是有类名的 不过是借用了父类或者接口的类名) 这样子
使用ReentrantLock锁来实现代码:
package day12;
import java.sql.Time;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class Printer3 {
private ReentrantLock r = new ReentrantLock();
private Condition c1 = r.newCondition();
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
public static int i = 1;
public int flag = 1;
public void print1() throws InterruptedException {
r.lock();
if (flag != 1) {
c1.await();
}
if(i==1001) {
c2.signal();
c3.signal();
r.unlock();
return;
}
System.out.println("线程A:" + i++);
flag = 2;
c2.signal();
r.unlock(); // 释放锁
}
public void print2() throws InterruptedException {
r.lock();
if (flag != 2) {
c2.await();
}
if(i==1001) {
c1.signal();
c3.signal();
r.unlock();
return;
}
System.out.println("线程B:" + i++);
flag = 3;
c3.signal();
r.unlock();
}
public void print3() throws InterruptedException {
r.lock();
if (flag != 3) {
c3.await();
}
if(i==1001) {
c1.signal();
c2.signal();
r.unlock();
return;
}
System.out.println("线程C:" + i++);
flag = 1;
c1.signal();
r.unlock();
}
}
public class thread {
public static void main(String[] args) {
final Printer3 p = new Printer3();
long startTime = System.currentTimeMillis();
new Thread() {
public void run() {
while (true) {
try {
if (p.i == 1001) {
System.out.println("线程A结束");
break;
}
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while (true) {
try {
if (p.i == 1001) {
System.out.println("线程B结束");
break;
}
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
long startTime = System.currentTimeMillis(); //程序结束记录时间
while (true) {
try {
if (p.i == 1001) {
System.out.println("线程C结束");
break;
}
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis(); //程序结束记录时间
long TotalTime = endTime - startTime;
System.out.print("耗时:"+TotalTime);
}
}.start();
}
}
实验结果:
最后提一下 线程 停止有多个方法 :
最好就是让他自己执行完run方法 自然消亡
而不是使用interrupt
强制打断
真的是千辛万苦……不过 也算是有点收获了
要是我的这篇博客可以解答你的些许问题,那也是极好的
如果有问题 欢迎提出 一起学习 一起进步。