目录
一、死锁
1.1 什么是死锁?
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
1.2 发生死锁的原因?
1.3 如何避免死锁产生?
编写一个将导致死锁的Java程序:A有画板,B有画笔,拿到对方的工具进行画画。
编写锁对象:
public class LockObject {
public static Object HuaBan = new Object();
public static Object HuaBi = new Object();
}
编写画板类:
public class HuaBan implements Runnable{
@Override
public void run() {
synchronized (LockObject.HuaBi){
System.out.println("A拿到画笔");
synchronized (LockObject.HuaBan){
System.out.println("A拿到了画板");
System.out.println("A开始画画");
}
}
}
}
编写画笔类:
public class HUaBi implements Runnable{
@Override
public void run() {
synchronized (LockObject.HuaBan){
System.out.println("B拿到画板");
synchronized (LockObject.HuaBi){
System.out.println("B拿到画笔");
System.out.println("B开始画画");
}
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
HuaBan huaBan = new HuaBan();
HUaBi hUaBi = new HUaBi();
Thread thread1 = new Thread(huaBan);
Thread thread2 = new Thread(hUaBi);
thread1.start();
thread2.start();
}
}
运行结果:
以上案例就导致了死锁现象。
如何避免死锁现象?
- 尽量不要使用锁嵌套;
- 不要使用锁,而使用安全锁(比如:java.util.concurrent下的类);
- 设置锁的超时时间。lock锁,到达指定时间没有获取锁,则不会再等待该锁。
使用第三种方法解决死锁案例:
public class HuaBan implements Runnable{
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (LockObject.HuaBi){
System.out.println("A拿到画笔");
synchronized (LockObject.HuaBan){
System.out.println("A拿到了画板");
System.out.println("A开始画画");
}
}
}
}
运行结果:
二、线程通信
等待:
- public final void wait()
- public final void wait(long timeout)
- 必须咋子对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,放入等待队列。
通知:
- public final void notify()
- public final void notifyAll()
2.1案例:
一张银行卡,小红取钱,小明存钱。
定义银行卡:
public class BankCard {
private double balance;//定义银行卡余额
private boolean flag;//true表示有钱, false表示没钱
public synchronized void save(double money) {
if (flag) {//有钱,进入等待队列
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
balance = balance + money;
System.out.println(Thread.currentThread().getName() + "往卡中存入" + money + "元,卡中余额" + balance + "元");
flag = true;
//唤醒等待队列中的线程
notify();
}
public synchronized void take(double money) {
if (flag == false) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
balance = balance - money;
System.out.println(Thread.currentThread().getName() + "从卡中取出" + money + "元,卡中余额" + balance + "元");
flag = false;
//唤醒等待队列中的线程
notify();
}
}
public class TakeRunnable implements Runnable{
private BankCard bankCard;
public TakeRunnable(BankCard card){
bankCard = card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bankCard.take(1000);
}
}
}
public class SaveRunnable implements Runnable{
private BankCard bankCard;
public SaveRunnable(BankCard card){
bankCard = card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bankCard.save(1000);
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
BankCard bankCard = new BankCard();
SaveRunnable save = new SaveRunnable(bankCard);
TakeRunnable take = new TakeRunnable(bankCard);
Thread t1 = new Thread(save, "小明");
Thread t2 = new Thread(take, "小红");
t2.start();
t1.start();
}
}
部分代码运行结果:
小明往卡中存入1000.0元,卡中余额1000.0元
小红从卡中取出1000.0元,卡中余额0.0元
小明往卡中存入1000.0元,卡中余额1000.0元
小红从卡中取出1000.0元,卡中余额0.0元
小明往卡中存入1000.0元,卡中余额1000.0元
小红从卡中取出1000.0元,卡中余额0.0元
2.2 Sleep和wait的区别?
- wait需要使用notify或notifyAll唤醒,而sleep到时间自动唤醒。
- wait来自于Object类中,sleep来自Thread类中
- wait会是否锁资源,sleep不会释放锁资源。
- wait必须放在同步代码中,而sleep可以放在任意位置。
三、线程的状态
3.1线程的状态
- NEW:新建状态。当new一个线程对象时进入该状态。
- RUNNABLE:运行状态。调用完start并获取cpu时间片进入运行状态。
- BLOCKED:堵塞状态,当没有获取锁资源时。
- WAITING:当执行了wait方法时,该线程进入等待状。
- TIME_WAITING:当执行sleep方法时,进入该状态。
- TERMINATED:终止。当线程执行完毕或出现异常中断。
四、线程池
4.1 线程池的概念
问题:
- 线程时宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
- 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
线程池:
-
线程容器,可设定线程分配的数量上限。
-
将预先创建的线程对象存入池中,并重用线程池中的线程对象。
-
避免频繁的创建和销毁。
4.2 线程池的原理
4.3创建线程池
(1)常用的线程池接口和类(所在包Java.util.concurrent):
- Executor:线程池的顶级接口。该接口中就存在一个方法。void execute(Runnable command);执行线程任务的方法。
- ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码。
-
1. shutdown():关闭线程池---当前线程池有还要任务,需要等任务完后后才会关闭。 2. shutdownNow(): 立刻关闭线程池。 3. isShutdown(): 是否属于关闭状态。 4. isTerminated(): 判断是否线程池终止了 5. submit(Callable<T> task): 执行线程任务。 submit(Runnable task);
- Executors工厂类:通过此类可以获得一个线程池。
- 通过newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量。
- 通过newCachedThreadPool() 获的动态数量的线程池,如不够则创建新的,没有上限
public class Test {
public static void main(String[] args) {
//获取固定长度的线程池。---阿里巴巴不建议使用Executors创建线程池
// ExecutorService executorService = Executors.newFixedThreadPool(5);
//创建单一线程池---池子中只有一个线程的对象。---适合任务的有序执行。
//ExecutorService executorService = Executors.newSingleThreadExecutor();
//可变线程池
//ExecutorService executorService = Executors.newCachedThreadPool();
//延时线程池
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("定时执行任务");
}
},3, TimeUnit.SECONDS);
// for (int i = 0; i < 200; i++) {
// executorService.submit(new Runnable() {
// @Override
// public void run() {
// System.out.println(Thread.currentThread().getName()+"--------------------");
// }
// });
// }
executorService.shutdown();
}
}
(2)自定义参数线程池
(1)没有无参构造函数 (2)构造函数私有化 int corePoolSize,核心线程数 int maximumPoolSize,最大的个数 long keepAliveTime,等待时间长 TimeUnit unit,时间单位--- BlockingQueue<Runnable> workQueue: 等待队列
public class Test {
public static void main(String[] args) {
BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
ExecutorService executorService = new ThreadPoolExecutor(2, 5, 10,TimeUnit.SECONDS, blockingQueue);
for (int i = 0; i < 19; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-------------");
}
});
}
}
}