JavaSE基础day23 死锁、线程生命周期、枚举
一. 多线程
(一) 死锁现象
1. 死锁的发生: 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程均处于等待状态,无法前往执行
public class DeadLock { public static void main(String[] args) { new Thread("线程1"){ public void run(){ synchronized ("A"){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "获取到A, 需要获取B"); synchronized ("B"){ System.out.println(Thread.currentThread().getName() + "获取到A和B"); } } } }.start(); new Thread("线程2"){ public void run(){ synchronized ("B"){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "获取到B, 需要获取A"); synchronized ("A"){ System.out.println(Thread.currentThread().getName() + "获取到A和B,啦啦啦啦"); } } } }.start(); } }
2. 死锁诊断(jstack工具的使用)
1) 打开命令运行窗口
2) 执行命令 : jps ,表示输出JVM中运行的进程状态信息,从而获取pid,即进程编号
3) 指定命令: jstack 指定进程pid, 查看某个Java进程内的某个线程堆栈信息
命令执行后的效果:
(二) 线程状态
1. 概述: 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。
2. 线程状态如下图:
线程状态 |
具体含义 |
NEW |
一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。 |
RUNNABLE |
当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。 |
BLOCKED |
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
WAITING |
一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。 |
TIMED_WAITING |
一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。 |
TERMINATED |
一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |
3. 线程状态(线程的生命周期)如下图:
(三) 线程池
3.1线程池概述
提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子,在该池子中存储很多个线程。
线程池存在的意义:
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务本身资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中成为空闲状态。等待下一次任务的执行。
3.2 Executors创建线程池
1. 概述 : JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。
2. 使用Executors中所提供的静态方法来创建线程池
(1) static ExecutorService newCachedThreadPool() 创建一个默认的线程池
(2) static ExecutorService newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
3. 提交线程任务:
(1) Future<?> submit(Runnable task):提交一个Runnable任务,返回一个Future对象,可以通过get方法获得线程运行的结果
(2) Future<?> submit(Callable<T> task):提交一个Callable任务,返回一个Future对象,可以通过get方法获得线程运行的结果
4. 关闭线程池的方法:
(1) void shutdown() : 关闭之后,已经提交的任务,执行完毕,后续不让提交任务了
(2) List<Runnable> shutdownNow() :立即关闭线程池,已经正在运行的任务,运行完毕,在队列中,正在等待运行的任务,放到 list集合当中返回回来
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool { public static void main(String[] args) { // 1. Executors: 类型是JDK提供,是一个创建线程池的工厂类, 主要功能创建出具有指定个数的线程池 // static newFixEdThreadPool(int 线程个数) // 2. 上述方法的返回值结果: ExecutorService, 类型主要功能就是可以操作线程池 ExecutorService es = Executors.newFixedThreadPool(3); // 3. 设计出一个Runnable线程任务 Runnable run1 = new Runnable(){ @Override public void run() { for(int i = 1; i <= 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }; Runnable run2 = new Runnable(){ @Override public void run() { for(int i = 1; i <= 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }; Runnable run3 = new Runnable(){ @Override public void run() { for(int i = 1; i <= 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }; Runnable run4 = new Runnable(){ @Override public void run() { for(int i = 1; i <= 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }; // 4. 将runnable线程任务提交到线程池执行 // ExecutorService中submit(Runnable able)功能: 将参数able线程任务提交到线程池执行 /* 1) 先检索线程池中是否有空闲线程 有: 执行线程任务 没有: 等待有空闲线程出现, 继续执行任务 */ es.submit(run1); es.submit(run2); es.submit(run3); es.submit(run4); // 5. 线程池使用完毕,代码不停止: 线程池等待继续接受任务,导致代码没有终止 // 因此为了解决上述问题, 在使用线程池结束之后,销毁线程池 // 1) 将所有提交的任务,执行完毕之后,停止线程池使用(销毁线程池) // es.shutdown(); // 2) shutdownNow(): 马上终止线程池,正在执行的线程,保证执行完毕; 已经提交的,没有开始执行的,不执行 es.shutdownNow(); } }
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolUse { public static void main(String[] args) { ExecutorService es = Executors.newCachedThreadPool(); Runnable run1 = new Runnable(){ @Override public void run() { for(int i = 1; i <= 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }; Runnable run2 = new Runnable(){ @Override public void run() { for(int i = 1; i <= 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }; Runnable run3 = new Runnable(){ @Override public void run() { for(int i = 1; i <= 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }; Runnable run4 = new Runnable(){ @Override public void run() { for(int i = 1; i <= 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }; Runnable run5 = new Runnable(){ @Override public void run() { for(int i = 1; i <= 5; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }; es.submit(run1); es.submit(run2); es.submit(run3); es.submit(run4); es.submit(run5); es.shutdown(); } }
二. 枚举的定义特点以及常用方法
(一) 枚举的概述
1. 为了间接的表示一些固定的值,Java就给我们提供了枚举,是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内
2. 枚举本质上来说就是对象的内容和个数已经确定了的类
枚举项:就是枚举类当中的一个一个的被确定了的对象
例如:星期类,只有周一到周日7个对象; 月份类,只有1-12月这12个对象;线程的状态Thead.State,只有6种状态,这些都可以使用枚举类型
(二) 枚举定义格式
1. 枚举类型使用 enum 关键字定义
定义格式:
public enum 枚举名{
枚举项1,枚举项2,枚举项3;
}
2. 枚举的使用场景
有些情况下,类型中可以创建的对象的个数是固定的,就可以使用枚举类型
3. 枚举类型,源文件.java , 编译后的文件.class
(三) 枚举的特点
1. 所有枚举类都是Enum的子类
2. 我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
3. 每一个枚举项其实就是该枚举的一个对象, 枚举项写作方式: 枚举对象名称,枚举对象名称,...最后一个对象名称;
4. 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的内容,这个分号就不能省略。建议不要省略
5. 枚举也是一个类,也可以去定义成员变量
6. 枚举类可以有构造器,但必须是private的,它默认的也是private的.
7. 枚举类也可以有抽象方法,但是枚举项必须重写该方法
案例 : 使用枚举类型, 模拟星期使用, 星期类型一共可以创建出7个对象,表示星期一---星期日
模仿枚举类实现方式
public abstract class Weekday { private String name; // 1. 构造方法私有化: 目的为了防止除了Weekday类型之外的所有场景下, 无法new对象 private Weekday(String name){ this.name = name; } // 2. 在Weekday类型中创建出指定个数对象 public static final Weekday WEN = new Weekday("星期一"){ @Override public void show() { System.out.println("我是星期一"); } }; public static final Weekday TUE = new Weekday("星期二"){ @Override public void show() { System.out.println("我是星期二"); } }; public static final Weekday TRH = new Weekday("星期三"){ @Override public void show() { System.out.println("我是星期三"); } }; public String getName() { return name; } public void setName(String name) { this.name = name; } public abstract void show(); }
枚举类
/ 定义出一个枚举类型: 目的就是定义出具有指定个数对象的类型 public enum WeekdayEnum { // 1. 枚举类型中, 所有的枚举类型对象, 都以枚举项的形式定义出来即可 // 每一个枚举项, 都表示一个枚举类型对象 // 枚举项只需要将名称写出即可,枚举项必须在枚举类型第一行 // public static final WeekdayEnum WEN = new WeekdayEnum(); WEN("枚举星期一"){ @Override public void show() { System.out.println("枚举类型重写的星期一"); } },TUE("枚举星期二"){ @Override public void show() { System.out.println("枚举类型重写的星期二"); } },THR("枚举星期三"){ @Override public void show() { System.out.println("枚举类型重写的星期三"); } }; // 2. 枚举类型中可以定义出普通成员变量 private String name; // 3. 枚举类型中可以普通方法 public String getName() { return name; } public void setName(String name) { this.name = name; } // 4. 枚举类型中可以定义出构造方法,所有构造默认私有 /*private WeekdayEnum(){}*/ private WeekdayEnum(String name){ this.name = name; } // 5. 枚举类型中可以直接定义抽象方法: 但是这个抽象方法需要在枚举项中进行重写 public abstract void show(); }
测试类
public class Test { public static void main(String[] args) { Weekday w = Weekday.WEN; System.out.println(w);// com.ujiuye.enumdemo.Weekday@1b6d3586 System.out.println(w.getName()); // 1. 测试枚举项使用 WeekdayEnum w2 = WeekdayEnum.WEN; System.out.println(w2);// WEN System.out.println(w2.getName());// 枚举星期一 w2.show(); } }
}
}.start();
new Thread("线程2"){
public void run(){
synchronized
("B"){
try {
Thread.sleep(200);
} catch
(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "获取到B, 需要获取A");
synchronized
("A"){
System.out.println(Thread.currentThread().getName() + "获取到A和B,啦啦啦啦");
}
}
}
}.start();
}
}