线程之间的通信
一、为什么要线程通信?
1. 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,
并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
2.当然如果我们没有使用线程通信来使用多线程共同操作同一份数据的话,虽然可以实现,
但是在很大程度会造成多线程之间对同一共享变量的争夺,那样的话势必为造成很多错误和损失!
3.所以,我们才引出了线程之间的通信,多线程之间的通信能够避免对同一共享变量的争夺。
二、什么是线程通信?
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。
就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。
于是我们引出了等待唤醒机制:(wait()、notify())
就是在一个线程进行了规定操作后,就进入等待状态(wait), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify);
(1)wait()方法:
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。
要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。
(2)notif()方法:
notify()方法会唤醒一个等待当前对象的锁的线程。唤醒在此对象监视器上等待的单个线程。
(3)notifAll()方法:
notifyAll()方法会唤醒在此对象监视器上等待的所有线程。
(4)如果多个线程在等待,它们中的一个将会选择被唤醒。这种选择是随意的,和具体实现有关。(线程等待一个对象的锁是由于调用了wait方法中的一个)
notify()方法应该是被拥有对象的锁的线程所调用。
(5)以上方法都定义在类:Object中
1.因为,这些方法在操作同步中的线程的时候,都必须标示其所操作线程所持有的锁(被该锁的对象调用),
而只有同一个对象监视器下(同一个锁上)的被等待线程,可以被持有该锁的线程唤醒,(无法唤醒不同锁上的线程)
2.所以,等待和唤醒的必须是同一个对象的监视器①下(同一个锁上)的线程。
而锁可以是任意已近确定的对象, 能被任意对象调用的方法应当定义在 Object类中。 注:
①
监视器(锁):同一个对象的监视器下(同一个锁上)的线程,一次只能执行一个:就是拥有监视器所有权(持有锁)的那一个线程。
生产者与消费者:
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
package threadCom;//摘自蚂蚁课堂
/**
* 线程通讯(生产者与消费者)
* @author Administrator
*
*/
class Res {
public String userName;
public String sex;
public boolean flag = false;
}
class InputThread extends Thread {
private Res res;
public InputThread(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (res) {
if (res.flag) {
try {
res.wait();
} catch (Exception e) {
// TODO: handle exception
}
}
if (count == 0) {
res.userName = "余胜军";
res.sex = "男";
} else {
res.userName = "小红";
res.sex = "女";
}
count = (count + 1) % 2;
res.flag = true;
res.notify();
}
}
}
}
class OutThrad extends Thread {
private Res res;
public OutThrad(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
if (!res.flag) {
try {
res.wait();
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println(res.userName + "," + res.sex);
res.flag = false;
res.notify();
}
}
}
}
public class ThreadCom1 {
public static void main(String[] args) {
Res res = new Res();
InputThread inputThread = new InputThread(res);
OutThrad outThrad = new OutThrad(res);
inputThread.start();
outThrad.start();
}
}
运行结果://
余胜军,男
小红,女
余胜军,男
小红,女
余胜军,男
小红,女
余胜军,男
小红,女
总结:wait 和notify必须针对使用同一把锁的线程有效
wait与sleep区别?
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。
JDK1.5-并发包中的Lock锁
在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。
LOCK写法:要访问共享资源时,lock锁也需要相同的
Lock lock = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操作
}finally{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
lock.ublock();
}
注:wait和notify不能在lock中使用
线程停止
两种方法:1、循环停止
2、使用中断异常捕捉停止
package sada_01;
public class StopThreadDemo2 {
public static void main(String[] args) {
// System.out.println("主线程ID:"+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
for(int i=0;i<10;i++) {
System.out.println("主线程="+i);
try {
Thread.sleep(1000);
if(i==5) {
thread.interrupt();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class MyRunnable implements Runnable{
private boolean flag=true;
public MyRunnable() {
}
public synchronized void run() {
// System.out.println("子线程ID:"+Thread.currentThread().getId());
System.out.println("线程已开始");
while(flag) {
try {
wait();
} catch (Exception e) {
// e.printStackTrace();
stop();
}
}
System.out.println("线程已结束");
}
public void stop(){
System.out.println("修改flag");
this.flag=false;
System.out.println("修改flag完成");
}
}
打印:
线程已开始
主线程=0
主线程=1
主线程=2
主线程=3
主线程=4
主线程=5
主线程=6
修改flag
修改flag完成
线程已结束
主线程=7
主线程=8
主线程=9
ThreadLocal作用,将线程共享变量变为线程自己的局部变量
package sada_01;
/**
* ThreadLocal线程内的共享变量变成每个新建线程的本地变量,即各线程有一个自己单独的变量
* @author Administrator
*
*/
class ResName{
public Integer count;
public ThreadLocal<Integer> tl=new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
};
};
public String getNumber() {
count = tl.get()+1;
tl.set(count);
return count+"";
}
}
class ThreadLocalDemo1 extends Thread{
private ResName resName;
public ThreadLocalDemo1(ResName resName) {
this.resName=resName;
}
@Override
public void run() {
for(int i=0;i<3;i++) {
System.out.println(getName()+":number="+resName.getNumber());
}
}
}
public class ThreadLocalDemo {
public static void main(String[] args) {
ResName resName = new ResName();
ThreadLocalDemo1 t1=new ThreadLocalDemo1(resName);
ThreadLocalDemo1 t2=new ThreadLocalDemo1(resName);
ThreadLocalDemo1 t3=new ThreadLocalDemo1(resName);
t1.start();
t2.start();
t3.start();
}
}
打印信息:
Thread-0:number=1
Thread-1:number=1
Thread-1:number=2
Thread-1:number=3
Thread-2:number=1
Thread-2:number=2
Thread-0:number=2
Thread-0:number=3
Thread-2:number=3