多线程之间通讯
1. 多线程之间如何实现通讯
什么是多线程之间通讯?
多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同
代码01 多线程发生线程安全问题(数据错乱):/thread03/src/com/mysoft/demo1/GoodsDemo01.java
package com.mysoft.demo1;
/**
*
* @author lpz
* 生产者与消费者同时进行发生线程安全问题(数据错乱)
*/
//创建实体类
class Goods{
private String type;
private String name;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//生产者生产商品
class Produce extends Thread{
public Goods goods;
public Produce(Goods goods) {
this.goods=goods;
}
@Override
public void run() {
int count=0;
while(true) {
if(count==0) {
goods.setType("日用品");
goods.setName("香皂");
}
if(count==1) {
goods.setType("水果");
goods.setName("苹果");
}
count=(count+1)%2;
}
}
}
//消费者消费商品
class Consumer extends Thread{
public Goods goods;
public Consumer(Goods goods) {
this.goods=goods;
}
@Override
public void run() {
while(true) {
System.out.println(goods.getType()+","+goods.getName());
}
}
}
public class GoodsDemo01 {
public static void main(String[] args) {
//创建商品共享对象
Goods goods=new Goods();
//创建两个线程,共享goods
Thread t1=new Thread(new Produce(goods));
Thread t2=new Thread(new Consumer(goods));
t1.start();
t2.start();
}
}
运行结果:
代码02 生产者与消费者案例:/thread03/src/com/mysoft/demo2/GoodsDemo02.java
同步解决了数据错乱问题,没有实现生产1个消费1个
package com.mysoft.demo2;
/**
*
* @author lpz
* 生产者与消费者:同步解决了数据错乱问题,没有实现生产1个消费1个
*/
//创建商品类
class Goods{
private String type;
private String name;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//生产者生产商品
class Produce extends Thread{
public Goods goods;
public Produce(Goods goods) {
this.goods=goods;
}
@Override
public void run() {
int count=0;
while(true) {
//同步解决数据错乱,同步锁必须是共享对象
synchronized (goods) {
if(count==0) {
goods.setType("日用品");
goods.setName("香皂");
}
if(count==1) {
goods.setType("水果");
goods.setName("苹果");
}
count=(count+1)%2;
}
}
}
}
//消费者消费商品
class Consumer extends Thread{
public Goods goods;
public Consumer(Goods goods) {
this.goods=goods;
}
@Override
public void run() {
//同步解决数据错乱,同步锁必须是共享对象
while(true) {
synchronized (goods) {
System.out.println(goods.getType()+","+goods.getName());
}
}
}
}
public class GoodsDemo02 {
public static void main(String[] args) {
//创建商品共享对象
Goods goods=new Goods();
//创建两个线程,共享goods
Thread t1=new Thread(new Produce(goods));
Thread t2=new Thread(new Consumer(goods));
t1.start();
t2.start();
}
}
2. wait()、notify()、notifyAll()区别
wait()、notify()、notifyAll()详解:
1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。
2.wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在
synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。
3、由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。
当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往
下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。
也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码
块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其
他线程让其获得锁
4、wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒。
5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是
无法被唤醒的。
6、notify 和 notifyAll的区别
notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对
象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。
如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使
用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。
7、在多线程中要测试某个条件的变化,使用if 还是while?
要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时
候,可以先把 wait 语句忽略不计来进行考虑;显然,要确保程序一定要执行,并且要保证程序直
到满足一定的条件再执行,要使用while进行等待,直到满足条件才继续往下执行。如下代码:
代码03 多线程之间的通讯:/thread03/src/com/mysoft/demo3/GoodsDemo03.java
要求:生产者线程生产一个,消费者线程立即消费。生产者没有任何生产,消费者不能消费。消费者没有消费完,生产不能再继续生产。
package com.mysoft.demo3;
/**
*
* @author lpz
* 多线程之间通讯
*/
//创建商品类
class Goods{
public String type;
public String name;
//线程通讯标志
public boolean flag=true;
}
//生产者生产商品
class Produce extends Thread{
public Goods goods;
public Produce(Goods goods) {
this.goods=goods;
}
@Override
public void run() {
int count=0;
while(true) {
//同步解决数据错乱,同步锁必须是共享对象
synchronized (goods) {
//flag为false,停止生产,等待消费者消费
if(!goods.flag) {
try {
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(count==0) {
goods.type="日用品";
goods.name="香皂";
}
if(count==1) {
goods.type="水果";
goods.name="苹果";
}
count=(count+1)%2;
goods.flag=false;
goods.notify();
}
}
}
}
//消费者消费商品
class Consumer extends Thread{
public Goods goods;
public Consumer(Goods goods) {
this.goods=goods;
}
@Override
public void run() {
//同步解决数据错乱,同步锁必须是共享对象
while(true) {
synchronized (goods) {
//flag为true时候,停止消费,等待生产者生产
if(goods.flag) {
try {
goods.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(goods.type+","+goods.name);
goods.flag=true;
goods.notify();
}
}
}
}
public class GoodsDemo03 {
public static void main(String[] args) {
//创建商品共享对象
Goods goods=new Goods();
//创建两个线程,共享goods
Thread t1=new Thread(new Produce(goods));
Thread t2=new Thread(new Consumer(goods));
t1.start();
t2.start();
}
}
3. Lock锁(实现多线程之间通讯)
3.1 Lock锁的写法
Lock lock = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操作
}finally{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
lock.ublock();
}
3.2 Lock 接口与 synchronized 关键字的区别
1、Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定。但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中;
2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3、Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使
用synchronized时,等待的线程会一直等待下去,不能够响应中断
4、通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5、Lock可以提高多个线程进行读操作的效率。
3.3 Condition用法
//创建condition
Condition condition = lock.newCondition();
//当前线程等待
condition.await(); //类似synchronized中的wait()
//唤醒某一个使用lock锁正在等待的线程
condition. Signal() //类似synchronized中的notify()
代码04 lock实现多线程之间的通讯:/thread03/src/com/mysoft/demo4/GoodsDemo04.java
package com.mysoft.demo4;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author lpz
* lock实现多线程之间的通讯
*/
//商品类
class Goods{
public String type;
public String name;
//线程通讯标志
public boolean flag=false;
//锁对象
Lock lock=new ReentrantLock();
}
//生产者
class Produce extends Thread{
Goods goods;
Condition condition;
public Produce(Goods goods,Condition condition) {
this.goods=goods;
this.condition=condition;
}
@Override
public void run() {
int count=0;
while(true) {
//上锁lock
goods.lock.lock();
try {
if(!goods.flag) {
try {
//当前线程等待,并且释放锁
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(count==0) {
goods.type="日用品";
goods.name="香皂";
}
if(count==1) {
goods.type="水果";
goods.name="苹果";
}
//奇数偶数之间相互转换
count=(count+1)%2;
goods.flag=false;
//唤醒某一个使用lock锁正在等待的线程
condition.signal();
} finally {
goods.lock.unlock();
}
}
}
}
class Consumer extends Thread{
Goods goods;
Condition condition;
public Consumer(Goods goods,Condition condition) {
this.goods=goods;
this.condition=condition;
}
@Override
public void run() {
while (true) {
//上锁lock
goods.lock.lock();
try {
if(goods.flag) {
try {
//当前线程等待,并且释放锁
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(goods.type+","+goods.name);
goods.flag=true;
condition.signal();
} finally {
//唤醒某一个使用lock锁正在等待的线程
goods.lock.unlock();
}
}
}
}
public class GoodsDemo04 {
public static void main(String[] args) {
//实现同步和通讯,必须保证lock锁和Condition对象都是唯一的
Goods goods=new Goods();
Condition condition=goods.lock.newCondition();
Thread t1=new Thread(new Produce(goods,condition));
Thread t2=new Thread(new Consumer(goods,condition));
t1.start();
t2.start();
}
}
4. 怎么停止线程
停止线程的3种方法
1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2、使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
3、使用interrupt方法中断线程
代码05 使用退出标志终止线程:/thread03/src/com/mysoft/demo5/StopThreadDemo01.java
注意:
1、如果主线程修改flag的值后,立即休眠1000ms,那么jmm将不能保证主线程与子线程之间的可见性。
2、使用volatile修饰共享变量flag,可以保证主线程和子线程之间的可见性
package com.mysoft.demo5;
/**
* @author lpz
* 停止线程的方法:
* 1、如果主线程修改flag的值后,立即休眠1000ms,那么jmm将不能保证主线程与子线程之间的可见性。
* 子线程无法获取最新的flag的值,而无法停掉
* 2、使用volatile修饰共享变量flag,可以保证主线程和子线程之间的可见性
*/
class Thread01 extends Thread{
private volatile boolean flag=true;
//volatile保证可见性
//private volatile boolean flag=true;
@Override
public void run() {
synchronized (this) {
System.out.println("子线程开始执行.....");
while(flag) {
}
System.out.println("子线程执行结束.....");
}
}
public void stopThread() {
this.flag=false;
}
}
public class StopThreadDemo01 {
public static void main(String[] args) throws InterruptedException {
Thread01 thread=new Thread01();
Thread t1=new Thread(thread);
t1.start();
for (int i = 0; i < 10; i++) {
//主线程修改flag后立即休眠,会影响jvm的可见性。所以子线程没有停掉
Thread.sleep(1000);
System.out.println("主线程"+i);
if(i==5) {
//方法1.调用stopThread方法停止线程
thread.stopThread();
}
}
}
}
代码06 Thread类的stop()方法终止线程:/thread03/src/com/mysoft/demo5/StopThreadDemo02.java
package com.mysoft.demo5;
/**
* @author lpz
* 停止线程的方法:使用Thread类的stop()方法,不介意使用
*/
class Thread02 extends Thread{
@Override
public void run() {
synchronized (this) {
System.out.println("子线程开始执行.....");
while(true) {
}
//System.out.println("子线程执行结束.....");
}
}
}
public class StopThreadDemo02 {
@SuppressWarnings("deprecation")
public static void main(String[] args) throws InterruptedException {
Thread02 thread=new Thread02();
Thread t1=new Thread(thread);
t1.start();
for (int i = 0; i < 10; i++) {
//主线程修改flag后立即休眠,会影响jvm的可见性。所以子线程没有停掉
Thread.sleep(1000);
System.out.println("主线程"+i);
if(i==5) {
//方法2 使用Thread类的stop()方法。
t1.stop();
}
}
}
}
代码06 Thread类interrupt()方法终止程:/thread03/src/com/mysoft/demo5/StopThreadDemo03.java
Thread类的interrupt()方法作用:如果调用该方法的线程处于等于等待状态,则该线程抛出异常
package com.mysoft.demo5;
/**
* @author lpz
* 停止线程的方法:Thread类的interrupt()方法
*/
class Thread03 extends Thread{
private volatile boolean flag=true;
@Override
public void run() {
synchronized (this) {
System.out.println("子线程开始执行.....");
while(flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
stopThread();
}
}
System.out.println("子线程执行结束.....");
}
}
public void stopThread() {
this.flag=false;
}
}
public class StopThreadDemo03 {
public static void main(String[] args){
Thread03 thread=new Thread03();
Thread t1=new Thread(thread);
t1.start();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程"+i);
if(i==5) {
//方法3 使用Thread类的interrupt()方法。
//如果当前线程正在等待,则直接抛出异常
t1.interrupt();
}
}
}
}
5. 生产者消费者模型
5.1 多个生产者多个消费者(生产者生产一种商品)
代码06 多个生产者多个消费者,生产者生产一种商品
package 生产者消费者模型;
import java.util.ArrayList;
import java.util.List;
//货物类
class Goods {
private String goods;
private int count;
public Goods(String goods) {
this.goods = goods;
}
/**
* 生产者生产货物的方法
*/
public synchronized void produce(){
//不断判断执行条件
/**
* 因为有多个线程 假设现在已有商品,生产者线程均在等待
* 消费者线程唤醒生产者线程后,假设此时生产者线程1生产产品
* 此时count=1,若用if判断会造成count数量一直增加
*/
while(count > 0){
System.out.println("该商品还有库存....");
//生产者等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有库存 商品数量+1
this.count++;
System.out.println(Thread.currentThread().getName()+"生产产品"+toString());
//唤醒所有消费者
notifyAll();
}
/**
* 消费者消费商品
*/
public synchronized void consumer(){
//不断判断执行条件
/**因为有多个线程 假设消费者线程先执行、由于不止一个消费者
* 此时可能有多个消费者在等待,如果此时一个生产者生产了商品
* 消费者线程均被唤醒、如果此时线程2消费产品 此时若用if判断
* 其余等待线程也会count-- 造成错误 所以需要用while判断
*/
while(count == 0){
System.out.println("仓库已空...");
//消费者等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费商品 数量-1
this.count--;
System.out.println(Thread.currentThread().getName()+"消费产品"+toString());
//唤醒所有生产者
notifyAll();
}
@Override
public String toString() {
return "Goods{" +
"goods='" + goods + '\'' +
", count=" + count +
'}';
}
}
//生产者线程
class Producer implements Runnable{
//生产的货物
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(true){
this.goods.produce();
}
}
}
//消费者线程
class Consumer implements Runnable{
//消费的货物
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(true){
this.goods.consumer();
}
}
}
public class Test{
public static void main(String[] args) {
Goods goods = new Goods("特百惠杯子");
List<Thread> list = new ArrayList<>();
for(int i=0;i<10;i++){
Thread thread = new Thread(new Producer(goods),"生产者"+i);
list.add(thread);
}
for(int i=0;i<5;i++){
Thread thread = new Thread(new Consumer(goods),"消费者"+i);
list.add(thread);
}
for(Thread thread:list){
//消费者、生产者谁先启动不一定
thread.start();
}
}
}
5.2 多个生产者多个消费者(生产者生产商品数量不固定)
代码07 多个生产者多个消费者(生产者生产商品数量不固定)
package 生产者消费者模型;
import java.util.ArrayList;
import java.util.List;
//货物类
class Goods {
private String goods;
private int count;
public Goods(String goods) {
this.goods = goods;
}
public int getCount() {
return count;
}
/**
* 生产者生产货物的方法
*/
public synchronized void produce(){
//如果没有库存 商品数量+1
if(this.count<10){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.count++;
System.out.println(Thread.currentThread().getName()+"生产产品"+toString());
}
//唤醒所有消费者
notifyAll();
}
/**
* 消费者消费商品
*/
public synchronized void consumer(){
//不断判断执行条件
/**因为有多个线程 假设消费者线程先执行、由于不止一个消费者
* 此时可能有多个消费者在等待,如果此时一个生产者生产了商品
* 消费者线程均被唤醒、如果此时线程2消费产品 此时若用if判断
* 其余等待线程也会count-- 造成错误 所以需要用while判断
*/
while(count == 0){
System.out.println("仓库已空...");
//消费者等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费商品 数量-1
this.count--;
System.out.println(Thread.currentThread().getName()+"消费产品"+toString());
//唤醒所有生产者
notifyAll();
}
@Override
public String toString() {
return "Goods{" +
"goods='" + goods + '\'' +
", count=" + count +
'}';
}
}
//生产者线程
class Producer implements Runnable{
//生产的货物
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
//最大库存为10
while(true){
this.goods.produce();
}
}
}
//消费者线程
class Consumer implements Runnable{
//消费的货物
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while(true){
this.goods.consumer();
}
}
}
public class Test{
public static void main(String[] args) {
Goods goods = new Goods("特百惠杯子");
List<Thread> list = new ArrayList<>();
for(int i=0;i<10;i++){
Thread thread = new Thread(new Producer(goods),"生产者"+i);
list.add(thread);
}
for(int i=0;i<5;i++){
Thread thread = new Thread(new Consumer(goods),"消费者"+i);
list.add(thread);
}
for(Thread thread:list){
//消费者、生产者谁先启动不一定
thread.start();
}
}
}
6. ThreadLocal
1、ThreadLocal是什么?
为共享变量在每个线程中创建一个副本,每个线程可以访问自己内部的副本变量。
2、ThreadLocal的接口方法
void set(Object value) 设置当前线程的线程局部变量的值。
public Object get() 该方法返回当前线程所对应的线程局部变量。
public void remove() 将当前线程局部变量的值删除
protected Object initialValue() 返回该线程局部变量的初始值。在线程第1次调用get()或
set(Object)时才执行,并且仅执行1次
代码08 ThreadLocal解决多个线程之间线程安全问题
package com.txkt.demo;
public class ThreadLocalDemo2 {
//多个线程使用的同一个threadLocal
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
//初始化value
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
//创建一个线程数组
Thread[] threads=new Thread[5];
for (int i = 0; i <threads.length; i++) {
//实例化线程对象
threads[i]=new Thread(()->{
//线程执行体
//获取当前线程threadLocal实例对应的int型value
int num = threadLocal.get().intValue();
num+=5;
//修改value值
threadLocal.set(num);
System.out.println(Thread.currentThread().getName()+":"+num);
},"thread-"+(i+1));
}
for (int i = 0; i <threads.length; i++) {
//启动线程
threads[i].start();
}
}
}
总结如下:
1、对于某一ThreadLocal来讲,他的索引值i是确定的,在不同线程之间访问时访问的是不同的table
数组的同一位置即都为table[i],只不过这个不同线程之间的table是独立的。
2、对于同一线程的不同ThreadLocal来讲,这些ThreadLocal实例共享一个table数组,然后每个
ThreadLocal实例在table中的索引i是不同的。