JAVA 多线程之间的通信
一、线程间通信 - 示例
多个线程在处理同一个资源,但是任务不同。
//资源
class Resource{
String name;
String sex;
}
//输入
class Input implement Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
if(x%2==0){
r.name = "mike";
r.sex = "man";
}else{
r.name = "丽丽";
r.sex = "女";
}
x++;
}
}
}
//输出
class Output implement Runnable{
Resource r;
Output(Resource r){
this.r = r;
}
public void run(){
while(true){
System.out.println(r.name+"..."+r.sex);
}
}
}
class Demo{
public static void main(String[] args){
Resource r = new Resource();
Input i = new Input(r);
Output o = new Output(r);
Thread t1 = new Thread(i);
Thread t2 = new Thread(o);
t1.start();
t2.start();
}
}
由于读和写不同步,所以会造成数据混乱,修改如下:
//输入
class Input implement Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
synchronized(this.r){
if(x%2==0){
r.name = "mike";
r.sex = "man";
}else{
r.name = "丽丽";
r.sex = "女";
}
x++;
}
}
}
}
//输出
class Output implement Runnable{
Resource r;
Output(Resource r){
this.r = r;
}
public void run(){
while(true){
synchronized(this.r){
System.out.println(r.name+"..."+r.sex);
}
}
}
}
二、线程间通信 - 等待唤醒机制 - wait() - notify()
涉及的方法
- wait():是让线程处于冻结状态,释放 cpu 的执行权和执行资格。被 wait 的线程会被存储到线程池中。
- notify():唤醒线程池中一个线程(任意)。
- notifyAll():唤醒线程池中的所有线程。
注意:这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪一个锁上的线程。
为什么操作线程的方法定义在Object类中? 因为这些方法是监视器的方法,监视器其实就是锁,而锁就是任意的对象,所以任意的对象可以调用的方法必须定义在Object中。
//资源
class Resource{
String name;
String sex;
boolean flag = false;
}
//输入
class Input implement Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
synchronized(this.r){
if(this.r.flag){
try{
this.r.wait();
}catch(InterruptedException){}
}
if(x%2==0){
this.r.name = "mike";
this.r.sex = "man";
}else{
this.r.name = "丽丽";
this.r.sex = "女";
}
this.r.flag = true;
this.r.notify();
x++;
}
}
}
}
//输出
class Output implement Runnable{
Resource r;
Output(Resource r){
this.r = r;
}
public void run(){
while(true){
synchronized(this.r){
if(!this.r.flag){
try{
this.r.wait();
}catch(InterruptedException){}
}
System.out.println(this.r.name+"..."+this.r.sex);
this.r.flag = false;
this.r.notify();
}
}
}
}
class Demo{
public static void main(String[] args){
Resource r = new Resource();
Input i = new Input(r);
Output o = new Output(r);
Thread t1 = new Thread(i);
Thread t2 = new Thread(o);
t1.start();
t2.start();
}
}
三、线程间通信-多生产者多消费者问题-wait()-notifyAll()
if 判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况 while 判断标记,解决了线程获取执行权后是否要运行(回过头判断)
notify 只能唤醒一个线程,如果奔放唤醒了本方就没有意义。而且 while 判断标记 + notify 会导致死锁 notifyAll 解决了本方线程一定会唤醒对方线程
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void setName(String name){
if(this.flag){//多生产者这里不能在用if,必须用while,进程唤醒之后再进行判断标记
try{this.wait();}catch(InterruptedException e){}
}
this.name = name + this.count;
this.count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
this.flag = true;
this.notify();//多生产者这里必须使用notifyAll()唤醒所有线程,不然会导致所有线程都被休眠现象(死锁)
}
public synchronized void out(){
if(!this.flag){//多生产者这里不能在用if,必须用while,进程唤醒之后再进行判断标记
try{this.wait();}catch(InterruptedException e){}
}
System.out.println(Thread.currentThread().getName()+"......消费者......"+this.name);
this.flag = false;
this.notify();
}
}
class Producer implement runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
while(true){
this.r.setName("烤鸭");
}
}
}
class Consummer implement runnable{
private Resource r;
Consummer(Resource r){
this.r = r;
}
public void run(){
while(true){
this.r.out();
}
}
}
class ProducerConsumerDemo{
public static void main(String[] args){
Resource r = new Resource();
Producer p = new Producer(r);
Consummer c = new Consummer(r);
//多生产者
Thread t0 = new Thread(p);
Thread t1 = new Thread(p);
//多消费者
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t1.start();
t2.start();
}
}
多生产者多消费者使用 notify() 是造成另一种死锁的原因。
四、线程间通信 - 多生产者多消费者问题 - JDK1.5 新特性- Lock
对于同步代码块,对于锁的操作是隐式的。
import java.util.concurrent.locks.*;
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
Look look = new ReentrantLock();//jdk1.5新特性-创建一个锁对象
public void setName(String name){
lock.lock();//使用显式锁
try{
while(this.flag){//此处就不能再用this了,要用锁上的wait
try{this.wait();}catch(InterruptedException e){}
}
this.name = name + this.count;
this.count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
this.flag = true;
this.notify();//此处就不能再用this了
}finally{
lock.unlock();
}
}
public void out(){
try{
if(!this.flag){//此处就不能再用this了
try{this.wait();}catch(InterruptedException e){}
}
System.out.println(Thread.currentThread().getName()+"......消费者......"+this.name);
this.flag = false;
this.notify();//此处就不能再用this了
}finally{
lock.unlock();
}
}
}
Lock 接口:出现替代了同步代码块或者同步函数,将同步的隐式锁操作变为显式锁操作。同时更为灵活。可以在一个锁上加上多个监视器
五、线程间通信 - 多生产者多消费者问题 - JDK1.5 新特性-Condition
import java.util.concurrent.locks.*;
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
Look look = new ReentrantLock();//jdk1.5新特性-创建一个锁对象
//通过已有的锁获取该锁上的监视器对象
Condition con1 = lock.newCondition();//两组监视器,一组监视生产者,一组监视消费者
Condition con2 = lock.newCondition();
public void setName(String name){
lock.lock();//使用显式锁
try{
while(this.flag){
try{con1.await();}catch(InterruptedException e){}//此处就不能再用this了,要用锁上的wait
}
this.name = name + this.count;
this.count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
this.flag = true;
//this.notify();//此处就不能再用this了
con2.signalAll();/con2.signal();
}finally{
lock.unlock();
}
}
public void out(){
try{
if(!this.flag){//此处就不能再用this了
try{con2.await();}catch(InterruptedException e){}
}
System.out.println(Thread.currentThread().getName()+"......消费者......"+this.name);
this.flag = false;
//this.notify();//此处就不能再用this了
con1.signalAll();/con1.signal();
}finally{
lock.unlock();
}
}
}
Condition 接口:出现替代了 Object 中的 wait notify notifyAll 方法。将这些监视器方法单独进行了封装,变成 Condition 监视器对象,可以任意锁进行组合 await signal signalAll。
六、wait 和 sleep 的区别
1、wait 可以指定时间也可以不指定。sleep必须指定时间。
2、在同步中,对 cpu 的执行权和锁的处理不同
- wait:释放执行权,释放锁
- sleep:释放执行权,不释放锁。
七、停止线程
1、stop()
已过时
2、run方法结束
任务中都会有循环结构,只要控制住循环就可以结束任务。 控制循环通常就用定义标记来完成。
但是如果线程处于冻结状态,无法读取标记,如何结束呢?
可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。 但是强制动作会发生 InterruptedException,记得要处理
八、守护线程 - setDaemon
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出,该方法必须在启动线程前调用。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论