线程间通信等待唤醒机制:
- wait---放弃执行资格,让线程冻结,会释放锁。
- sleep---也会放弃执行资格,但是不会释放锁
- notify---唤醒第一个冻结线程
- notifyAll---唤醒所有冻结线程
1、 线程间通信演示---线程间通信安全问题----线程间通信等待唤醒机制
例1:男人挣钱女人花钱
package net.csdn.qf.test;
/**
* @author 北冥有熊
* 2018年11月7日
*/
public class Test01 {
public static void main(String[] args) {
Person p = new Person();
In in = new In(p);
Out out = new Out(p);
new Thread(in,"郭靖").start();
new Thread(in,"杨康").start();
new Thread(out,"黄蓉").start();
new Thread(out,"穆念慈").start();
}
}
//IN
class In implements Runnable{
Person person = null;
public In(Person person) {
this.person = person;
}
public void run() {
while(person.count>0) {
person.inMoney();
person.count--;
}
}
}
//Out
class Out implements Runnable{
Person person = null;
public Out(Person person) {
this.person = person;
}
@Override
public void run() {
while(person.count>0) {
person.outMoney();
person.count--;//因为共享count,所以这句可以不要
}
}
}
//Person
class Person{
private String name;
private boolean haveMoney;
int count = 20;
public Person() {
super();
}
//男人挣钱
public synchronized void inMoney() { //同步方法
while(haveMoney) { //判断是否有钱
try {
this.wait();//等待
} catch (Exception e) {
// TODO: handle exception
}
try {
Thread.sleep(500);
} catch (Exception e) {
// TODO: handle exception
}
}
name = "男人";
System.out.println(Thread.currentThread().getName()+"--"+name+"---挣钱");
haveMoney = !haveMoney; //改成没钱
notifyAll(); //唤醒全部
}
//女人花钱
public synchronized void outMoney() { //同步方法
while(!haveMoney){ //判断是否有钱
try {
this.wait(); //等待
} catch (Exception e) {
// TODO: handle exception
}
try {
Thread.sleep(500);
} catch (Exception e) {
// TODO: handle exception
}
}
name = "女人";
System.out.println(Thread.currentThread().getName()+"--"+name+"----花钱");
haveMoney = !haveMoney; //改成有钱
notifyAll(); //唤醒全部
}
public String toString() {
return name+"-----";
}
}
while+NotifyAll-------其中while解决的是唤醒自己方线程出现重复录入或者输出的情况,notifyAll 解决的是防止出现全部wait。
所有的控制前提:
- 要有两个以上的线程
- 要操作同一个资源
- 而且还要是同一把锁
Lock、Condition:
在Java中Lock和Condition 也是用来解决多线程安全问题。Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
- Lock lock = new ReentrantLock(); //创建一个锁对象lock
- Condition manCondition = lock.newCondition(); //创建男人线程对象manCondition
- Condition womanCondition = lock.newCondition(); //创建女人线程对象womanCondition
- lock.lock(); //上锁
- lock.unLock(); //解锁
- await()---线程等待
- signal()---线程唤醒
- signalAll()---唤醒所有线程
下面以Lock Condition解决男人挣钱女人花钱的多线程安全问题:
package net.scdn.qf.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 北冥有熊
* 2018年11月8日
*/
public class Test {
public static void main(String[] args) {
Person p = new Person();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (p.count<=20) {
p.inMoney();
}
}
},"郭靖").start();
new Thread(new Runnable() { //匿名内部类
@Override
public void run() {
// TODO Auto-generated method stub
while (p.count<=20) {
p.outMoney();
}
}
},"黄蓉").start();
}
}
class Person{
private String name;
private boolean haveMoney;
int count = 1;
Lock lock = new ReentrantLock(); //创建一个锁对象
Condition manCondition = lock.newCondition(); //创建男人线程对象
Condition womanCondition = lock.newCondition(); //创建女人线程对象
//男人挣钱
public void inMoney() {
lock.lock();//上锁
try {
while (haveMoney) {//当有钱的时候等待
try {
manCondition.await(); //男方线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
name = "男人";
if (count<=20) {
System.out.println(Thread.currentThread().getName()+name+"--挣钱");
}
haveMoney = !haveMoney; //转变为没钱
womanCondition.signal(); //唤醒女方线程
} finally {
lock.unlock();//释放锁
}
}
//女人花钱
public void outMoney() {
lock.lock();//上锁
try {
while (!haveMoney) {
try {
womanCondition.await(); //女方线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}name = "女人";
if (count<=20) {
System.out.println(Thread.currentThread().getName()+name+"----花钱");
}
count++;
haveMoney = !haveMoney;
manCondition.signal(); //唤醒男方线程
}finally {
lock.unlock();//释放锁
}
}
}
以上这总供求供求关系也被称为"生产者消费者模式"。
线程死锁
线程间抢占资源导致,死锁不能解决,只能防止
死锁的前提:
1:资源匮乏
2:多个线程都来操作这个资源,有多个锁
3:锁之间有使用对方资源的情况,必须有两把锁以上,而且要锁中有锁 你中有我 我中有你。
关于死锁,在面试的时候面试官可能让你写一个死锁。
例:
package net.scdn.qf.test;
/**
* @author 北冥有熊
* 2018年11月8日
*/
public class Test01 {
public static void main(String[] args) {
DeadLock dLock = new DeadLock();
new Thread(dLock,"AA").start();
new Thread(dLock,"BB").start();
new Thread(dLock,"CC").start();
new Thread(dLock,"DD").start();
}
}
class DeadLock implements Runnable{
private static final Object OBJECT1 = new Object();
private static final Object OBJECT2 = new Object();//创建两个锁对象
boolean isup;
@Override
public void run() {
// TODO Auto-generated method stub
deadLock();
}
public void deadLock() {
while (true) {
if (isup) {
//锁一套锁二
synchronized(OBJECT1) {
System.out.println(Thread.currentThread().getName()+"--if---OBJECT1---");
synchronized(OBJECT2) {
System.out.println(Thread.currentThread().getName()+"--if---OBJECT2---");
}
}
}else {
//锁二套锁一
synchronized(OBJECT2) {
System.out.println(Thread.currentThread().getName()+"--else---OBJECT2---");
synchronized(OBJECT1) {
System.out.println(Thread.currentThread().getName()+"--else---OBJECT1---");
}
}
}
isup = !isup;
}
}
}
结果:死锁
AA--else---OBJECT2---
AA--else---OBJECT1---
AA--if---OBJECT1---
DD--else---OBJECT2---
分析:
刚一开始,ABCD四个线程进行if判断,都进入else语句,首先线程A抢到OBJECT1锁对象,这是BCD线程再锁外等待。线程A进去,执行第一天输出语句,紧接着顺利进入OBJECT1锁对象,在执行第二条输出语句,再接着A线程释放锁1,再接着释放锁2.就在刚释放锁2后,刚开始在外等待的BCD线程中的D线程抢到锁2对象,进入锁2.与此同时,A线程再进行if判断,并进入锁1,此时锁1呈关闭状态,A在执行第三条输出语句,接着D线程执行第四条输出语句。就在这时发生了死锁状态。由于A将锁1锁住,线程D无法进入到else语句中的锁1,同时D线程进入到的锁1也被锁,在if语句中的线程A无法进入锁2。这时发生死锁。
有关线程死锁方面有一个很经典的故事叫"哲学家进餐"问题,小伙伴们可以参见我的另一篇博客。
连接地址:
停止线程
- 使用逻辑控制线程结束
- 强行叫醒正在沉睡(sleep、wait)的线程,再使用逻辑控制线程结束
例:
package net.csdn.qf.teat;
/**
* @author 北冥有熊
* 2018年11月8日
*/
public class Test03 {
public static void main(String[] args) {
//demo1(); //逻辑中断线程
demo2(); //停止sleep或wait的线程
}
public static void dmo1() {
Demo1 demo1 = new Demo1();
Thread thread = new Thread(demo1,"AA");
thread.start();
try {
Thread.sleep(5000); //让AA线程先运行一会儿,
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
demo1.num = 1001; //使用逻辑控制AA线程结束
System.out.println("主线程main---over---");
}
public static void demo2() {
Demo2 demo2 = new Demo2();
Thread thread2 = new Thread(demo2,"BB");
thread2.start();
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Thread.sleep(5000); //沉睡5秒后再叫醒已经熟睡的BB线程
thread2.interrupt();//强行叫醒正在沉睡的BB线程
} catch (Exception e) {
// TODO: handle exception
}
demo2.num = 1001; //控制BB线程结束
System.out.println("主线程main---over---");
}
}
class Demo2 implements Runnable{
int num = 1;
public void run() {
while (num<=100) {
System.out.println(Thread.currentThread().getName()+"---"+num++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(num==15) {
try {
System.out.println("BB线程开始睡觉");
Thread.sleep(100000000); //将其改为wait同理
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class Demo1 implements Runnable{
int num = 1;
public void run() {
while (num<1000) {
System.out.println(Thread.currentThread().getName()+"---"+num++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
守护线程:
守护线程(Daemon)是运行在后台的一种特殊线程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。使用setDaemon()方法将线程变为守护线程。
例:
package net.csdn.qf.teat;
/**
* @author 北冥有熊
* 2018年11月8日
*/
public class Test04 {
public static void main(String[] args) {
Demo4 demo4 = new Demo4();
Thread thread = new Thread(demo4,"CC");
thread.setDaemon(true); //将这个线程设置为守护线程,该方法必须在启动线程前调用。
thread.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程main---over---JVM退出");
}
}
class Demo4 implements Runnable{
int num = 1;
public void run() {
while(num<=100) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("---------"+num++);
}
}
}
如果用户线程全部退出了,只剩下守护线程存在了,虚拟机也就退出了。
最后再来看一下join方法:
t1.join()---抢夺执行权---主方法会把执行权交给t1,坐等t1挂,t1挂了主方法才接着执行。可以用来中途加入需要执行的线程。
例:
package net.csdn.qf.teat;
/**
* @author 北冥有熊
* 2018年11月8日
*/
public class Test05 {
public static void main(String[] args) {
JoinDemo joinDemo=new JoinDemo();
Thread t1=new Thread(joinDemo);
t1.start();
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
if (i==5) {
try {
t1.join();//抢夺执行权
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"线程执行---"+i);
}
}
}
class JoinDemo implements Runnable {
public int num=10;
public void run() {
// TODO Auto-generated method stub
while (num<50) {
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(("----AAA线程执行---"+num));
}
}
}
结果:
main线程执行---0
----AAA线程执行---10
main线程执行---1
----AAA线程执行---10
main线程执行---2
----AAA线程执行---10
main线程执行---3
----AAA线程执行---10
main线程执行---4
----AAA线程执行---10
----AAA线程执行---10
----AAA线程执行---10
----AAA线程执行---10
----AAA线程执行---10
----AAA线程执行---10
----AAA线程执行---10
----AAA线程执行---10
很明显,在当两个线程同时运行时,中途main线程将执行权交给了AAA线程。
本博客持续更新中.......!