本篇文章转载自:Java多线程基础-使用多线程(三) - 简书
|-目录
| 线程间通讯
-|wait与notify方式
-|等待(join)方式
-|管道(pipeStream)方式
| -线程共享变量【ThreadLocal】
-线程间通讯
1.线程间通讯-wait与notify方式
线程间通讯:调用wait方法前,首先获得wait对象的锁。执行wait方法后,当前线程释放锁,并且状态变更为Waiting状态,当前任务加入至“预处理队列”中;调用notify方法前,首先获得notify对象的锁。执行完notify synchronized同步代码块后,当前线程释放锁,如果该线程没有任务则该线程状态变更为Terminated状态。
1个实例中的多个synchronized方法使用的是同一把锁
图1-1 线程通讯
public class ThreadWaitNotifyStateDemo {
public static void main(String[] args) throws InterruptedException{
final ServiceWaitNotify service = new ServiceWaitNotify();
Thread thread_1 = new Thread("thread_1") {
public void run() {
try {
service.testMethodA();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
};
Thread thread_2 = new Thread("thread_2") {
public void run() {
try {
service.testMethodB();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
};
thread_1.start();
thread_2.start();
Thread.sleep(1100);
System.out.println(thread_1.getName() + ",state:" + thread_1.getState());
Thread.sleep(500);
System.out.println(thread_1.getName() + ",state:" + thread_1.getState());
}
}
class ServiceWaitNotify {
public void testMethodA() throws InterruptedException {
Thread.sleep(1000);
synchronized (this) {
System.out.println(Thread.currentThread().getName() + ",进入Wait!" + Thread.currentThread().getState());
this.wait();
System.out.println(Thread.currentThread().getName() + ",退出Wait!" + Thread.currentThread().getState());
}
}
public void testMethodB() throws InterruptedException {
Thread.sleep(1500);
synchronized (this) {
System.out.println(Thread.currentThread().getName() + ",进入notify!" + Thread.currentThread().getState());
this.notify();
System.out.println(Thread.currentThread().getName() + ",退出notify!" + Thread.currentThread().getState());
}
}
}
图1-2 线程通讯
以上代码:介绍了wait与notify的简单使用,使用这两个方法时必须有同种锁的同步代码块。同时说明了线程执行相关方法时,线程所处状态。如【图1-2】可以看出当线程执行wait方法后所处状态为WATING,当notify线程同步代码块执行完后,wait线程进入RUNNABLE状态,等待wait线程任务执行完后进入TERMINATED状态。
补充1个测试案例,说明两个方法用的同一把锁
public void testMethodB() throws InterruptedException {
Thread.sleep(1500);
synchronized (this) {
System.out.println(Thread.currentThread().getName() + ",进入notify!" + Thread.currentThread().getState());
this.notify();
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + ",退出notify!" + Thread.currentThread().getState());
}
}
2.线程间通讯-等待(join)方式
一个线程通过等待的方式,获取另一个线程数据执行的结果,就是说thread.join()要先执行完才能执行后面的
public class ThreadJoinDemo {
public static void main(String[] args) {
try {
JoinThread thread = new JoinThread("thread_1");
thread.start();
thread.join();
System.out.println("" + Thread.currentThread().getName() + ",获得["
+ thread.getName() + "],value:" + thread.value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class JoinThread extends Thread {
public int value;
public JoinThread (String name) {
super(name);
}
@Override
public void run() {
System.out.println("" + Thread.currentThread().getName() + ",开始运行!");
try {
Thread.sleep(1000);
value = 5;
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("" + Thread.currentThread().getName() + ",结束运行!");
}
}
图1-3 线程通讯
从【图1-3】可以看出,join方法是等待子线程全部执行完后,主线程才往下执行,这样主线程就能获取子线程修改后的值。
这里要产生一个怀疑点,join方法的柱塞方式是否与wait方法相似?
能够产生上述怀疑的有两点:
1)join方法会throws InterruptedException【InterruptedException说明
:线程在活动前或活动中,处于等待或睡眠状态时,该线程被异常中断则报上述异常。】,故该异常必定与sleep或wait方法有关联。
2)join方法等待属于不确定的等待,故join底层不可能使用sleep。
验证:
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;
}
}
}
从上面源码可以看出,join底层就是使用wait方法,并且wait方法使用的锁是线程对象本身。【这样可以验证调用join方法是否与wait方法一样会释放当前锁对象】
public class ThreadJoinDemo {
public static void main(String[] args) throws InterruptedException{
JoinThread thread = new JoinThread("thread_1");
synchronized (thread) {
System.out.println("" + Thread.currentThread().getName() + ",获得thread锁!");
thread.start();
Thread.sleep(1000);
System.out.println("" + Thread.currentThread().getName() + ",调用thread.join()");
thread.join();
System.out.println("" + Thread.currentThread().getName() + ",结束!");
}
}
}
class JoinThread extends Thread {
public int value;
public JoinThread (String name) {
super(name);
}
@Override
public void run() {
synchronized (this) {
System.out.println("" + Thread.currentThread().getName() + ",开始运行!");
value = 5;
}
System.out.println("" + Thread.currentThread().getName() + ",结束运行!");
}
}
图1-4 线程通讯
以上代码可以看出,如果join不能释放锁,则JoinThread run方法获取不了锁也执行不了方法体中程序。但是【图1-4】完全推翻了上述‘如果假设’,证明了join底层就是wait,调用join方法与wait方法一样会释放当前锁。
3.线程间通讯-管道(pipeStream)方式
两个线程不借助文本,使用管道【输出管道-写数据,输入管道-读数据】方式直接进行数据传输。
管道通讯两种方式:
1)PipedInputStream
和PipedOutputStream
2)PipedReader
和PipedWriter
管道通讯使用注意点:
1)管道通讯必须建立连接。src.connect(snk)/snk.connect(src)【src:未连接的管道输出流,snk:未链接的管道输入流】。
2)PipedInputStream和PipedOutputStream不能在同一个线程使用。【会出现死锁现象】
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class ThreadPipeStreamDemo {
public static void main(String[] args) throws IOException {
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
out.connect(in);
ThreadWrite write = new ThreadWrite("write_thread", out);
ThreadRead read = new ThreadRead("read_thread", in);
write.start();
read.start();
}
}
class ThreadWrite extends Thread {
private PipedOutputStream outputStream;
public ThreadWrite (String name, PipedOutputStream out) {
super(name);
this.outputStream = out;
}
@Override
public void run() {
if (null == outputStream) return;
try {
System.out.println("" + Thread.currentThread().getName() + ",开始写入数据!");
for(int i = 0; i < 10; i++) {
String writeData = "" + (i + 1);
outputStream.write(writeData.getBytes(), 0 ,writeData.getBytes().length);
}
outputStream.flush();
outputStream.close();
System.out.println("" + Thread.currentThread().getName() + ",完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ThreadRead extends Thread {
private PipedInputStream inputStream;
public ThreadRead (String name, PipedInputStream in) {
super(name);
this.inputStream = in;
}
@Override
public void run() {
if (null == inputStream) return;
byte[] buffer = new byte[1048];
System.out.println("" + Thread.currentThread().getName() + ",开始读数据!");
String data = "";
try {
int length = inputStream.read(buffer, 0, buffer.length);
while (length != -1) {
String str = new String(buffer, 0, length);
data += str;
length = inputStream.read(buffer, 0, buffer.length);
}
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("" + Thread.currentThread().getName() + ",数据:" + data);
}
}
图1-5 线程通讯
-线程共享变量【ThreadLocal】
ThreadLocal类提供了线程局部(thread-local)变量,ThreadLocal与普通变量的区别在于:1,每个线程持有的是变量初始化副本的弱引用【直译:每个线程都有自己的局部变量】。2,ThreadLocal与线程生命周期相关联【线程消失之后,其线程局部实例的所有副本都会被垃圾回收】
public class ThreadLocalDemo {
public static void main(String[] args) {
final Tools tools = new Tools();
Thread thread_1 = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(5000); //确保getUid在线程2执行完后面执行
int num = tools.getUid();
System.out.println("" + Thread.currentThread().getName() + ",num:" + num);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "thread_1");
Thread thread_2 = new Thread(new Runnable() {
public void run() {
tools.setUid(10);
int num = tools.getUid();
System.out.println("" + Thread.currentThread().getName() + ",num:" + num);
}
}, "thread_2");
thread_1.start();
thread_2.start();
}
}
class Tools {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {//ThreadLocal 获取初始默认值
return 0;
};
};
public int getUid() {
return threadLocal.get().intValue(); //ThreadLocal get()方法获取当前变量值
}
public synchronized void setUid(int num) { //强迫同步观察threadLocal是否能根据线程区分变量
threadLocal.set(num); //ThreadLocal set()方法设置当前变量值
}
}
图1-5 线程共享变量
从结果可以看出,同一个ThreadLocal静态变量但是获取出来的值不同,这就解释了上面局部变量的定义。
-总结
这里主要讲解线程间通讯,以及线程共享变量简单使用