第1章 多线程
1.1 串行、并发与并行
-
串行:程序一个一个的执行
-
并行:指两个或多个事件在同一时刻发生(同时执行)。
-
并发:指两个或多个事件在同一个时间段内发生(交替执行)。
1.2 线程与进程
-
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
-
线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
进程与线程的区别
-
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
-
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
线程调度:
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
-多线程并发执行任务时,本身根据抢占式调度,所以结果会出现随机性
- 谁获得cup的执行权,谁就执行,执行多久,由获得 “时间片”决定
多线程及其优势和劣势
多线程:一个进程中至少有一个主线程在执行任务,但是也允许有多个线程并发执行任务
优势:
- 多线程并发执行任务效率高
- 创建一个线程,总归比创建一个进程,开销是小的多
- 大型并发场景下,核心技术更支持使用多线程解决并发问题
劣势:
- 线程开的过多,消耗越大
- 多线程并发执行程序,造成资源的争抢,从而引发线程安全问题
1.3 创建线程的方式
1.3.1 extends Thread类(创建线程方式一)
1. 如何创建线程
1.1自定义一个类,类继承extends Thread类
1.2 重写run(),并指定执行的任务
1.3 通过new创建线程对象,并由start()启动正在的线程
2.优势
代码简洁,直接new进行创建,并start()启动即可
3.劣势
java中类是单继承的,一旦继承了Thread类,就不可以再继承其他类了
4.注意
不能多次启动同一个线程,会报出异常 java.lang.IllegalThreadStateException
5.代码如下:
public class ThreadDemo {
//打印输出线程信息
public static void main(String[] args) {
MyThread myThread = new MyThread("T1");
myThread.start(); //启动线程
//myThread.start(); 多次启动线程会出异常 java.lang.IllegalThreadStateException
}
}
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Thread thread = Thread.currentThread();
System.out.println(thread + " "+ i);
}
}
}
结果
Thread[T1,5,main] 0
Thread[T1,5,main] 1
Thread[T1,5,main] 2
Thread[T1,5,main] 3
Thread[T1,5,main] 4
Thread[T1,5,main] 5
Thread[T1,5,main] 6
Thread[T1,5,main] 7
Thread[T1,5,main] 8
Thread[T1,5,main] 9
可以使用Lambda简化代码
new Thread("T1"){
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread() + " " + i);
}
}
}.start();
结果和上面代码作用一样
1.3.2 implements Runnable接口(创建线程方式二)
1. 如何创建线程
1.1自定义一个类,类实现implements Runnable接口
1.2 重写run(),并指定执行的任务
1.3 通过new创建线程对象,传入任务对象,并由start()真正启动线程
2.优势
2.1避免单继承的弊端,在此处仅仅只是实现了一个接口,扩展性增强
2.2 线程池种,只能传入实现Runnable 或者 Callable的任务
3.劣势
代码相对第一种直接new Thread方式,较为繁琐
4.注意
允许多个线程,去运行同一任务。
5.代码如下:
public class RunnableDemo {
public static void main(String[] args) {
//创建任务
MyRunnable myRunnable = new MyRunnable();
//创建线程,并传入任务,启动线程
new Thread(myRunnable,"t1").start();
new Thread(myRunnable,"t2").start();
//主线程执行如下任务
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
结果(具有随机性)
main:0
t1:0
t1:1
t1:2
t1:3
t1:4
t2:0
main:1
main:2
main:3
main:4
t2:1
t2:2
t2:3
t2:4
lambda简化
//使用Lambda简化代码
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
},"T1").start();
//对lambda代码再次简化
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
},"T2").start();
Thread类实际上也是实现了Runnable接口的类。
实现Runnable接口比继承Thread类所具有的优势:
-
适合多个相同的程序代码的线程去共享同一个资源。
-
可以避免java中的单继承的局限性。
-
增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
-
线程池只能放入实现Runable或Callable类线程。
1.3.3 implements Callable接口(创建线程方式三)
1. 如何创建线程
1.1自定义一个类,类实现implements Callable接口
1.2 重写call(),并指定执行的任务
1.3 创建callabe对象
1.4 创建FutureTask任务对象,并传入callable对象
1.5 通过new创建线程对象,传入FutureTask任务对象,并由start()真正启动线程
1.6 通过FutureTask获取任务执行完毕的返回值
2.优势
2.1避免单继承的弊端,在此处仅仅只是实现了一个接口,扩展性增强
2.2 线程池种,只能传入实现Runnable 或者 Callable的任务
3.劣势
代码相对第一种直接new Thread方式,较为繁琐
4.注意
Callable 可以携带线程执行的返回值。
5.代码如下:
public class CallableDemo {
public static void main(String[] args) {
//创建任务
MyCallable myCall = new MyCallable();
//指定任务
FutureTask task1 = new FutureTask(myCall);
FutureTask task2 = new FutureTask(myCall);
//创建线程
new Thread(task1,"线程一").start();
new Thread(task2,"线程二").start();
//获取当前任务的返回值
String string1 = null;
String string2 = null;
try {
string1 = task1.get().toString();
string2 = task2.get().toString();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
System.out.println("string1 : " + string1);
System.out.println("string2 : " + string2);
}
}
class MyCallable implements Callable<String> {
//制定执行的任务,可以携带返回值。
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return Thread.currentThread().getName() + ":" + sum;
}
}
结果
线程一:0
线程一:1
线程一:2
线程一:3
线程一:4
线程二:0
线程二:1
线程二:2
线程二:3
线程二:4
string1 : 线程一:10
string2 : 线程二:10
//lambda简化
new Thread(new FutureTask<String>(()->{
int sum = 0;
for (int i = 0; i < 10; i++) {
sum+=i;
System.out.println(Thread.currentThread().getName()+":"+i);
}
return Thread.currentThread().getName() + " ,sum = " + sum;
}),"T1").start();
1.4 Thread类的构造方法和常用API
线程开启我们需要用到了java.lang.Thread
类,API中该类中定义了有关线程的一些方法,具体如下:
构造方法:
-
public Thread()
:分配一个新的线程对象。 -
public Thread(String name)
:分配一个指定名字的新的线程对象。 -
public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。 -
public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字。第一个参数是任务对象,第二个参数是线程名字
常用方法:
-
public String getName()
:获取当前线程名称。 -
public void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。 -
public void run()
:此线程要执行的任务在此处定义代码。 -
public static
native sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),线程沉睡,一旦调用sleep,当前正在执行的线程是否时间片,进入沉睡状态;此时指定的沉睡时间,是它的最短不执行任务的时间;因为时间结束后,是需要再次争抢时间片,只有抢占到时间片才可以继续执行任务 -
public static native void yield(); //线程退让,一点调用yield,当前线程会退让,退让给优先级更高的线程。“假意退让”,确实会释放时间片,然后立马参与争抢时间片,一旦抢到时间片则会继续执行任务
-
public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。 -
public final void setPriority(int newPriority):设置优先级(1-10)
-
public final int getPriority(): 获取优先级
-
线程优先级
-
public final static int MIN_PRIORITY=1;
-
public final static int MORM_PRIORITY=5;
-
public final static int MAX_PRIORITY=10;
-
-
public final void join() throws InterruptedException 无限等待(只能等调用者执行结束方可)
-
public final synchronized void join(long millis) throws InterruptedException 有计时等待(等待的时间结束即可)
第2章 线程安全
2.1 线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
我们通过一个案例,演示线程的安全问题:
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票),需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟。
代码
//模拟票
public class Ticket implements Runnable{
private int ticket = 100;
//卖票操作
@Override
public void run() {
while(true){
if (ticket > 0){
//ticket>0说明有票
//出票操作
try { //这里用sleep模拟一下出票时间
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖" + ticket--);
}
}
}
}
//测试类
class Test{
public static void main(String[] args) {
//创建线程对象
Ticket ticket = new Ticket();
//创建三个对象,表示有三个车站
Thread t1 = new Thread(ticket,"窗口1");
Thread t2 = new Thread(ticket,"窗口2");
Thread t3 = new Thread(ticket,"窗口3");
//开始卖票
t1.start();
t2.start();
t3.start();
}
}
结果:只有末尾的一部分数据表示线程不安全
窗口2正在卖2
窗口3正在卖1
窗口2正在卖0
窗口1正在卖-1
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
2.2 线程同步
线程同步是为了解决线程安全问题。
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。
根据案例简述:
窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
那么怎么去使用呢?
有三种方式完成同步操作:
-
同步代码块。
-
同步方法。
-
锁机制。
2.3 同步代码块
-
同步代码块:
synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
-
锁对象 可以是任意类型。
-
多个线程对象 要使用同一把锁。
代码
* //面试题: 线程安全 和 线程同步
* 一。线程安全
* 多线程并发访问同一资源时,可能会发生争抢资源的问题,从而引发线程安全的问题
*
* "电影院售票案例":多个人一起买票,有可能出现买到同一张票,也有可能出现负数的票,这些都是线程不安求
*
* 二.线程同步
* 线程同步,是解决线程安全的一种机制
* 出现 “有序”,“等待”的现象
*
* 三。确保线程同步三种方式
* 方式一:同步代码块
* 1.语法
* synchroized(对象锁){//确保线程同步的代码}
* 2.对象流:任何一个对象都是可以作为对象锁,包括类
* 2.1 this 作为对象所,谁来调用,谁就是对象锁
* 使用同一把锁(s1,s1)作为对象锁,此时会出现等待现象,
* 使用两把锁(s1,s2)作为对象流,会出现争抢的现象
* 2.2类.class类作为对象锁,对象都是同一个类型,对他来说都是同一把锁
* 使用同一把锁(s1,s1)作为对象锁,此时会出现等待现象,
* * 使用两把锁(s1,s2)作为对象流,会出现等待的现象
* 2.3 字符串“A”再字符串常量池分配内存空间
* 使用同一把锁(s1,s1)作为对象锁,此时会出现等待现象,
* * 使用两把锁(s1,s2)作为对象流,会出现等待的现象
* 2.4 private byte[] lock = new byte[0];消耗最小,当作属性进行使用时等价于this情况
* 使用同一把锁(s1,s1)作为对象锁,此时会出现等待现象,
* 使用两把锁(s1,s2)作为对象流,会出现争抢的现象
* 方式二:同步方法
* 方式三:lock锁+ReentrantLock
*/
public class SyncDemo1 {
private Byte[] lock = new Byte[0];
public void method1(){
synchronized (/*this*/ /*SyncDemo1.class*/ /*"A"*/ lock){
for (int i = 0; i <10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public void method2(){
synchronized (/*this*/ /*SyncDemo1.class*/ /*"A"*/ lock){
for (int i = 0; i <10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public static void main(String[] args) {
SyncDemo1 s1 = new SyncDemo1();
SyncDemo1 s2 = new SyncDemo1();
new Thread(()->s1.method1(),"线程一").start();
//new Thread(()->s1.method1(),"线程二").start(); //一把锁的情况
new Thread(()->s2.method2(),"线程二").start(); //两把锁的情况
}
}
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
使用同步代码块解决代码:
//模拟票
public class Ticket implements Runnable{
private int ticket = 100;
//卖票操作
@Override
public void run() {
synchronized (this){
while(true){
if (ticket > 0){
//ticket>0说明有票
//出票操作
try { //这里用sleep模拟一下出票时间
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖" + ticket--);
}
}
}
}
}
//测试类
class Test{
public static void main(String[] args) {
//创建线程对象
Ticket ticket = new Ticket();
//创建三个对象,表示有三个车站
Thread t1 = new Thread(ticket,"窗口1");
Thread t2 = new Thread(ticket,"窗口2");
Thread t3 = new Thread(ticket,"窗口3");
//开始卖票
t1.start();
t2.start();
t3.start();
}
}
当使用了同步代码块后,上述的线程的安全问题,解决了。
2.4 同步方法
-
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
public synchronized void/数据类型 方法名(参数列表){//方法体 }
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
/**
* @TODO
* @Author Chushaoyang
* 2025/3/31
* //面试题: 线程安全 和 线程同步
* 一。线程安全
* 多线程并发访问同一资源时,可能会发生争抢资源的问题,从而引发线程安全的问题 *
* 二.线程同步
* 线程同步,是解决线程安全的一种机制
* 出现 “有序”,“等待”的现象
* 三。确保线程同步三种方式
* 方式一:同步代码块
* 方式二:同步方法
* 1.语法
* public synchronized void/数据类型 方法名(参数列表){//方法体 }
* 2.1 成员方法
* 类似于 this 作为对象所,谁来调用,谁就是对象锁
* * 使用同一把锁(s1,s1)作为对象锁,此时会出现等待现象,
* * 使用两把锁(s1,s2)作为对象流,会出现争抢的现象
* 2.2 静态方法
* 类似于 .class类作为对象锁,对象都是同一个类型,对他来说都是同一把锁
* * 使用同一把锁(s1,s1)作为对象锁,此时会出现等待现象,
* * * 使用两把锁(s1,s2)作为对象流,会出现等待的现象
* 方式三:lock锁+ReentrantLock
*/
public class SyncDemo2 {
private Byte[] lock = new Byte[0];
public synchronized static void method1(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public synchronized static void method2(){
for (int i = 0; i <10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
SyncDemo2 s1 = new SyncDemo2();
SyncDemo2 s2 = new SyncDemo2();
new Thread(()->s1.method1(),"线程一").start();
new Thread(()->s1.method1(),"线程二").start(); //一把锁的情况
//new Thread(()->s2.method2(),"线程二").start(); //两把锁的情况
}
}
2.5 Lock锁
java.util.concurrent.locks.Lock
机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大
lock接口
* 1.java.util.concurrent.locks.lock 接口
* java.util.concurrent.locks.ReentrantLock 实现类 - 可重入锁
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
-
public void lock()
:加同步锁。 -
public void unlock()
:释放同步锁。 -
boolean trylock() : 尝试获取锁
-
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 指定时间内,尝试获取锁
Lock锁
/**
* @TODO
* @Author Chushaoyang
* 2025/3/31
* lock接口
* 1.java.util.concurrent.locks.lock 接口
* java.util.concurrent.locks.ReentrantLock 实现类
*
* 2.API
* void lock(); 获取锁
* void unlock(); 释放锁
* boolean tryLock(); 尝试获取锁
* boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 指定时间内,尝试获取锁
* 3.作为属性
* private final Lock lock = new ReentrantLock(); 成员属性
* 使用同一个对象则lock锁也是同一个,此时会出现“等待”现象,
* 使用两个对象则lock锁不是同一个,此时会出现“争抢”现象
* private final static Lock lock = new ReentrantLock(); //静态属性
* 使用同一个对象则lock锁也是同一个,此时会出现“等待”现象,
* 使用两个对象则lock锁不是同一个,此时会出现“等待”现象
* 4.注意
* 必须再finally中释放资源,确保无论释放正常执行任务,都可以释放锁,从而避免因为出现异常而导致死锁问题
*/
public class LockDemo {
//创建锁对象 可重入锁
private final Lock lock = new ReentrantLock();
//private static final Lock lock = new ReentrantLock();
//测试lock() & unlock
public void testLock(){
//获取锁
lock.lock();
System.out.println(Thread.currentThread().getName() + "获取锁");
try{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
TimeUnit.SECONDS.sleep(1);
}
}catch (Exception e){
e.printStackTrace();
}finally {
//必执行 释放锁
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
//测试tryLock $ unlock()
public void testTryLock(){
if (lock.tryLock()){
try {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
TimeUnit.SECONDS.sleep(1); //沉睡1秒
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}else {
System.out.println(Thread.currentThread().getName() + "没有获取到锁,让其他线程先执行");
}
}
public static void main(String[] args) {
LockDemo demo = new LockDemo();
// new Thread(() -> demo.testLock(),"线程1").start();
// new Thread(() -> demo.testLock(),"线程2").start();
new Thread(()->demo.testTryLock(),"线程1").start();
new Thread(()->demo.testTryLock(),"线程2").start();
}
}
2.6 生产者和消费者
/**
* @TODO 消费者
* @Author Chushaoyang
* 2025/4/1
*/
public class Consumer implements Runnable{
private House house;
public Consumer(House house) {
this.house = house;
}
//售卖
@Override
public void run() {
for (int i = 0; i < 15; i++) {
house.sale();
}
}
}
/**
* @TODO 消费者
* @Author Chushaoyang
* 2025/4/1
*/
public class Prodect implements Runnable{
private House house;
public Prodect(House house) {
this.house = house;
}
//上架商品
@Override
public void run() {
for (int i = 0; i < 15; i++) {
house.add();
}
}
}
/**
* @TODO 仓库
* @Author Chushaoyang
* 2025/4/1
*
* 生产者和消费者模型
*1.等待唤醒机制
* 2.基于线程安全的情况下,才需要考虑线程通讯
* 3.wait()和notifyAll(),必须搭配synchronized使用,否则抛出异常
* 4.对象锁一定要唯一
* 5.线程通信必须保证对象锁的唯一性
* 6.可能出现虚假唤醒的现象
* 虚假唤醒 的问题
* 消费者或者生产者进入等待状态后,直至被唤醒,此时不会再进行条件if判断是否满足;将会继续往下执行;
* 解决“虚假唤醒问题"
* 判断条件,必须放在while循环里,万一进入等待状态后,被唤醒时,可以再次进行
*/
public class House {
//容量的限定 10件商品
private int num = 10;
//add上架
public void add(){
synchronized (this){
while (num >= 10){
System.out.println("仓库已满,生产者进入等待现象");
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("当前生产力: " + ++num);
//一旦生成,就可以唤醒消费者
this.notifyAll();
}
}
//sale 售卖
public void sale(){
synchronized (this){
//仓库已空,进入等待状态
while (num <= 0){
System.out.println("仓库已空,消费者进入等待状态");
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("当前消费了:" + num--);
System.out.println("一旦产生消费,则可以唤醒生产者");
this.notifyAll();
}
}
}
/**
* @TODO 测试类
* @Author Chushaoyang
* 2025/4/1
*/
public class Test {
public static void main(String[] args) {
House house = new House();
//任务
Prodect prodect = new Prodect(house);
Consumer consumer = new Consumer(house);
//线程,并启动线程
new Thread(prodect,"生产者A").start();
new Thread(consumer,"消费者B").start();
}
}
2.7 面试题 公平锁和非公平锁
非公平锁:
1.NonFairSync
2.先占先得
3.最大的缺点:锁饥饿现象
公平锁
1.FairSync 按序排队公平锁
2.判断同步队列是否还有先驱节点的存在 if(!hasQueuedPredecessors())
3.如果没有先驱节点才能获取锁
/**
* @TODO 公平锁和非公平锁
* @Author Chushaoyang
* 2025/4/1
*
* synchronized 本身就是非公平锁
* lock - ReentrantLock 默认是非公平锁,也可以构造参数中传入true代表公平锁
*
* 按序排序公平锁,就是判断同步队列是否还有先驱节点的存在,如果没有先驱节点才获取锁,
* 先占先得非公平锁,是不管这个事的,只要能抢到同步状态就可以
*
* 非公平锁:NonfairSync 最大缺点:锁饥饿现象
* 公平锁:FairSync if(!hasQueuedPredecessors()) 雨露均沾
*/
public class Ticket {
private int number = 30;
//默认传入的是false,使用的是非公平锁
//传入的是true,使用公平锁
ReentrantLock lock = new ReentrantLock(true);
public void sale(){
lock.lock();
try{
if (number > 0){
System.out.println(Thread.currentThread().getName() + "卖出第:\t" +(number--) + "\t还剩下" + number);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
class SaleTicketDemo{
public static void main(String[] args) {
//共同资源
Ticket ticket = new Ticket();
new Thread(()->{for (int i = 0; i < 35; i++) ticket.sale();},"a").start();
new Thread(()->{for (int i = 0; i < 35; i++) ticket.sale();},"b").start();
new Thread(()->{for (int i = 0; i < 35; i++) ticket.sale();},"c").start();
new Thread(()->{for (int i = 0; i < 35; i++) ticket.sale();},"d").start();
}
}
2.8 可重入锁(又叫递归锁)
/**
* @TODO 可重入锁
* @Author Chushaoyang
* 2025/4/1
*
* 可重入锁是指再同一个线程的外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁的对象得是同一个对象),
* 不会因为之前已经获取还没有释放而阻塞
*
* 如果是1个有synchronized修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚
* 所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁
*/
public class ReEntryLockDemo {
final Object objectLockA = new Object();
public static void main(String[] args) {
ReEntryLockDemo re = new ReEntryLockDemo();
//new Thread(()->re.method1()).start();
/**结果
* ----外层调用
* ----中层调用
* ----内层调用
*/
//new Thread(()->re.m1()).start();
/**结果
* ----m1
* ----m2
* ----m3
*/
new Thread(()->re.method2()).start();
/**结果
* ---外层调用lock
* ---中层调用lock
* ---内层调用lock
*/
}
//同步代码块
public void method1(){
synchronized (objectLockA){
System.out.println("----外层调用");
synchronized (objectLockA){
System.out.println("----中层调用");
synchronized (objectLockA){
System.out.println("----内层调用");
}
}
}
}
//同步方法
public synchronized void m1(){
System.out.println("----m1");
m2();
}
public synchronized void m2(){
System.out.println("----m2");
m3();
}
public synchronized void m3(){
System.out.println("----m3");
}
//lock
private final Lock lock = new ReentrantLock();
public void method2(){
try{
System.out.println("---外层调用lock");
lock.lock();
try{
System.out.println("---中层调用lock");
lock.lock();
try{
System.out.println("---内层调用lock");
lock.lock();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
//lock锁一定要在finally中关闭
//由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直再等待
//加锁次数和释放锁次数一定要一样
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
2.9 synchronized 和 Lock区别
第3章 线程状态
3.1 线程状态概述
线程由生到死的完整过程:
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State
这个枚举中给出了六种线程状态:
这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析
线程状态 | 导致状态发生条件 |
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread只有线程对象,没有线程特征。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。调用了t.start()方法 :就绪(经典叫法) |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Terminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
getState() 获取状态
我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题。
3.2 睡眠sleep方法
我们看到状态中有一个状态叫做计时等待,可以通过Thread类的方法来进行演示.
public static void sleep(long time)
让当前线程进入到睡眠状态,到毫秒后自动醒来继续执行
3.3 等待wait和唤醒notify
Object类的方法
public void wait()
: 让当前线程进入到等待状态 此方法必须锁对象调用.
public final native void notify(); 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.
public final native void notifyAll(); 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用
3.4 面试题 sleep和wait区别
1. sleep(long)静态方法,属于Thread线程类
wait()/wait(long) 非静态方法,属于Object类
2. sleep(long)计时等待,知道沉睡时间结束,再次参与争抢时间片,抢到时间片后才可以继续执行
wait()无线等待,直到被唤醒,抢到对象锁,获得对象锁,才能继续执行
wait(long)计时等待,直到沉睡时间结束或者被唤醒。抢对象锁,获得对象锁,才可以继续执行任务
3. sleep 不会释放对象锁
wait 释放对象锁
4. sleep 不一定搭配synchronized 使用
wati一定搭配synchronized使用,否则会抛出异常
第4章 线程池方式
4.1 线程池的思想
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间,线程也属于宝贵的系统资源。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。
一 .简介
线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的。在jdk1.5之后这一情况有了很大的改观。Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用。为我们在开发中处理线程的问题提供了非常大的帮助。
二.线程池
1.线程池的作用:线程池作用就是限制系统中执行线程的数量。
- 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;
- 少了浪费了系统资源,多了造成系统拥挤效率不高。
- 用线程池控制线程数量,其他线程排队等候。
- 一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待任务,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
2.为什么要用线程池:
- 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executors,严格意义上讲Executors并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口ExecutorService。
3.比较重要的几个类:
* Executors 线程池的工具类
* public static ExecutorService newSingleThreadExecutor() 创建单个线程的线程池
* public static ExecutorService newFixedThreadPool(int nThreads) 创建固定数量线程的线程池
* public static ExecutorService newCachedThreadPool() 创建缓存的线程池
* public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建执行定时任务的线程池
*
* Executor 顶级线程池接口
*
* ExecutorService 线程池接口
*
* ThreadPoolExecutor 实现类,一般用于自定义线程池
* ScheduledExecutorService 执行定时任务线程池接口
ExecutorService 真正的线程池接口。
ScheduledExecutorService 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor ExecutorService的默认实现。
ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
1. newSingleThreadExecutor 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
/**
* @TODO
* @Author Chushaoyang
* 2025/4/1
*
* Executors 线程池的工具类
*
* newSingleThreadExecutor() 静态工厂方法
* 这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。
* 如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。
* 此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
*/
public class SingleThreadExecutorDemo {
public static void main(String[] args) {
//单线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
//execute() 执行任务
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
//关闭线程池
pool.shutdown();
/**结果 只有一个线程
* pool-1-thread-1
* pool-1-thread-1
* pool-1-thread-1
* pool-1-thread-1
*/
}
}
public class MyRunnable implements Runnable{ //下面的调用MyRunnable都是这一个
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
2. newFixedThreadPool 创建程固定大小的线池。 每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束, 那么线程池会补充一个新线程。
/**
* @TODO
* @Author Chushaoyang
* 2025/4/1
* Executors 线程池的工具类
*newFixedThreadPool 创建固定大小的线程池。
* 每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
* 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,
* 那么线程池会补充一个新线程。
*/
public class NewFixedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
//关闭线程池
pool.shutdown();
/**结果,只有三个线程,上面设置为3个
* pool-1-thread-2
* pool-1-thread-1
* pool-1-thread-3
* pool-1-thread-1
* pool-1-thread-2
* pool-1-thread-3
*/
}
}
3. newCachedThreadPool 创建一个可缓存的线程池。 如果线程池的大小超过了处理任务所需要的线程, 那么会回收部分空闲(60秒不执行任务)的线程,当任务数增加时, 此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制, 线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
/**
* @TODO
* @Author Chushaoyang
* 2025/4/1
*
* Executors 线程池的工具类
*
* newCachedThreadPool 创建一个可缓存的线程池。
* 如果线程池的大小超过了处理任务所需要的线程, 那么会回收部分空闲(60秒不执行任务)的线程,
* 当任务数增加时, 此线程池又可以智能的添加新线程来处理任务。
* 此线程池不会对线程池大小做限制,
* 线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
*/
public class NewCachedThreadPoolDemo {
public static void main(String[] args) {
//创建一个可缓存的线程池。
ExecutorService pool = Executors.newCachedThreadPool();
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.shutdown();
/**结果
* pool-1-thread-1
* pool-1-thread-4
* pool-1-thread-3
* pool-1-thread-2
*/
}
}
newScheduledThreadPool 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
public class NewScheduledThreadPoolDemo { public static void main(String[] args) { //ScheduledExecutorService z定时执行任务的线程池 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); /** * scheduleAtFixeRate() * 第一个参数,定时执行的任务,Rannable对象 * 2:延迟时间 * 3;抽取时间 * 时间单位 */ scheduledExecutorService.scheduleAtFixedRate(() -> { LocalDateTime now = LocalDateTime.now(); System.out.println("now" + now); }, 1000, 1000, TimeUnit.MILLISECONDS); /**结果 * now2025-04-02T13:30:18.878 * now2025-04-02T13:30:19.865 * now2025-04-02T13:30:20.860 * now2025-04-02T13:30:21.865 * * 。。。。。。 * */ } }
4.2 线程池概念
-
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:
合理利用线程池能够带来三个好处:
-
降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
-
提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
-
提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
4.3 线程池的使用
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
Executors类中有个创建线程池的方法如下:
-
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
-
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行
Future接口:用来记录线程任务执行完毕后产生的结果。
使用线程池中线程对象的步骤:
-
创建线程池对象。
-
创建Runnable接口子类对象。(task)
-
提交Runnable接口子类对象。(take task)
-
关闭线程池(一般不做)。
Callable测试代码:
-
<T> Future<T> submit(Callable<T> task)
: 获取线程池中的某一个线程对象,并执行. -
Future : 表示计算的结果.
-
V get()
: 获取计算完成的结果。
4.4 submit() 和execute()区别
1.interface Executor接口,execute()
interfae ExecutorService extends Executor 接口 ,submit()
2.void execute(Runnable command); execute只能传入Runnable任务对象
Future<?> submit(Runnable task); submit不仅可以传入Runnable任务对象,也可以传入Callable任务对象
<T> Future<T>submit(Callable<T> task);
3.submit()执行时,可以带有返回值Future,通过Futrue的get()获得任务的返回值,搭配传入Callable的任务对象更加实用,
4.submit()对于出现异常的处理更加完善一些,实现Callable的call()时是需要抛出异常的,所以一点线程池中执行任务时,若遇到异常通过Future的get()抛出ExecutionException,从而阻止其他任务的执行。
**
* @TODO
* @Author Chushaoyang
* 2025/4/2
* submit() 和execute()区别
*/
public class FixedThreadCallablePool {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(1);
//1.Funture<?> subit(Runnable task); 执行Runnable不带返回值的任务
/*Future<?> future1 = pool.submit(new MyRunnable()); //pool-1-thread-1
try {
System.out.println(future1.get()); //null Runnable接口run()根本没有返回值 不建议使用
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
Future<?> future2 = pool.submit(new MyRunnable());
try {
System.out.println(future2.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}*/
//2.<T> Future<T> submit(Callable<T> task); 执行Callable待遇返回值的任务
Future<String> f1 = pool.submit(new MyCallable());
try {
System.out.println(f1.get()); //pool-1-thread-1
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
Future<String> f2 = pool.submit(new MyCallable());
try {
System.out.println(f2.get()); //pool-1-thread-1
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
/**
* @TODO
* @Author Chushaoyang
* 2025/4/2
*/
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName();
}
}
/**
* @TODO
* @Author Chushaoyang
* 2025/4/1
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
4.5 自定义线程池
/**
* @TODO 自定义线程池
* @Author Chushaoyang
* 2025/4/2
*合理利用线程池能够带来三个好处:
*
* 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
* 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
* 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
*
* ThreadPoolExecutor
* public ThreadPoolExecutor(int corePoolSize,
* int maximumPoolSize,
* long keepAliveTime,
* TimeUnit unit,
* BlockingQueue<Runnable> workQueue,
* RejectedExecutionHandler handler)
*
* 2.API方法
* public void execute(Runnable command) 执行任务
* public BlockingQueue<Runnable> getQueue() 获取阻塞队列
* public int getPoolSize() 返回当前线程池中的线程数
*/
public class CustomPoolDemo1 {
private ExecutorService executorService = null;
public CustomPoolDemo1() {
executorService = new ThreadPoolExecutor(
1, //核心线程数
3, //最大线程数
60L, //最大非活动时间
TimeUnit.SECONDS, //时间单位
new LinkedBlockingQueue<>(2), //阻塞队列,queueSize存放任务的个数
new ThreadPoolExecutor.AbortPolicy() //拒绝策略,默认拒绝策略,一旦超过queueSize + maxPoolSize,直接拒绝任务,并抛出异常
);
}
//执行任务的方法
public void execute(Runnable runnable) {
executorService.execute(runnable);
}
//关闭线程池的方法
public void close() {
executorService.shutdown();
}
public static void main(String[] args) {
//1.创建自定义线程池
CustomPoolDemo1 pool = new CustomPoolDemo1();
//执行任务
pool.execute(new MyRunnable()); //corePoolSize执行成功
pool.execute(new MyRunnable()); //静茹queue
pool.execute(new MyRunnable()); //进入queue
/**只有三个结果
* pool-1-thread-1
* pool-1-thread-1
* pool-1-thread-1
*/
pool.execute(new MyRunnable()); //创建maxPoolSize - corePoolSize = 3-1 = 2线程
pool.execute(new MyRunnable()); //创建maxPoolSize - corePoolSize = 3-1 = 2线程
/**上面五个结果
*pool-1-thread-2
* pool-1-thread-1
* pool-1-thread-3
* pool-1-thread-2
* pool-1-thread-1
*/
pool.execute(new MyRunnable()); //直接拒绝,并抛出异常
/**结果 会出异常
*Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.chushaoyang.javase.day13.classwork.custon.MyRunnable@45ee12a7 rejected from java.util.concurrent.ThreadPoolExecutor@330bedb4[Running, pool size = 3, active threads = 3, queued tasks = 2, completed tasks = 0]
* at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
* at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
* at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
* at com.chushaoyang.javase.day13.classwork.custon.CustomPoolDemo1.execute(CustomPoolDemo1.java:47)
* at com.chushaoyang.javase.day13.classwork.custon.CustomPoolDemo1.main(CustomPoolDemo1.java:75)
* pool-1-thread-2
* pool-1-thread-3
* pool-1-thread-1
* pool-1-thread-1
* pool-1-thread-2
*/
//关闭线程池
pool.close();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
4.6 自定义线程池的拒绝策略
/**
* @TODO
* @Author Chushaoyang
* 2025/4/2
*常见阻塞队列:用来保存等待执行任务的队列
* 1.ArrayBlockingQueue 基于数组结构的有界阻塞队列,依据FIFO先进先出策略
* 2.LinkedBlockingQueue 链表阻塞队列
* 3.SynchronousQueue
* 4.PriorityBlockingQueue 优先级阻塞队列
*
*
*
* 自定义线程池的拒绝策略
* 1.AbortPolicy 直接拒绝任务,并抛出异常RejectedExecutionException的策略
* 2.DiscardPolicy 直接拒绝任务,但是不抛出异常
* 3.DiscardOldestPolicy 拒绝任务队列中排队最久的任务,将新的任务加入至队列的末尾
* CallerRunsPolicy 调起正在执行当前任务的线程来执行
*/
public class CustomPoolDemo2 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1,2,
60L,TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
//执行任务
/**
* new ThreadPoolExecutor.AbortPolicy()
* 直接拒绝任务,并抛出异常RejectedExecutionException的策略
* 最大执行的任务数 queueSize + MaximumPoolSize = 3+3=6
* 1.corePoolSize
* 2.corePoolSize
* 3.进入阻塞队列 queue
* 4.进入阻塞队列 queue
* 5.进入阻塞队列 queue
* 6.计算目前能够创建线程的个数 = maxPoolSize - corePoolSize = 3-2=1
* 7.直接拒绝,并抛出异常
*/
// for (int i = 0; i < 8; i++) {
// pool.execute(new MyRunnable());
// }
/** 结果 new ThreadPoolExecutor.AbortPolicy()
*Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.chushaoyang.javase.day13.classwork.custon.MyRunnable@135fbaa4 rejected from java.util.concurrent.ThreadPoolExecutor@45ee12a7[Running, pool size = 2, active threads = 2, queued tasks = 3, completed tasks = 0]
* at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
* at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
* at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
* at com.chushaoyang.javase.day13.classwork.custon.CustomPoolDemo2.main(CustomPoolDemo2.java:42)
* pool-1-thread-1
* pool-1-thread-2
* pool-1-thread-1
* pool-1-thread-2
* pool-1-thread-2
*/
//执行任务
/**
* new ThreadPoolExecutor.DiscardOldestPolicy()
* 直接拒绝任务,并抛出异常RejectedExecutionException的策略
* 最大执行的任务数 queueSize + MaximumPoolSize = 3+3=6
* 1.corePoolSize
* 2.corePoolSize
* 3.进入阻塞队列 queue 移除任务
* 4.进入阻塞队列 queue
* 5.进入阻塞队列 queue
* 6.计算目前能够创建线程的个数 = maxPoolSize - corePoolSize = 3-2=1
* 7.干掉第三元素
* 8.干掉第4个元素
*/
for (int i = 0; i < 8; i++) {
pool.execute(new MyRun((i+1) + ""));
}
//输出堵塞队列的信息
for (Runnable runnable : pool.getQueue()){
runnable.run();
}
pool.shutdown();
}
}
class MyRun implements Runnable {
private String name;
public MyRun(String name) {
this.name = name;
}
@Override
public void run() {
try{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "执行任务" + name);
}
}
第5章 死锁
5.1 什么是死锁
在多线程程序中,使用了多把锁,造成线程之间相互等待.程序不往下走了。
5.2 产生死锁的条件
1.有多把锁
2.有多个线程
3.有同步代码块嵌套
5.3死锁代码
/**
* @TODO 死锁
* @Author Chushaoyang
* 2025/4/2
* 1.相互持有对方资源
* 2.同步代码块嵌套
*
* 排除线程
* 方式一
* 1.打开终端 jps -l 查看相关进程,锁定进程好
* 2.jstack 进程号 Found 1 deadlock.信号
* 方式二
* cmd + 输入jconsole
*
* 解决死锁相关问题
* 1.不要相互持有对方资源
* 2.尽量少代码嵌套
*/
public class DeadDemo {
public static void main(String[] args) {
final Object boj1 = new Object();
final Object boj2 = new Object();
new Thread(()->{
synchronized (boj1) {
System.out.println(Thread.currentThread().getName() + " 持有boj1对象锁");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// synchronized (boj2) {
// System.out.println(Thread.currentThread().getName() + "持有boj2对象锁");
// }
}
},"线程1").start();
new Thread(()->{
synchronized (boj2) {
System.out.println(Thread.currentThread().getName() + "持有boj2对象锁");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// synchronized (boj1) {
// System.out.println(Thread.currentThread().getName() + "持有boj1对象锁");
// }
}
},"线程2").start();
}
}