线程通信
一、线程通信的概念
在操作系统中每个线程都是一个独立的个体,线程通信就是将这些线程个体常量起来的,只有具备了线程通信机制,线程才更有意义。线程通信不仅提高了CPU的利用率,还为程序开发提供了线程过程的监督与把控。
二、等待/通知机制概述
wait()方法是Object类的方法,它能使当前执行的线程进行等待,将线程置入“与执行队列”中,代码在wait()方法所在的行停止。
notify()方法需要在同步方法或者同步代码块中进行调用,线程必须获得对象级别的锁,notify()方法用于通知处于等待状态的线程继续执行。notify()方法执行后,不会马上释放锁,需要等待notify()方法所在的线程执行完synchronized代码块后才会释放锁。
notifyAll()方法使处于等待的线程进入可运行状态,但是哪个线程获得执行需要看CPU分配资源的结果。
1、wait()/notify()方法
线程类
public class WaitAndNontifyTreadA extends Thread{
private Object lock;
public WaitAndNontifyTreadA(Object lock) {
this.lock = lock;
}
@Override
public void run() {
super.run();
synchronized(lock){
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("线程:" + Thread.currentThread().getName() + ",开始执行解锁方法," + "时间:" + dateformat.format(System.currentTimeMillis()));
lock.notify();
System.out.println("线程:" + Thread.currentThread().getName() + ",开始sleep," + "时间:" + dateformat.format(System.currentTimeMillis()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + ",解锁结束," + "时间:" + dateformat.format(System.currentTimeMillis()));
}
}
}
public class WaitAndNotifyThreadB extends Thread {
private Object lock;
public WaitAndNotifyThreadB(Object lock) {
this.lock = lock;
}
@Override
public void run() {
super.run();
synchronized(lock){
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
System.out.println("线程:" + Thread.currentThread().getName() + ",开始执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
lock.wait();
System.out.println("线程:" + Thread.currentThread().getName() + ",继续执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类
@SpringBootTest
public class WaitAndNotifyTest {
@Test
public static void main(String[] args) {
Object lock = new Object();
WaitAndNontifyTreadA threadA = new WaitAndNontifyTreadA(lock);
threadA.setName("threadA");
WaitAndNotifyThreadB threadB = new WaitAndNotifyThreadB(lock);
threadB.setName("threadB");
threadB.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadA.start();
}
}
控制台输出结果
线程:threadB,开始执行,时间:2020-08-31 19:24:15。
线程:threadA,开始执行解锁方法,时间:2020-08-31 19:24:18
线程:threadA,开始sleep,时间:2020-08-31 19:24:18
线程:threadA,解锁结束,时间:2020-08-31 19:24:20
线程:threadB,继续执行,时间:2020-08-31 19:24:20。
从控制台输出结果可知,ThreadA解锁后,即退出synchronized代码块后ThreadB才继续执行wait()后面的代码。
2、验证wait()释放锁
service类
public class WaitReleaseLockService {
public void testMethod(Object lock){
synchronized(lock){
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("线程:" + Thread.currentThread().getName() + ",开始执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + ",结束执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
}
}
}
线程类
public class WaitReleaseLockThread extends Thread{
private Object lock;
public WaitReleaseLockThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
super.run();
WaitReleaseLockService service = new WaitReleaseLockService();
service.testMethod(lock);
}
}
测试类
@SpringBootTest
public class WaitReleaseThreadTest {
@Test
public static void main(String[] args) {
Object lock = new Object();
WaitReleaseLockThread threadA = new WaitReleaseLockThread(lock);
threadA.setName("threadA");
threadA.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
WaitReleaseLockThread threadB = new WaitReleaseLockThread(lock);
threadB.setName("threadB");
threadB.start();
}
}
控制台输出结果
线程:threadA,开始执行,时间:2020-08-31 20:17:33。
线程:threadB,开始执行,时间:2020-08-31 20:17:34。
结论:从控制台的输出结果可以看出,当threadA线程wait后,thtreadB线程还能获得锁,并执行同步方法。因此,可知threadA线程执行了wait()方法后释放了锁。
拓展:当把上面的例子中的线程类的wait方法该为sleep方法,则两个线程同步执行。因此,threadB线程被阻塞。
3、notify()与notifyAll()
notify()方法一次只能随机唤醒一个线程,notifyAll()方法能唤醒所有线程。
线程类
public class NotifyService {
public void method(Object lock){
synchronized(lock){
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("线程:" + Thread.currentThread().getName() + ",开始执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + ",结束执行," + "时间:" + dateformat.format(System.currentTimeMillis()) + "。");
}
}
public void notifyMethod(Object lock){
synchronized(lock){
lock.notify();
// lock.notifyAll();
}
}
}
测试类
@SpringBootTest
public class NotifyTest {
@Test
public static void main(String[] args) {
Object lock = new Object();
NotifyService service = new NotifyService();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
service.method(lock);
}
},"threadA");
threadA.start();
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
service.method(lock);
}
},"threadB");
threadB.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread notifyThread = new Thread(new Runnable() {
@Override
public void run() {
service.notifyMethod(lock);
}
},"notifyThread");
notifyThread.start();
}
}
控制台输出结果
// notif()输出结果
线程:threadA,开始执行,时间:2020-08-31 21:04:29。
线程:threadB,开始执行,时间:2020-08-31 21:04:29。
// notifAll()输出结果
线程:threadA,开始执行,时间:2020-08-31 20:58:49。
线程:threadB,开始执行,时间:2020-08-31 20:58:49。
线程:threadB,结束执行,时间:2020-08-31 20:58:51。
线程:threadA,结束执行,时间:2020-08-31 20:58:51。
4、wait(long)方法
5、生产者消费者模型
(1)、单生产者单消费者
产品栈类
public class ProduceStack {
private List stackList = new ArrayList();
synchronized public void push(){
while (stackList.size() == 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
stackList.add("push = " + Math.random());
this.notify();
System.out.println("执行进栈后 stackList size = " + stackList.size());
}
synchronized public void pop(){
while (stackList.size() == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
stackList.remove(0);
this.notify();
System.out.println("执行出栈后stackList size = " + stackList.size());
}
}
生产者&消费者线程类
// 生产者线程类
public class ProducerThread extends Thread{
private ProduceStack producerList;
public ProducerThread(ProduceStack producerList) {
this.producerList = producerList;
}
@Override
public void run() {
super.run();
while(true){
producerList.push();
}
}
}
// 消费者线程类
public class ConsumerThread extends Thread {
private ProduceStack producerList;
public ConsumerThread(ProduceStack producerList) {
this.producerList = producerList;
}
@Override
public void run() {
super.run();
while(true){
producerList.pop();
}
}
}
测试累
@SpringBootTest
public class ProducerAndConsumerTest {
@Test
public static void main(String[] args) {
ProduceStack producerList = new ProduceStack();
ProducerThread producerThread = new ProducerThread(producerList);
ConsumerThread consumerThread = new ConsumerThread(producerList);
producerThread.setName("producerThread");
consumerThread.setName("consumerThread");
producerThread.start();
consumerThread.start();
}
}
控制台输出结果
执行进栈后 stackList size = 1
执行出栈后stackList size = 0
执行进栈后 stackList size = 1
执行出栈后stackList size = 0
执行进栈后 stackList size = 1
执行出栈后stackList size = 0
执行进栈后 stackList size = 1
执行出栈后stackList size = 0
执行进栈后 stackList size = 1
...
(2)、线程假死
在多生产者,多消费者模式下,容易出现线程假死的情况。线程假死是因为生产者和消费者的线程都处于WAITING状态中,程序不再往下执行了。修改上面例子中的测试类验证线程假死。
测试类修改
@SpringBootTest
public class ProducerAndConsumerTest {
@Test
public static void main(String[] args) {
ProduceStack producerList = new ProduceStack();
ProducerThread[] producerThreadList = new ProducerThread[2];
ConsumerThread[] consumerThreadList = new ConsumerThread[2];
for (int i = 0; i < 2 ; i++){
producerThreadList[i] = new ProducerThread(producerList);
producerThreadList[i].setName("生产者" + (i + 1));
consumerThreadList[i] = new ConsumerThread(producerList);
consumerThreadList[i].setName("消费者" + (i + 1));
producerThreadList[i].start();
consumerThreadList[i].start();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread[] threadList = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
Thread.currentThread().getThreadGroup().enumerate(threadList);
for (int i = 0; i < threadList.length; i++){
System.out.println(threadList[i].getName() + " " + threadList[i].getState());
}
}
}
控制台输出结果
...
执行出栈后stackList size = 0
执行进栈后 stackList size = 1
执行出栈后stackList size = 0
执行进栈后 stackList size = 1
执行出栈后stackList size = 0
执行进栈后 stackList size = 1
执行出栈后stackList size = 0
执行进栈后 stackList size = 1
执行出栈后stackList size = 0
执行进栈后 stackList size = 1
main RUNNABLE
Monitor Ctrl-Break RUNNABLE
生产者1 WAITING
消费者1 WAITING
生产者2 WAITING
消费者2 WAITING
造成线程假死的原因是生产者唤醒了同类线程,消费者同样唤醒了同类线程,造成生产者和消费者两边同时处于等待状态。假死是个概率事件,为了消除线程假死的影响,可以使用notifyAll()方法,当一个线程释放锁后,将其他线程全部唤醒。
6、线程通信——管道
管道(pipeStream)是一种特殊的流,用于线程间的通信。一个线程发送数据到管道,另外一个线程从管道中读取数据。
(1)、字节流管道
读写类
public class WriteData {
public void writeMethode(PipedOutputStream out){
try {
System.out.println("write: ");
for (int i = 0; i < 300; i++) {
String outData = "" + (i + 1);
out.write(outData.getBytes());
System.out.print(outData);
}
System.out.println();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class ReadData {
public void readMethod(PipedInputStream input){
try {
System.out.println("read: ");
byte[] byteArray = new byte[200];
int readLenght = input.read(byteArray);
while(readLenght != -1){
String newData = new String(byteArray, 0, readLenght);
System.out.println(newData);
readLenght = input.read(byteArray);
}
System.out.println();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
线程类
public class ThreadWrite extends Thread{
private WriteData write;
private PipedOutputStream out;
public ThreadWrite(WriteData write, PipedOutputStream out) {
this.write = write;
this.out = out;
}
@Override
public void run() {
write.writeMethode(out);
}
}
public class ThreadRead extends Thread{
private ReadData read;
private PipedInputStream input;
public ThreadRead(ReadData read, PipedInputStream input) {
this.read = read;
this.input = input;
}
@Override
public void run() {
read.readMethod(input);
}
}
测试类
@SpringBootTest
public class PipedTest {
@Test
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);
ThreadRead threadRead = new ThreadRead(readData,inputStream);
threadRead.start();
Thread.sleep(2000);
ThreadWrite threadWrite = new ThreadWrite(writeData,outputStream);
threadWrite.start();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
控制台输出结果
read:
write:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545512345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
53545556
5657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
57585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
13813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320
42052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702
71272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
从控制台输出结果可以看出,读取和写入两个线程时异步运行。
三、join()方法
join()方法能让线程排队运行,具有类似同步的效果。join内部使用的是wait()方法,当线程isActive状态时在无限循环中,只有线程销毁后才退出循环继续后续代码的执行。
1、join()和synchronized的区别
synchronized内部使用的时对象监听器。因此,join和synchronized两种的原理不一样。
任务类
public class TaskA {
synchronized public void methodA(){
System.out.println("-------------运行methodA方法---------------");
}
public void methodB(){
synchronized(this){
for (int i = 0; i < 5; i++){
System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + i + ";");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
测试类
@SpringBootTest
public class JoinSynTest {
@Test
public static void main(String[] args) {
try {
TaskA task = new TaskA();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
task.methodB();
}
},"threadA");
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
task.methodB();
}
},"threadB");
threadA.start();
System.out.println("--------线程:" + Thread.currentThread().getName() + ",开始时间" + System.currentTimeMillis() + "---------");
threadA.join(3000);
System.out.println("--------线程:" + Thread.currentThread().getName() + ",结束时间" + System.currentTimeMillis() + "---------");
threadB.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
控制台输出结果
--------线程:main,开始时间1599013870456---------
线程:threadA;i = 0;
线程:threadA;i = 1;
线程:threadA;i = 2;
--------线程:main,结束时间1599013873457---------
线程:threadA;i = 3;
线程:threadA;i = 4;
线程:threadB;i = 0;
线程:threadB;i = 1;
线程:threadB;i = 2;
线程:threadB;i = 3;
线程:threadB;i = 4;
从控制台输出结果可以看出,因为methdB()方法的对象锁是task,join()方法的锁是对象threadA,因此两个线程异步运行。threadA和threadB的对象锁都是task,因此两个线程同步执行。
另外,上面例子还使用了join(long)方法,当线程isAlive将一直等待,即使设置时间到了还是处于等待状态。
join()方法
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
2、join()和sleep()的区别
join内部是实现了wait方法,因此是释放锁的,sleep是不释放锁的。
测试类
@SpringBootTest
public class JoinTest {
@Test
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程:" + Thread.currentThread().getName() + "执行方法A。" + System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"threadA");
threadA.start();
try {
threadA.join();
// Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程:" + Thread.currentThread().getName() + "执行方法B。" + System.currentTimeMillis());
}
}
控制台输出结果
线程:threadA执行方法A。1598966676610
主线程:main执行方法B。1598966678611
四、ThreadLocl类
ThreadLocl类使每个线程都绑定自己的值,主要做的是变量在不同线程间的隔离性。
1、ThreadLocl的隔离性
ThreadLocl具有隔离性,每个线程都能取到自己的私有值。从这个例子的控制台打印结果可以看出,两个线程set和get到了自己私有值。
常量类
public class ConstantUtil {
public static ThreadLocal<String> t1 = new ThreadLocal();
}
测试类
@SpringBootTest
public class ThreadLoclTest {
@Test
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable(){
@Override
public void run() {
try {
for (int i = 0; i < 5; i++){
ConstantUtil.t1.set("线程:" + Thread.currentThread().getName() + ",时间:" + System.currentTimeMillis());
System.out.println(ConstantUtil.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"ThreadA");
Thread threadB = new Thread(new Runnable(){
@Override
public void run() {
try {
for (int i = 0; i < 5; i++){
ConstantUtil.t1.set("线程:" + Thread.currentThread().getName() + ",时间:" + System.currentTimeMillis());
System.out.println(ConstantUtil.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"ThreadB");
threadA.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadB.start();
}
}
控制台输出结果
线程:ThreadA,时间:1599018738241
线程:ThreadB,时间:1599018738340
线程:ThreadA,时间:1599018738442
线程:ThreadB,时间:1599018738541
线程:ThreadA,时间:1599018738642
线程:ThreadB,时间:1599018738741
线程:ThreadB,时间:1599018738942
线程:ThreadB,时间:1599018739142
2、重写initialValue()方法,解决第一次get值为null问题
继承ThreadLocal类
public class InitialThreadLocal extends ThreadLocal{
@Override
protected Object initialValue() {
return "线程:" + Thread.currentThread().getName() + ",时间:" + System.currentTimeMillis();
}
}
测试类
@SpringBootTest
public class InitialValueTest {
@Test
public static void main(String[] args) {
InitialThreadLocal threadLocal = new InitialThreadLocal();
System.out.println(threadLocal.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
}
},"threadA");
threadA.start();
}
}
控制台输出结果
线程:main,时间:1599044771216
线程:threadA,时间:1599044772217
五、InheritableThreadLocal类
InheritableThreadLocal类继承了ThreadLocal类,提供了子线程从父线程取值的方法,子线程可以通过InheritableThreadLocal的get方法取到父线程的值。同时通过重写childValue()方法,子线程也可以再父线程的基础上对数据进行加工。
继承类
public class InheritableThreadLocal1 extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return System.currentTimeMillis();
}
@Override
protected Object childValue(Object parentValue) {
return super.childValue(parentValue) + " i am the child value";
}
}
测试类
@SpringBootTest
public class InitialValueTest {
@Test
public static void main(String[] args) {
InheritableThreadLocal1 it1 = new InheritableThreadLocal1();
System.out.println( "线程:" + Thread.currentThread().getName() + ",时间:" + it1.get());
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println( "线程:" + Thread.currentThread().getName() + ",时间:" + it1.get());
}
},"threadA");
threadA.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( "线程:" + Thread.currentThread().getName() + ",时间:" + it1.get());
}
}
控制台输出结果
线程:main,时间:1599045818241
线程:threadA,时间:1599045818241 i am the child value
线程:main,时间:1599045818241