场景一:
假设在一个线程 A 中开启了另一个线程 B,A 线程依赖 B 线程的计算结果,可是直接开启线程后,很有可能 A 线程比 B 线程跑得快,也就是说还没等 B 线程计算出结果,A 线程已经结束了。
- 存在问题的代码:
public static void main(String[] args) throws Exception {
Thread parser1 = new Thread(new Runnable() {
@Override
public void run() {
temp temp = new temp(); //temp 对象的 a 属性初始化值为 0
temp.a += 100;
System.out.println("parser1 finish");
}
});
parser1.start();
temp temp = new temp();
System.out.println(temp.a);
}
//main 函数属于一个线程,它内部开启了 parser1 这个线程。
打印出的结果是 0 ,而不是 100 。
怎么解决这个问题?
让线程 A 等待着线程 B,直到线程 B 结束之后再继续进行。 只需要在线程 B 开启之后,再调用 join() 方法阻塞线程,就可以了。
- 改善代码
public static void main(String[] args) throws Exception {
Thread parser1 = new Thread(new Runnable() {
@Override
public void run() {
temp temp = new temp();
temp.a += 100;
System.out.println("parser1 finish");
}
});
parser1.start();
parser1.join(); //阻塞线程
temp temp = new temp();
System.out.println(temp.a);
}
打印的结果为 100
场景二:
假如在线程 A 里面有 B、C 这几个线程,A 依赖于 B 和 C 线程的执行结果,并且 B 和 C 之间也有某种联系,只有当 B 和 C 在某个固定的点上同步执行,它们才能默契的完成任务。
两个线程之间的关系.png
public static void main(String[] args) throws Exception {
//temp 对象的静态变量 a 初始值等于 0
//如果第一个线程先执行,那么 a 就等于 1
//这时候再执行第二个线程,a 会变成 100;
//如果它们的执行顺序正好相反,那么 a 就变成 1 了。
Thread parser1 = new Thread(new Runnable() {
@Override
public void run() {
temp temp = new temp();
temp.a += 1;
try {
System.out.println("线程一其余代码被执行");
Thread.sleep(5000L);
System.out.println("线程一执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread parser2 = new Thread(new Runnable() {
@Override
public void run() {
temp temp = new temp();
temp.a *= 100;
try {
System.out.println("线程二其余代码被执行");
Thread.sleep(5000L);
System.out.println("线程二执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
- 粗糙的方案:
如果只是简单粗暴的调用 join() 方法让线程阻塞,然后调整他们的执行顺序,其实是一种低效的方法。main 想要拿到最后的计算结果,必须等到两个线程都走完之后。
public static void main(String[] args) throws Exception {
Thread parser1 = new Thread(new Runnable() {
@Override
public void run() {
temp temp = new temp();
temp.a += 1;
try {
System.out.println("线程一其余代码被执行");
Thread.sleep(5000L);
System.out.println("线程一执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread parser2 = new Thread(new Runnable() {
@Override
public void run() {
temp temp = new temp();
temp.a *= 100;
try {
System.out.println("线程二其余代码被执行");
Thread.sleep(5000L);
System.out.println("线程二执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//parser1 和 parser2 之间是平等的竞争关系,哪个先执行不一定,所以让第二个线程等待1秒
parser1.start();
parser2.start();
parser1.join();
parser2.join(1000L);
System.out.println(new temp().a);
}
//执行结果
线程一其余代码被执行
线程二其余代码被执行
线程一执行结束
线程二执行结束
(。。。。经历漫长的等待之后。。。。)
100
- 优化方案:
我们可以使用并发包中的 CountDownLatch 来提高效率
static CountDownLatch c = new CountDownLatch(2);
//构造函数中传入参数作为计数器,如果想要等待N个点完成,就传入N
public static void main(String[] args) throws Exception {
Thread parser1 = new Thread(new Runnable() {
@Override
public void run() {
temp temp = new temp();
temp.a += 1;
c.countDown(); //计数器减一
try {
System.out.println("线程一其余代码被执行");
Thread.sleep(5000L);
System.out.println("线程一执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread parser2 = new Thread(new Runnable() {
@Override
public void run() {
temp temp = new temp();
c.countDown(); //计数器减二
temp.a *= 100;
try {
System.out.println("线程二其余代码被执行");
Thread.sleep(5000L);
System.out.println("线程二执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
parser1.start();
parser2.start();
c.await(); //计数器等于0时,不再阻塞当前线程
System.out.println(new temp().a);
}
//执行结果
线程二其余代码被执行
线程一其余代码被执行
100
(。。。。经历漫长的等待之后。。。。)
线程二执行结束
线程一执行结束
网友评论