熟练多线程之间通讯Wait、Notify、NotifyAll用法
需求:分别创建两个线程,名称为A和B,A线程负责写操作,也就是不停的写入名称和性别。 B线程负责读取操作,
也就是不停的读取现在写入的名称和性别,
看图演示
2.3代码实现基本实现
package ReadInput;
class Res {
public String userSex;
public String userName;
}
/**
* 写操作
* @author zzq
*
*/
class IntThrad extends Thread {
private Res res;
public IntThrad(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
if (count == 0) {
res.userName = "zzq";
res.userSex = "男";
} else {
res.userName = "小紅";
res.userSex = "女";
}
count = (count + 1) % 2;
}
}
}
/**
* 读操作
* @author zzq
*
*/
class OutThread extends Thread {
private Res res;
public OutThread(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
System.out.println(res.userName + "--" + res.userSex);
}
}
}
public class MainTest {
public static void main(String[] args) {
Res res = new Res();
IntThrad intThrad = new IntThrad(res);
OutThread outThread = new OutThread(res);
intThrad.start();
outThread.start();
}
}
数据发生错乱,造成线程安全问题
解决线程安全问题:第一种方法synchronized锁
对输入输出部分都加上锁。
package ReadInput;
class Res {
String name;
String sex;
//默认是false
boolean flag;
}
/**
* 写入操作
*
* @author zzq
*
*/
class Input implements Runnable {
private Res res;
public Input(Res res) {
this.res = res;
}
@Override
public void run() {
int i = 0;
while (true) {
/**
* 1、解决线程不安全问题(上锁:不在循环外面加锁,会造成单线程,)
*/
synchronized (res) {
// 判断如果flag为true的时候,就让当前线程等待(因为B线程还没有写完)
if (res.flag) {
try {
res.wait();
} catch (InterruptedException e) {
}
}
//一旦不等于true的时候就写入数据,
if (i == 0) {
res.name = "zzq";
res.sex = "男";
} else {
res.name = "小红";
res.sex = "女";
}
i = (i + 1) % 2;
//赋值为true,让另一个线程读取(而不会一直去写)
res.flag = true;
res.notify();
}
}
}
}
/**
* 读取操作
*
* @author zzq
*
*/
class ReadInput implements Runnable {
private Res res;
public ReadInput(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
//如果flag为false,先不获取数据,
if (!res.flag){
try {
//让当前线程进入到等待
res.wait();
} catch (Exception e) {
}
}
//当flag不是false的时候,就应该获取值了
System.out.println("name:" + res.name + "--sex:" + res.sex);
//将flag标记为false(表示我已经将值获取完了,告诉A线程再写数据)
res.flag = false;
res.notify();
}
}
}
}
public class TestThread {
public static void main(String[] args) {
//读的和写的线程都共享Res
Res res = new Res();
Input input = new Input(res);
ReadInput readInput = new ReadInput(res);
Thread thread = new Thread(input);
Thread thread1 = new Thread(readInput);
thread.start();
thread1.start();
}
}
等待唤醒机制
涉及的方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
notifyAll:唤醒的是线程池中的所有线程。
注意:
1:这些方法都需要定义在同步中。
2:因为这些方法必须要标示所属的锁。
你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。
3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能
被任意对象调用的方法一定定义在Object类中。
Wait和Sleep区别
分析这两个方法:从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但不是不释放锁。
线程的停止
通过stop方法就可以停止线程。但是这个方式过时了。
停止线程:原理就是:让线程运行的代码结束,也就是结束run方法。
怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。
第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,
将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。
第二种:Lock关键字用法
提到Java中的锁,相信大家都知道它是用来控制多个线程访问共享资源的方式(即锁能防止多个线程同时访问空享
资源而出现线程安全问题)。在实践过程中使用最多的也最常见的锁就是 synchronized 在jdk1.5之前也仅仅有这一种
锁而已。在jdk1.5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,Lock接口提供了与synchronized关键字
类似的同步功能,但需要在使用时手动获取锁和释放锁。虽然Lock接口没有synchronized关键字自动获取和释放锁那么
便捷,但Lock接口却具有了锁的可操作性,可中断获取以及超时获取锁等多种非常实用的同步特性,除此之外Lock接口
还有两个非常强大的实现类重入锁和读写锁,下面一一讲解这些内容。
原因:
jdk1.5 中提供给了多线程升级解决方案。
将同步synchronized替换成实Lock操作
将Object中的wait、notify、notifyAll,替换了Condition对象。
该对象可以Lock锁 ,进行获取。
Lock接口的使用
Lock lock = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操作
}finally{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
lock.ublock();
}
package ReadInput;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Res2 {
String name;
String sex;
//默认是false
boolean flag;
}
/**
* 写入操作
*
* @author zzq
*
*/
class Input2 implements Runnable {
private Res2 res;
private Lock lock;
private Condition newCondition;
public Input2(Res2 res,Lock lock,Condition newCondition) {
this.res = res;
this.lock = lock;
this.newCondition = newCondition;
}
@Override
public void run() {
int i = 0;
while (true) {
/**
* 1、解决线程不安全问题(上锁:不在循环外面加锁,会造成单线程,)
*/
//synchronized (res) {
//已经上锁,拿到了锁之后,没有释放锁,其他线程是不能访问的
lock.lock();
// 判断如果flag为true的时候,就让当前线程等待(因为B线程还没有写完)
try {
if (res.flag) {
newCondition.await();
}
//一旦不等于false的时候就写入数据,
if (i == 0) {
res.name = "zzq";
res.sex = "男";
} else {
res.name = "小红";
res.sex = "女";
}
i = (i + 1) % 2;
//唤醒其他线程
newCondition.signal();
//赋值为true,让另一个线程读取(而不会一直去写)
res.flag = true;
} catch (Exception e) {
}finally{
//将锁释放掉
lock.unlock();
}
//}
}
}
}
/**
* 读取操作
*
* @author zzq
*
*/
class ReadInput2 implements Runnable {
private Res2 res;
private Lock lock;
private Condition newCondition;
public ReadInput2(Res2 res,Lock lock,Condition newCondition) {
this.res = res;
this.lock = lock;
this.newCondition = newCondition;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (!res.flag){
newCondition.await();
}
} catch (Exception e) {
//当flag不是false的时候,就应该获取值了
System.out.println("name:" + res.name + "--sex:" + res.sex);
//将flag标记为false(表示我已经将值获取完了,告诉A线程再写数据)
res.flag = false;
newCondition.signal();
}finally{
lock.unlock();
}
}
}
}
public class TestThread2 {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
//lock.newCondition();
Condition newCondition= lock.newCondition();
//读的和写的线程都共享Res
//Lock lock =new ReentrantLock();
Res2 res = new Res2();
Input2 input = new Input2(res,lock,newCondition);
ReadInput2 readInput = new ReadInput2(res,lock,newCondition);
Thread thread = new Thread(input);
Thread thread1 = new Thread(readInput);
thread.start();
thread1.start();
}
}
Lock接口与synchronized关键字的区别
Lock接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
Lock接口能被中断地获取锁 与synchronized不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,
中断异常将会被抛出,同时锁会被释放。
Lock接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。
核心的重点是:当synchronized关键字它的锁是不用自己释放的,而lock要自己释放的。
Lock接口的API
void lock() 获取锁,调用该方法当前线程将会获取锁,当锁获取后,该方法将返回。
void lockInterruptibly() throws InterruptedException可中断获取锁,与lock()方法不同之处在于该方法会响应中断,即
在锁的获取过程中可以中断当前线程
boolean tryLock() 尝试非阻塞的获取锁,调用该方法立即返回,true表示获取到锁
boolean tryLock(long time,TimeUnitunit) throws InterruptedException 超时获取锁,以下情况会返回:时间内获取到了
锁,时间内被中断,时间到了没有获取到锁。
void unlock() 释放锁
了解Lock接口最常用的实现ReentrantLock重入锁
ReentrantLock是Lock接口一种常见的实现,它是支持重进入的锁即表示该锁能够支持一个线程对资源的重复加锁。
该锁还支持获取锁时的公平与非公平的选择。
关于锁的重进入,其实synchronized关键字也支持。如前所述,synchronized关键字也是隐式的支持重进入而对于
ReentrantLock而言,对于已经获取到锁的线程,再次调用lock()方法时依然可以获取锁而不被阻塞。
理解了锁的重进入,现在解释刚刚提到的公平获取锁与非公平获取锁。如果在绝对时间上,先对于锁进行获取的请
求一定先被满足,那么这个锁就是公平的,反之就是非公平的。公平的获取锁也就是等待时间最久的线程优先获取
到锁。ReentrantLock的构造函数来控制是否为公平锁。
我在第一次了解到公平锁于非公平锁的时候,第一反应是公平锁的效率高,应该使用公平锁。但实际的情况是,非
公平的锁的效率远远大于公平锁。
了解Lock接口的实现类ReentrantReadWriteLock读写锁
前面提到的ReentrantLock是排他锁,该锁在同一时刻只允许一个线程来访问,而读写锁在同一时刻允许可以有多
个线程来访问,但在写线程访问时,所有的读线程和其他写线程被阻塞。读写锁维护了一对锁,一个读锁和一个写
锁,通过读写锁分离,使得并发性相比一般的排他锁有了很大的提升。
读写锁除了使用在写操作happends-before与读操作以及并发性的提升之外,读写锁也能够简化读写交互场景的编
程方式。假设在程序中定义一个共享的用作缓存数据结构,它的大部分时间提供读服务(查询,搜索等)而写操作较
少,但写操作之后需要立即对后续的读操作可见。在没有读写锁之前,实现这个功能需要使用等待通知机
制(http://blog.csdn.net/canot/article/details/50879963)。无论使用那种方式,目的都是为了写操作立即可见于读操
作而避免脏读。但使用读写锁却比等待通知简单明了多了。
一般情况下,读写锁性能优于排他锁。它能提供更好的并发性和吞吐量。
ReentrantReadWriteLock读写锁的几个特性:
公平选择性
重进入
锁降级
sleep与wait区别?
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的
时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方
法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。
Lock 接口与 synchronized 关键字的区别
Lock 接口可以尝试非阻塞地获取锁当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取
并持有锁。
*Lock 接口能被中断地获取锁与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中
断时,中断异常将会被抛出,同时锁会被释放。
Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。
Condition用法
Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能,
代码:
Condition condition = lock.newCondition();
res. condition.await(); 类似wait
res. Condition. Signal() 类似notify
如何停止线程?
停止线程思路
1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能
发生不可预料的结果)。
3. 使用interrupt方法中断线程。
守护线程
Java中有两种线程,一种是用户线程,另一种是守护线程。
当进程不存在或主线程停止,守护线程也会被停止。
使用setDaemon(true)方法设置为守护线程
**
*
* 什么是守护线程? 守护线程 进程线程(主线程挂了) 守护线程也会被自动销毁.
*
* @classDesc: 功能描述:(守护线程)
* @author: 余胜军
* @createTime: 2017年8月20日 下午8:55:58
* @version: v1.0
* @copyright:上海每特教育科技有限公司
*/
public class DaemonThread {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("我是子线程...");
}
}
});
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (Exception e) {
}
System.out.println("我是主线程");
}
System.out.println("主线程执行完毕!");
}
}
一、join()方法作用
join作用是让其他线程变为等待
6.1需求:
创建一个线程,子线程执行完毕后,主线程才能执行。
class JoinThread implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---i:" + i);
}
}
}
/**
*
* @classDesc: 功能描述:(Join方法)
* @author: 余胜军
* @createTime: 2017年8月20日 下午9:23:30
* @version: v1.0
* @copyright:上海每特教育科技有限公司
*/
public class JoinThreadDemo {
public static void main(String[] args) {
JoinThread joinThread = new JoinThread();
Thread t1 = new Thread(joinThread);
Thread t2 = new Thread(joinThread);
t1.start();
t2.start();
try {
//其他线程变为等待状态,等t1线程执行完成之后才能执行join方法。
t1.join();
} catch (Exception e) {
}
for (int i = 0; i < 100; i++) {
System.out.println("main ---i:" + i);
}
}
}
优先级
现代操作系统基本采用时分的形式调度运行的线程,线程分配得到的时间片的多少决定了线程
使用处理器资源的多少,也对应了线程优先级这个概念。在JAVA线程中,通过一个int priority
来控制优先级,范围为1-10,其中10最高,默认值为5。下面是源码(基于1.8)中关于priority
的一些量和方法。
class PrioritytThread implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().toString() + "---i:" + i);
}
}
}
/**
*
* @classDesc: 功能描述:(Join方法)
* @author: 余胜军
* @createTime: 2017年8月20日 下午9:23:30
* @version: v1.0
* @copyright:上海每特教育科技有限公司
*/
public class ThreadDemo4 {
public static void main(String[] args) {
PrioritytThread prioritytThread = new PrioritytThread();
Thread t1 = new Thread(prioritytThread);
Thread t2 = new Thread(prioritytThread);
t1.start();
// 注意设置了优先级, 不代表每次都一定会被执行。 只是CPU调度会有限分配
t1.setPriority(10);
t2.start();
}
}
Yield方法
Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)
yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的
机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,
实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
结论:大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。