合理的使用Java多线程可以更好地利用服务器资源。一般来讲,线程内部有自己私有的线程上下文,互不干扰。但是当我们需要多个线程之间相互协作的时候,就需要我们掌握Java线程的通信方式。本文将介绍Java线程之间的几种通信原理。
5.1 锁与同步
在Java中,锁的概念都是基于对象的,所以我们又经常称它为对象锁。线程和锁的关系,我们可以用婚姻关系来理解。一个锁同一时间只能被一个线程持有。也就是说,一个锁如果和一个线程“结婚”(持有),那其他线程如果需要得到这个锁,就得等这个线程和这个锁“离婚”(释放)。
在我们的线程之间,有一个同步的概念。什么是同步呢,假如我们现在有2位正在抄暑假作业答案的同学:线程A和线程B。当他们正在抄的时候,老师突然来修改了一些答案,可能A和B最后写出的暑假作业就不一样。我们为了A,B能写出2本相同的暑假作业,我们就需要让老师先修改答案,然后A,B同学再抄。或者A,B同学先抄完,老师再修改答案。这就是线程A,线程B的线程同步。
可以以解释为:线程同步是线程之间按照一定的顺序执行。为了达到线程同步,我们可以使用锁来实现它。
一个无锁的程序
public class Communication {
public static void main(String[] args){
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
}
public static class ThreadA extends Thread{
@Override
public void run() {
for(int i = 0; i < 10000; i++){
System.out.println("A");
}
}
}
public static class ThreadB extends Thread{
@Override
public void run() {
for(int i = 0; i < 10000; i++){
System.out.println("B");
}
}
}
}
输出:A、B为乱序的。
那我现在有一个需求,我想等A先执行完之后,再由B去执行,怎么办呢?最简单的方式就是使用一个“对象锁”:
public class Communication {
private static Object lock = new Object();
public static void main(String[] args){
new Thread(new ThreadA()).start();//谁先拿到锁,执行谁。
new Thread(new ThreadB()).start();
}
public static class ThreadA implements Runnable{
@Override
public void run() {
synchronized (lock){
for(int i = 0; i < 100000; i++){
System.out.println("A");
}
}
}
}
public static class ThreadB implements Runnable{
@Override
public void run() {
synchronized (lock){
for(int i = 0; i < 100000; i++){
System.out.println("B");
}
}
}
}
}
这里声明了一个名字为lock的对象锁。我们在ThreadA和ThreadB内需要同步的代码块里,都是用synchronized关键字加上了同一个对象锁lock。
上文我们说到了,根据线程和锁的关系,同一时间只有一个线程持有一个锁,那么线程就会等另一个线程执行完成后释放lock,才能获得锁lock。
5.2 等待/通知机制
上面一种基于“锁”的方式,线程需要不断地去尝试获得锁,如果失败了,再继续尝试。这可能会耗费服务器资源。
而等待/通知机制是另一种方式。
下面实现了交替执行,互相通知。
public class Communication {
private static Object lock = new Object();
public static void main(String[] args){
new Thread(new ThreadA()).start();//交替执行,互相通知
new Thread(new ThreadB()).start();
}
public static class ThreadA implements Runnable{
@Override
public void run() {
synchronized (lock){
for(int i = 0; i < 100000; i++){
System.out.println("A");
lock.notify();
try {
lock.wait();//
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();//通知wait
}
}
}
public static class ThreadB implements Runnable{
@Override
public void run() {
synchronized (lock){
for(int i = 0; i < 100000; i++){
System.out.println("B");
lock.notify();
try {
lock.wait();//
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();//通知wait
}
}
}
}
在这个Demo里,线程A和线程B首先打印出自己需要的东西,然后使用notify()方法叫醒另一个正在等待的线程,然后自己使用wait()方法陷入等待并释放lock锁。