一、什么是多线程之间通讯?
多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同。
二、如何实现多线程之间的通讯?
使用wait()、notify()、notifyAll()方法
wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
这三个方法最终调用都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。
- 如果对象调用wait()方法,就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
- 如果对象调用了notify方法,就会通知某个正在等待这个对象控制权的线程可以继续运行。
- 如果对象调用了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();
}
}
五、如何优雅的停止线程?
实际在企业中,怎么停止线程?
企业中都是使用线程池进行管理,不需要手动的开启和停止线程,而且企业中多线程不会写死循环。
停止线程思路:
- 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
- 使用stop方法强行终止线程(这个方法不推荐使用,因为Thread.stop()本质上是不安全的,没有异常处理机制,和suspend、resume一样,可能发生不可预料的结果)
- 使用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()将导致线程从运行状态转到可运行状态,但有可能没有效果。