Java多线程学习笔记(三)

线程间通信

线程是操作系统中独立的个体,但这些个体如果不经过特殊处理就不能成为一个整体。在线程间进行通信后,系统之间的交互性会更加强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督。

等待/通知机制

wait():使当前执行代码的线程进行等待,该方法是Object类的方法,可以将当前线程置入”预执行队列”中,并且在wait()所在的代码行停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,也就是说只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放对象锁。如果调用wait()时没有持有适当的锁,则抛出IllagalMonitorStateException,这是RuntimeException的一个子类,不需要try-catch语句进行捕捉。

notify()/notifyAll():在调用notify()之前,线程也必须获得该对象的对象级别锁,也就是说要在同步方法或同步块中调用。同样,如果调用notify()时没有持有适当的锁,也会抛出IllagalMonitorStateException。notify()方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出一个呈wait状态的线程,对其发出notify,并使得它等待获取该对象的对象锁。但是需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码后,当前线程才会释放锁。总而言之,notify()方法随机唤醒等待队列中的等待同一共享资源(同一对象锁)的线程,进入可运行状态。如果发出notify操作时没有处于阻塞状态中的线程,那么该命令可以忽略。

关键字synchronized可以将任何一个Object对象作为同步对象来看待,而Java为每个Object都实现了wait()和notify()方法,它们必须用在被synchronized同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify操作可以唤醒一个因调用了wait操作而处于等待状态的线程,使其进入就绪状态,重新试图获得对象锁,等待CPU的调用,执行wait()方法之后的代码。

我们需要注意的是,sleep()方法是不释放对象锁的。同时,每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。

总结一下,对象锁何时会被释放:

  • 执行完同步代码块
  • 在执行同步代码块的过程中,遇到异常而导致线程终止
  • 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进行对象的等待池

这里介绍一个结束线程的方法:对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。那么,如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。

多线程实例代码:通过管道进行线程之间数据通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public class PipeInputOutputTest {
public static void main(String[] args) {
try {
WriteData writeData = new WriteData();
ReadData readData = new ReadData();
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
//将输入管道和输出管道连接起来
outputStream.connect(inputStream);
//注意:首先读取线程启动,由于当时没有数据写入,会有I/O阻塞,直到数据被写入,才继续运行下去
ThreadRead threadRead = new ThreadRead(readData, inputStream);
threadRead.start();
Thread.sleep(1000);
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
threadWrite.start();
} catch (IOException e) {
e.printStackTrace();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class WriteData{
public void writeMethod(PipedOutputStream outputStream) {
try {
System.out.println("write:");
for (int i = 0; i < 10; i++) {
String outData = "" + i;
outputStream.write(outData.getBytes());
System.out.print(outData);
}
System.out.println();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ReadData{
public void readMethod(PipedInputStream inputStream){
try {
System.out.println("read:");
byte[] bytes = new byte[2];
int readLength = inputStream.read(bytes);
while (readLength != -1) {
String readData = new String(bytes, 0, readLength);
System.out.print(" read : "+readData);
readLength = inputStream.read(bytes);
}
System.out.println();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ThreadWrite extends Thread {
private WriteData writeData;
private PipedOutputStream outputStream;
ThreadWrite(WriteData writeData, PipedOutputStream outputStream) {
this.writeData = writeData;
this.outputStream = outputStream;
}
@Override
public void run() {
super.run();
writeData.writeMethod(outputStream);
}
}
class ThreadRead extends Thread {
private ReadData readData;
private PipedInputStream inputStream;
public ThreadRead(ReadData readData, PipedInputStream inputStream) {
this.readData = readData;
this.inputStream = inputStream;
}
@Override
public void run() {
super.run();
readData.readMethod(inputStream);
}
}

方法join的使用

在很多情况下,主线程启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。join()方法的作用是等待对象销毁。可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的join()方法,直到线程A执行完毕后,才会继续执行线程B。join()方法内部实现是通过调用wait()方法实现的,比如当main线程调用t.join时候,main线程会获得线程对象t的锁(wait意味着拿到该对象的锁),调用该对象的wait(等待时间)。这就意味着main线程调用t.join时,必须能够拿到线程t对象的锁。同时,在调用join()方法的时候也会释放对象锁。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Test {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
thread.join();
System.out.println("在thread对象执行完之后再执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThread extends Thread {
@Override
public void run() {
super.run();
try {
int secondValue = (int) (Math.random() * 1000);
System.out.println(secondValue);
Thread.sleep(secondValue);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

类ThreadLocal的使用

类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据。

类Threadlocal解决的是变量在不同线程间的隔离性,也就是不同线程拥有自己的值,不同线程中的值是可以放入Threadlocal类中进行保存的。

0%