浅谈多线程之间实现通讯

一、什么是多线程之间通讯?

多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同。

二、如何实现多线程之间的通讯?

使用wait()、notify()、notifyAll()方法
wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
这三个方法最终调用都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。

  1. 如果对象调用wait()方法,就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
  2. 如果对象调用了notify方法,就会通知某个正在等待这个对象控制权的线程可以继续运行。
  3. 如果对象调用了notifyAll方法,就会通知所有等待这个对象控制权的线程继续运行。
    注意:wait()、notify()、notifyAll()一定要和同步synchronized一起使用
    代码实现:
    package chauncy.communication;
    
    /**
     * 
     * @classDesc: 功能描述:(共享资源)
     * @author: ChauncyWang
     * @verssion: v1.0
     */
    class Res {
    	public String name;
    	public String sex;
    	// flag为false表示out线程未读取值
    	public boolean flag = false;
    }
    
    /**
     * 
     * @classDesc: 功能描述:(写入线程)
     * @author: ChauncyWang
     * @verssion: v1.0
     */
    class InputThread extends Thread {
    	public Res res;
    
    	public InputThread(Res res) {
    		this.res = res;
    	}
    
    	@Override
    	public void run() {
    		int count = 0;
    		while (true) {
    			synchronized (res) {
    				if (res.flag) {
    					// 当前线程等待,wait()可以让当前线程从运行状态变为休眠状态,类似于Thread.sleep(),但是sleep和wait有本质上区别
    					// wait使用在多线程之间同步 和synchronized一起用,可以释放锁,而sleep不能释放锁
    					try {
    						res.wait();
    					} catch (InterruptedException e) {
    					}
    				}
    				if (count == 0) {
    					res.name = "ChauncyWang";
    					res.sex = "male";
    				} else {
    					res.name = "xiaohong";
    					res.sex = "female";
    				}
    				// 实现奇数和偶数
    				count = (count + 1) % 2;
    				res.flag = true;
    				// 和wait一起使用,唤起另一个线程,唤醒:就是使线程从阻塞转台变成运行状态
    				res.notify();
    			}
    		}
    	}
    }
    
    /**
     * 
     * @classDesc: 功能描述:(读的线程)
     * @author: ChauncyWang
     * @verssion: v1.0
     */
    class OutThread extends Thread {
    	public Res res;
    
    	public OutThread(Res res) {
    		this.res = res;
    	}
    
    	@Override
    	public void run() {
    		while (true) {
    			synchronized (res) {
    				if (!res.flag) {
    					try {
    						res.wait();
    					} catch (InterruptedException e) {
    					}
    				}
    				System.out.println(res.name + "----" + res.sex);
    				res.flag = false;
    				res.notify();
    			}
    		}
    	}
    }
    
    /**
     * @classDesc: 功能描述:(第一个线程写入(input)用户,另一个线程读取(out)用户,实现读一个,写一个操作。)
     * @author: ChauncyWang
     * @verssion: v1.0
     */
    public class SynchronizedSharedThread {
    	public static void main(String[] args) {
    		Res res = new Res();
    		InputThread inputThread = new InputThread(res);
    		OutThread outThread = new OutThread(res);
    		inputThread.start();
    		outThread.start();
    	}
    }
    

三、多线程之间通讯相关问题

为什么wait()、notify()、notifyAll()定义在Object类中?
因为当我们要使用多线程进行同步的时候,锁是自定义的,可以是一个类锁,类的父类是Object,所以把wait()、notify()、notifyAll()定义在Object类中的理念是让任何的锁都能使用。
wait()与sleep()区别?
wait使用在多线程之间同步 和synchronized一起用,可以释放锁,而sleep不能释放锁。

四、解决线程不安全问题之Lock锁

synchoronized同步锁不能手动开锁、解锁,所以有了并发包Lock、线程池Executor、并发包Condition。
在jdk1.5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,Lock接口提供了与synchronized关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。
Lock锁不能使用wait(),wait()是与synchronized连用的,如果想进行多线程之间通讯的话要使用Condition对象,condition.await()类似wait(),condition.Signal()类似notify(),condition.Signalal()l类似notifyAll()。
很复杂多线程程序就使用lock。
代码实现:

package chauncy.communication.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 
 * @classDesc: 功能描述:(共享资源)
 * @author: ChauncyWang
 * @verssion: v1.0
 */
class Res {
	public String name;
	public String sex;
	// flag为false表示out线程未读取值
	public boolean flag = false;
	// 创建重入锁Lock对象,多线程之间通讯一定要定义成单例锁,保持多线程间只有一个一样的锁
	public Lock lock = new ReentrantLock();
	// Lock锁不能使用wait,所以需要创建cdotion对象调用await方法
	Condition condition = lock.newCondition();
}

/**
 * 
 * @classDesc: 功能描述:(写入线程)
 * @author: ChauncyWang
 * @verssion: v1.0
 */
class InputThread extends Thread {
	public Res res;

	public InputThread(Res res) {
		this.res = res;
	}

	@Override
	public void run() {
		int count = 0;
		while (true) {
			try {
				// 获取锁的资源
				res.lock.lock();
				if (res.flag) {
					try {
						res.condition.await();
					} catch (Exception e) {
					}
				}
				if (count == 0) {
					res.name = "ChauncyWang";
					res.sex = "male";
				} else {
					res.name = "xiaohong";
					res.sex = "female";
				}
				// 实现奇数和偶数
				count = (count + 1) % 2;
				res.flag = true;
				res.condition.signal();
			} catch (Exception e) {
			} finally {
				// 释放锁资源,为了防止产生异常后锁无法释放,
				res.lock.unlock();
			}
		}
	}
}

/**
 * 
 * @classDesc: 功能描述:(读的线程)
 * @author: ChauncyWang
 * @verssion: v1.0
 */
class OutThread extends Thread {
	public Res res;

	public OutThread(Res res) {
		this.res = res;
	}

	@Override
	public void run() {
		while (true) {
			try {
				res.lock.lock();
				if (!res.flag) {
					try {
						res.condition.await();
					} catch (Exception e) {
					}
				}
				System.out.println(res.name + "----" + res.sex);
				res.flag = false;
				res.condition.signal();
			} catch (Exception e) {
			} finally {
				res.lock.unlock();
			}
		}
	}
}

/**
 * @classDesc: 功能描述:(第一个线程写入(input)用户,另一个线程读取(out)用户,实现读一个,写一个操作。)
 * @author: ChauncyWang
 * @verssion: v1.0
 */
public class LockSharedResource {
	public static void main(String[] args) {
		Res res = new Res();
		InputThread inputThread = new InputThread(res);
		OutThread outThread = new OutThread(res);
		inputThread.start();
		outThread.start();
	}
}

五、如何优雅的停止线程?

实际在企业中,怎么停止线程?
企业中都是使用线程池进行管理,不需要手动的开启和停止线程,而且企业中多线程不会写死循环。
停止线程思路:

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  2. 使用stop方法强行终止线程(这个方法不推荐使用,因为Thread.stop()本质上是不安全的,没有异常处理机制,和suspend、resume一样,可能发生不可预料的结果)
  3. 使用interrupt方法中断线程(线程在阻塞状态)。
    代码实现:
    package chauncy.communication.stopthread;
    
    class StopThread extends Thread {
    	// flag为true,线程一直在运行状态,为false,停止线程
    	public boolean flag = true;
    
    	@Override
    	public synchronized void run() {
    		while (flag) {
    			try {
    				wait();
    			} catch (Exception e) {
    				StopThread();
    			}
    			System.out.println(Thread.currentThread().getName() + "---我是子线程");
    		}
    	}
    
    	/**
    	 * 
    	 * @methodDesc: 功能描述(停止线程)
    	 * @author: ChauncyWang
    	 * @param:
    	 * @returnType: void
    	 */
    	public void StopThread() {
    		flag = false;
    		System.out.println(getName() + "------线程已经停止");
    	}
    }
    
    /**
     * 一般使用多线程的时候run方法会一直在执行,大多数会使用for、while循环
     * 
     * @classDesc: 功能描述(使用退出标识停止线程,没有根本解决问题,因为run方法使用同步函数并且wait,线程虽然死掉了但是还在等待着唤醒)
     * @author: ChauncyWang
     * @version: 1.0
     */
    public class StopThreadDemo {
    	public static void main(String[] args) {
    		StopThread stopThread1 = new StopThread();
    		StopThread stopThread2 = new StopThread();
    		stopThread1.start();
    		stopThread2.start();
    		for (int i = 0; i < 30; i++) {
    			try {
    				Thread.sleep(10);
    			} catch (Exception e) {
    
    			}
    			System.out.println("main ....." + i);
    			if (i == 29) {
    				// stopThread1.StopThread();
    				// stopThread2.StopThread();
    				stopThread1.interrupt();
    				stopThread2.interrupt();
    			}
    		}
    	}
    }
    

六、守护线程

Java中有两种线程,一种是用户线程(前台线程),一种是守护线程 (后台线程)。
当进程不存在或主线程停止,守护线程也会被停止。
使用setDaemon(true)方法设置为守护线程。
代码实现:

package chauncy.communication.daemon;

/**
 * 线程分为两种,一种是用户线程(前台线程),一种是守护线程(后台线程)
 * 守护线程:主线程或jvm进程挂了,守护线程也会被停止掉。gc也是守护线程   
 * @classDesc: 功能描述(守护线程)  
 * @author: ChauncyWang
 * @version: 1.0  
 */  
public class DaemonThread {
	
	public static void main(String[] args) {
		Thread thread = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i < 100; i++) {
					try {
						Thread.sleep(10);
					} catch (Exception e) {
						
					}
					System.out.println("我是子线程------i:"+i);
				}
			}
		});
		//标识为守护线程
		thread.setDaemon(true);
		thread.start();
		for (int i = 0; i < 30; i++) {
			try {
				Thread.sleep(10);
			} catch (Exception e) {
			
			}
			System.out.println("我是主线程------i:"+i);
		}
		System.out.println("主线程执行完毕");
	}
}

七、Join方法

join方法的作用是让其他线程等待。
当一个线程调用join方法会让其他线程等待,只有当前线程run方法执行完毕,才会释放资格如果对join方法传入参数则根据传入参数的时间大小,当传参时间结束就释放资格
代码实现:

package chauncy.communication.join;

class ThreadJoin extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 40; i++) {
			try {
				Thread.sleep(30);
			} catch (Exception e) {
			
			}
			System.out.println(getName() + "---------i:" + i);
		}
	}
}

/**
 * @classDesc: 功能描述(Join Demo)
 * @author: ChauncyWang
 * @version: 1.0
 */
public class JoinTest {
	public static void main(String[] args) throws InterruptedException {
		ThreadJoin threadJoin1 = new ThreadJoin();
		ThreadJoin threadJoin2 = new ThreadJoin();
		threadJoin1.start();
		threadJoin1.join();//让其他线程等待,只有当前线程执行完毕,才会释放资格,如果传入参数则根据传入参数的时间大小来释放资格
		threadJoin2.start();
		for (int i = 0; i < 40; i++) {
			System.out.println("main---------i:" + i);
		}
	}
}
package chauncy.communication.join;

/**
 * @classDesc: 功能描述(有T1、T2、T3三个线程,如何怎样保证T2在T1执行完后执行,T3在T2执行完后执行)
 * @author: ChauncyWang
 * @version: 1.0
 */
public class JoinPractice {
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 30; i++) {
					System.out.println(Thread.currentThread().getName() + "-------i:" + i);
				}
			}
		}, "t1");
		t1.start();
		t1.join();
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 30; i++) {
					System.out.println(Thread.currentThread().getName() + "-------i:" + i);
				}
			}
		}, "t2");
		t2.start();
		t2.join();
		Thread t3 = new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 30; i++) {
					System.out.println(Thread.currentThread().getName() + "-------i:" + i);
				}
			}
		}, "t3");
		t3.start();
		t3.join();
	}
}

八、线程优先级

现代操作系统基本采用时分的形式调度运行的线程,线程分配得到的时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。在JAVA线程中,通过setPriority(10);方法来设置线程优先级,范围为1-10,其中10最高,默认值为5。
注意:设置线程优先级并不代表线程一定会优先执行,只是优先执行的概率大了。

九、Yield方法

Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)
yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
结论:大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值