线程间通信:
wait、notify、notifyAll
使用场景:在多线程环境下,有时候一个线程 的执行,依赖于另外一个线程的某种状态的改变,这个时候就可以使用wait与notify或者notifyAll
wait和sleep的区别:
wait会释放持有的锁,而sleep不会,sleep只是让线程在指定的时间内不去抢占CPU资源。
注意:
wait和notify必须放在同步代码块中,切必须拥有当前对象的锁,即不能取得A对象的锁而调用B对象的wait,哪个对象wait就要调用哪个对象的notify。
notify和notifyAll的区别:
notify随机唤醒一个等待的线程。
notifyAll唤醒所有在该对象上等待的线程。
等待通知经典模型 -生产者消费者:
中间容器:
public class Container {
private int num = 0;
private static final int TOTAL = 20;
public synchronized void produc(){
//如果容器没满则生产并通知消费者消费
//如果满了则生产者等待
if (num < TOTAL){
System.out.println("生产者生产了。。当前还有" + ++num);
notifyAll();
}
else {
try {
System.out.println("库存已满" + num);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consume(){
//若容器不空则消费并通知生产者生产,若空则等待
if (num > 0){
System.out.println("消费者消费了。。当前还有" + --num);
notifyAll();
}
else {
try {
System.out.println("库存为空" + num);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产者:
public class Producer implements Runnable {
private Container container;
public Producer(Container container){
this.container = container;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
container.produc();
}
}
}
消费者:
public class Consumer implements Runnable{
private Container container;
public Consumer(Container container){
this.container = container;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
container.consume();
}
}
}
main函数:
public class Main {
public static void main(String[] args) {
Container container = new Container();
new Thread(new Producer(container)).start();
new Thread(new Producer(container)).start();
new Thread(new Producer(container)).start();
new Thread(new Producer(container)).start();
new Thread(new Consumer(container)).start();
new Thread(new Consumer(container)).start();
}
}
结果:
生产者生产了。。当前还有1
消费者消费了。。当前还有0
生产者生产了。。当前还有1
生产者生产了。。当前还有2
生产者生产了。。当前还有3
消费者消费了。。当前还有2
生产者生产了。。当前还有3
消费者消费了。。当前还有2
生产者生产了。。当前还有3
生产者生产了。。当前还有4
生产者生产了。。当前还有5
消费者消费了。。当前还有4
使用管道流进行通信:
以内存为媒介用于线程之间的数据传输。
主要有面向字节:PipedOutputStream、PipedInputStream
面向字符:PipedReader、PipedWriter
实例:
Reader.java
public class Reader implements Runnable {
private PipedInputStream pipedInputStream;
public Reader(PipedInputStream pipedInputStream){
this.pipedInputStream = pipedInputStream;
}
@Override
public void run() {
if (pipedInputStream != null){
System.out.println("进入run");
String collect = new BufferedReader(new InputStreamReader(pipedInputStream))
.lines().collect(Collectors.joining("\n"));
System.out.println(collect);
}
try {
pipedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
main.java
public class main {
public static void main(String[] args) throws IOException {
PipedInputStream pipedInputStream = new PipedInputStream();
PipedOutputStream pipedOutputStream = new PipedOutputStream();
pipedOutputStream.connect(pipedInputStream);
new Thread(new Reader(pipedInputStream)).start();
BufferedReader bufferedReader = null;
try{
bufferedReader = new BufferedReader(new InputStreamReader(System.in));
pipedOutputStream .write(bufferedReader.readLine().getBytes());
}finally {
pipedOutputStream.close();
if(bufferedReader != null){
bufferedReader.close();
}
}
}
}
Thread.join通信:
使用场景:
线程A执行到一半,需要一个数据,这个数据需要线程B去执行修改,只有B修改完成之后A才能继续操作。
线程A的run方法里面,调用线程B的join方法,这时线程A会等待线程B运行完成之后再接着运行。
public class Testjoin {
public static void main(String[] args) {
Thread thread = new Thread(()->{
System.out.println(Thread.currentThread().getName() + "运行了");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束了");
}, "线程1");
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "运行了");
thread.start();
try {
thread.join(); //等待线程1运行结束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束了");
},"线程2").start();
}
}
结果:
线程2运行了
线程1运行了
线程1运行结束了
线程2运行结束了
ThreadLocal的使用:
ThreadLocal 用一种存储变量与线程绑定的方式,在每个线程中用自己的 ThreadLocalMap 安全隔离变量,为解决多线程程序的并发问题提供了一种新的思路,如为每个线程创建一个独立的数据库连接。因为是线程绑定的,所以在很多场景也被用来实现线程参数传递,如 Spring 的 RequestContextHolder。也因为每个线程拥有自己唯一的 ThreadLocalMap ,所以 ThreadLocalMap 是天然线程安全的。
常用方法:
ThreadLocal.get:获取ThreadLocal当前线程共享变量的值。
ThreadLocal.set:设置ThreadLocal中当前线程共享变量的值。
ThreadLocal.remove:移除ThreadLocal中当前线程共享变量的值。
ThreadLocal.initialValue:原始值。
实例:
public class Testthreadlocal {
ThreadLocal <Integer> num = ThreadLocal.withInitial(() -> 0);//初始化为0
public void inCrease(){
Integer myNum = num.get();
myNum++;
System.out.println(Thread.currentThread().getName() + "-------" + myNum);
num.set(myNum);
}
public static void main(String[] args) {
Testthreadlocal testthreadlocal = new Testthreadlocal();
for (int i = 1;i < 3; i++){
int j = i;
new Thread(()->{
while (true){
testthreadlocal.inCrease();
try {
Thread.sleep(j*1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
结果证明ThreadLocal是与线程绑定的存储:
Thread-1-------1
Thread-0-------1
Thread-0-------2
Thread-1-------2
Thread-0-------3
Thread-0-------4
Thread-1-------3
Thread-0-------5
详见博客:https://blog.csdn.net/sdfgtr/article/details/90032912
Condition的使用:
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。
awaitUninterruptibly()方法与await()方法基本相同,但awaitUninterruptibly()方法不会在等待过程中响应中断。
singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obejct.notify()方法类似。
condition.await()方法必须在lock.lock()与lock.unlock()方法之间调用。
实例:缓冲队列的实现
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class BoundedBuffer {
final Lock lock = new ReentrantLock();// 锁对象
final Condition notFull = lock.newCondition(); //写线程条件
final Condition notEmpty = lock.newCondition();//读线程条件
final Object[] items = new Object[100];// 初始化一个长度为100的队列
int putptr/* 写索引 */, takeptr/* 读索引 */, count/* 队列中存在的数据个数 */;
public void put(Object x) throws InterruptedException {
lock.lock(); //获取锁
try {
while (count == items.length)
notFull.await();// 当计数器count等于队列的长度时,不能再插入,因此等待。阻塞写线程。
items[putptr] = x;//赋值
putptr++;
if (putptr == items.length)
putptr = 0;// 若写索引写到队列的最后一个位置了,将putptr置为0。
count++; // 每放入一个对象就将计数器加1。
notEmpty.signal(); // 一旦插入就唤醒取数据线程。
} finally {
lock.unlock(); // 最后释放锁
}
}
public Object take() throws InterruptedException {
lock.lock(); // 获取锁
try {
while (count == 0)
notEmpty.await(); // 如果计数器等于0则等待,即阻塞读线程。
Object x = items[takeptr]; // 取值
takeptr++;
if (takeptr == items.length)
takeptr = 0; //若读锁应读到了队列的最后一个位置了,则读锁应置为0;即当takeptr达到队列长度时,从零开始取
count++; // 每取一个将计数器减1。
notFull.signal(); //枚取走一个就唤醒存线程。
return x;
} finally {
lock.unlock();// 释放锁
}
}
}
此即Condition的强大之处,假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程,那么假设只有一个Condition会有什么效果呢,缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这时又去唤醒,这样就浪费了很多时间。
例:经典问题:三个线程依次打印ABC,代码示例如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Business {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();
private String type = "A"; //内部状态
/*
* 方法的基本要求为:
* 1、该方法必须为原子的。
* 2、当前状态必须满足条件。若不满足,则等待;满足,则执行业务代码。
* 3、业务执行完毕后,修改状态,并唤醒指定条件下的线程。
*/
public void printA() {
lock.lock(); //锁,保证了线程安全。
try {
while (type != "A") { //type不为A,
try {
conditionA.await(); //将当前线程阻塞于conditionA对象上,将被阻塞。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//type为A,则执行。
System.out.println(Thread.currentThread().getName() + " 正在打印A");
type = "B"; //将type设置为B。
conditionB.signal(); //唤醒在等待conditionB对象上的一个线程。将信号传递出去。
} finally {
lock.unlock(); //解锁
}
}
public void printB() {
lock.lock(); //锁
try {
while (type != "B") { //type不为B,
try {
conditionB.await(); //将当前线程阻塞于conditionB对象上,将被阻塞。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//type为B,则执行。
System.out.println(Thread.currentThread().getName() + " 正在打印B");
type = "C"; //将type设置为C。
conditionC.signal(); //唤醒在等待conditionC对象上的一个线程。将信号传递出去。
} finally {
lock.unlock(); //解锁
}
}
public void printC() {
lock.lock(); //锁
try {
while (type != "C") {
try {
conditionC.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 正在打印C");
type = "A";
conditionA.signal();
} finally {
lock.unlock(); //解锁
}
}
}
public class Test{
public static void main(String[] args) {
final Business business = new Business();//业务对象。
//线程1号,打印10次A。
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
business.printA();
}
}
});
//线程2号,打印10次B。
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
business.printB();
}
}
});
//线程3号,打印10次C。
Thread tc = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
business.printC();
}
}
});
//执行3条线程。
ta.start();
tb.start();
tc.start();
}
}