一、死锁
死锁:多个线程之间,由于针对多个对象进行上锁,导致的多个线程之间,需要相互等待对方释放对象的锁的过程
1.1 死锁产生的条件
互斥使用:针对同一个资源,提供了互斥锁(synchronized)
不可剥夺:当线程占用某一个对象锁以后,不可能被其他线程进行剥夺
请求和保持:当进程因请求资源而阻塞时,对已获得的资源保持不放。
相互等待:a线程需要等待b线程的释放,b线程需要等待a线程的释放
eg:
package com.woniuxy.java25.study.deadLock;
/**
* 定义一个死锁的任务
*
* @author Administrator
*
*/
public class DeadLockTask implements Runnable {
private static Object o1 = new Object();
private static Object o2 = new Object();
// 1-先锁o1 再锁o2 2-先锁o2 再锁o1
private int flag;
public DeadLockTask(int flag) {
super();
this.flag = flag;
}
@Override
public void run() {
// TODO Auto-generated method stub
if (flag == 1) {
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + "锁住了 o1");
// 休眠1S
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 试图去锁o2
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + "锁住了 o2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + "锁住了 o2");
// 休眠1S
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 试图去锁o1
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + "锁住了 o1");
}
}
}
}
}
package com.woniuxy.java25.study.deadLock;
public class MainEnter {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1 = new Thread(new DeadLockTask(1));
Thread t2 = new Thread(new DeadLockTask(2));
//开启t1 t2
t1.start();
t2.start();
}
}
1.2 死锁的原因与解决
死锁的原因
死锁的发生多半是因为程序设计不佳所造成的,主要原因是来自于对象锁无法获得
死锁的解决
修正你的代码逻辑,让所有的线程都按照相同的顺序进行针对对象上锁,即可解决
package com.woniuxy.java25.study.deadLock;
/**
* 定义一个死锁的任务
*
* @author Administrator
*
*/
public class DeadLockTask implements Runnable {
private static Object o1 = new Object();
private static Object o2 = new Object();
public DeadLockTask() {
super();
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + "锁住了 o1");
// 休眠1S
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 试图去锁o2
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + "锁住了 o2");
}
}
}
}
package com.woniuxy.java25.study.deadLock;
public class MainEnter {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1 = new Thread(new DeadLockTask());
Thread t2 = new Thread(new DeadLockTask());
//开启t1 t2
t1.start();
t2.start();
}
}
二、线程间的通讯
线程之间的数据传输,可以使用某个对象进行数据传输,但是线程之间的状态变化,这里就可能会使用到wait() notify() notifyAll()
wait() notify() notifyAll(),均来自于Object类
wait() 可以让线程处于等待式阻塞状态
notify() 可以让线程去唤醒处于 等待式阻塞状态的线程
notifyAll 可以让线程去唤醒,所有处于等待式阻塞状态的线程
2.1 不使用wait() notify() notifyAll()的情况
package com.woniuxy.java25.study.communicate;
import java.io.Serializable;
/**
* 消息类
* @author Administrator
*
*/
public class InformationBean implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1401842938475530654L;
/**
* 消息
*/
private String message;
public InformationBean() {
super();
// TODO Auto-generated constructor stub
}
public InformationBean(String message) {
super();
this.message = message;
}
public synchronized String getMessage() {
return message;
}
public synchronized void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "InformationBean [message=" + message + "]";
}
}
package com.woniuxy.java25.study.communicate;
import java.util.UUID;
/**
* 产生消息
* @author Administrator
*
*/
public class ProducerTask implements Runnable {
private InformationBean msg;
public ProducerTask(InformationBean msg) {
super();
this.msg = msg;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
//间隔2S产生一次消息!!!!
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String str = UUID.randomUUID().toString();
//一个36位的随机字符串
msg.setMessage(str);
System.out.println(Thread.currentThread().getName() + "产生的消息是:" + str);
}
}
}
package com.woniuxy.java25.study.communicate;
/**
* 消息的消费者
* @author Administrator
*
*/
public class ConsumerTask implements Runnable {
private InformationBean msg;
public ConsumerTask(InformationBean msg) {
super();
this.msg = msg;
}
@Override
public void run() {
// TODO Auto-generated method stub
//获取消息
while(true) {
System.out.println(Thread.currentThread().getName() + "接收的消息是:" + msg.getMessage());
}
}
}
package com.woniuxy.java25.study.communicate;
/**
* 主启动类
* @author Administrator
*
*/
public class MainEnter {
public static void main(String[] args) {
// TODO Auto-generated method stub
//实例化
InformationBean msg = new InformationBean();
Thread t1 = new Thread(new ProducerTask(msg));
Thread t2 = new Thread(new ConsumerTask(msg));
//启动
t1.start();
t2.start();
}
}
执行结果是:
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
消费者,这边一直在不断 输出内容!!!
而我们的需求是:生产者,产生一条,消费者,才消费一条
2.2 使用wait() notify() notifyAll()的情况
package com.woniuxy.java25.study.communicate;
import java.io.Serializable;
/**
* 消息类
* @author Administrator
*
*/
public class InformationBean implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1401842938475530654L;
/**
* 消息
*/
private String message;
public InformationBean() {
super();
// TODO Auto-generated constructor stub
}
public InformationBean(String message) {
super();
this.message = message;
}
public synchronized String getMessage() {
//让线程等待
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return message;
}
public synchronized void setMessage(String message) {
this.message = message;
//唤醒
// notify();
notifyAll();
}
@Override
public String toString() {
return "InformationBean [message=" + message + "]";
}
}
其他代码不做更改
执行结果:
Thread-1接收的消息是:d7c95585-45e5-4b1c-8f84-a1cc2255f2f5
Thread-0产生的消息是:d7c95585-45e5-4b1c-8f84-a1cc2255f2f5
Thread-0产生的消息是:b75412e2-f750-4f00-83ea-34e8c373e6b8
Thread-1接收的消息是:b75412e2-f750-4f00-83ea-34e8c373e6b8
Thread-0产生的消息是:1c60436f-cf80-4164-8164-858cff9fbd0f
Thread-1接收的消息是:1c60436f-cf80-4164-8164-858cff9fbd0f
达到了产生一条消息,读取一条消息的目的
2.3 sleep()和 wait() 的区别(面试题)
1、sleep() 是Thread类的静态方法,代表是让当前线程休眠
wait() 是Object类提供的,代表是让当前线程处于等待
2、sleep()休眠一定时间,会自动唤醒,并且让线程出于Rannable就绪状态,
而wait()必须需要其他线程来唤醒(notify() notifyAll())
3、sleep()在休眠期间,不会释放线程所持有的内存资源,但是会释放CPU的执行权,wait() 在等待期间,会释放线程所持有的内存资源,同样也会释放CPU的执行权
三、Queue
栈:先进后出
Queue:先进先出
package com.woniuxy.java25.study.queue;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
public class QueueStudy {
public static void main(String[] args) {
//ArrayBlockingQueue 是线程安全的
//rabbitmq == queue队列
//创建一个队列,并指定队列的大小 500
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(500);
for(int i = 0; i < 500; i ++) {
queue.add(UUID.randomUUID().toString());
}
//遍历(1)
queue.forEach(e -> System.out.println(e));
System.out.println(queue.size());
//遍历(2)
//迭代器
//检索(不删除)
//从队列的头部开始,获取第一个元素
System.out.println(queue.peek());
System.out.println(queue.size());
//检索(删除)
//从队列的头部开始,获取第一个元素
System.out.println(queue.poll());
System.out.println(queue.size());
}
}
四、线程池
目前使用Thread 和 Runable这种方式,这种方式最大的毛病,就是会产生多个线程。计算机同时支持线程的数据是有限,它受到JVM内存限制。
这个时候,往往就需要线程池的支持:因为线程池可以重复利用线程。
4.1 线程池的执行原理
任务交给线程池后,它可以自动分配线程给任务,并且任务执行完毕之后,它会自动将线程归还到线程池中。 ——未来所学的:对象池,连接池,都是同样的原理。
4.2 线程池的提供类
线程池所在的包:java.util.concurrent
线程池主要使用的类:Executors ThreadPoolExecutor
这2个类,都可以产生线程池,区别在于:Executors 线程量不高的,需要快捷使用线程的;ThreadPoolExecutor 线程量很高的
Executors
package com.woniuxy.java25.study.executors;
/**
* 定义任务(1)
* 如果线程,不需要返回一个结果,那么就需要使用Runnable接口
* @author Administrator
*
*/
public class SayTask implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i < 10; i ++) {
System.out.println("我喜欢你!!!!");
}
}
}
package com.woniuxy.java25.study.executors;
import java.util.concurrent.Callable;
/**
* 定义任务(2)
* 如果线程,需要返回一个结果,那么就需要使用Callable接口
* @author Administrator
*
*/
public class TalkTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
for(int i = 0; i < 10; i ++) {
System.out.println("我喜欢你!!!!");
}
//返回执行的次数
return 10;
}
}
package com.woniuxy.java25.study.executors;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorsStudy {
public static void main(String[] args) {
// TODO Auto-generated method stub
//产生一个内置了10根线程的线程池
ExecutorService es = Executors.newFixedThreadPool(10);
// //产生一个不限量的线程的线程池
// ExecutorService es = Executors.newCachedThreadPool();
// //产生一个内置了1根线程的线程池
// ExecutorService es = Executors.newSingleThreadExecutor()
Future future = es.submit(new SayTask());
try {
System.out.println(future.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//关闭线程池
es.shutdown();
}
}
4.3 Runnable 和Callable的区别
1、都是用来定义任务的,但是Runnable 不返回任务的结果;Callable ,返回任务的结果
2、Callable定义的任务,在线程执行时是有序
package exercise04;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorsStudy {
public static void main(String[] args) {
System.out.println("主线程开始!!!!!");
study02();
System.out.println("主线程结束!!!!!");
}
private static void study02() {
// TODO Auto-generated method stub
// 产生一个内置了10根线程的线程池
ExecutorService es = Executors.newFixedThreadPool(10);
Future<Integer> re01 = es.submit(() -> {
System.out.println(10);
return 10;
});
Future<Integer> re02 = es.submit(() -> {
System.out.println(20);
return 20;
});
Future<Integer> re03 = es.submit(() -> {
System.out.println(30);
return 30;
});
Future<Integer> re04 = es.submit(() -> {
System.out.println(40);
return 40;
});
try {
System.out.println(re01.get());
System.out.println(re02.get());
System.out.println(re03.get());
System.out.println(re04.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 关闭线程池
es.shutdown();
}
}
4.4 面试题(经典)
主线程-包含10根子线程,问你?一般来讲线程的执行顺序是无序的,而且是获取不到返回结果,问你:你如何保证 主线程 在所有的子线程执行完毕之后,方可继续执行,而且要输出子线程的结果
答案:使用Callable定义任务,使用Future 接收任务的结果。
五、守护线程
守护线程,又叫后台线程,它是运行线程内部的线程,它的特点是:外部线程,运行完毕了,它运行不完毕都直接停止。
隐藏在线程后面的线程,就是后台线程
后台线程 应用场景
GC时: GC垃圾回收线程
迅雷: 下载内容(可以使用)
上传:……
package com.woniuxy.java26.study.daemon;
/**
* 守护线程
* @author Administrator
*
*/
public class DaemonStudy {
public static void main(String[] args) {
System.out.println("主线程开启!!!!");
/**
* 外部线程
*/
Thread t1 = new Thread(()->{
for(int i = 0; i < 10; i ++) {
System.out.println("明天放假!!!!");
}
Thread it = new Thread(() ->{
while(true) {
System.out.println("后天也放假,而且还没有作业!!!!");
}
}) ;
//设置后台线程
it.setDaemon(true);
//开启内部线程
it.start();
});
t1.start();
System.out.println("主线程结束!!!!");
}
}
六、定时任务(重要)
有需要循环一定时间之后,需要做什么事情,可以使用定时任务。
会有一些定时框架,底层原理就是如下内容
package com.woniuxy.java26.study.daemon;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* 定时任务
* @author Administrator
*
*/
public class TimeStudy {
public static void main(String[] args) {
System.out.println("主线程开启!!!");
//时间类(定义定时任务,定义预安排任务)
Timer timer = new Timer();
//制定计划(间隔1秒钟,需要做任务)
//0 代表延迟时间 单位:毫秒
//1000 间隔1S时间
// timer.schedule(new TimerTask() {
//
// @Override
// public void run() {
// // TODO Auto-generated method stub
// System.out.println("当前的时间:" + new Date());
// }
//
// }, 0, 1000);
Date date = new Date();
//推算1分钟以后的时间
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, 1);
date = calendar.getTime();
//从固定的某个时刻开始,间隔1分钟去循环执行任务
// timer.schedule(new TimerTask() {
//
// @Override
// public void run() {
// // TODO Auto-generated method stub
// System.out.println("半夜,起床上厕所,给女朋友发个短信!!!!");
// }
// }, date, 60000);
//到达固定时间,只执行1次任务
timer.schedule(new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("半夜,起床上厕所,给女朋友发个短信!!!!");
}
}, date);
//停止定时任务
timer.cancel();
System.out.println("主线程结束!!!");
}
}
七、JVM内存与线程(面试题)
class文件,无法直接直接执行,需要通过类装载器加载到JVM之后,才可以进行执行。
装载完毕之后,JVM会自动划分5片空间用于存储数据:
- 线程共享区:方法区、堆
- 线程私有区:虚拟机栈、本地方法栈、程序计数器