Java学习 多线程

第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类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。

  2. 可以避免java中的单继承的局限性。

  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

  4. 线程池只能放入实现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引入了线程同步机制。

那么怎么去使用呢?

有三种方式完成同步操作:

  1. 同步代码块。

  2. 同步方法。

  3. 锁机制。

2.3 同步代码块

  • 同步代码块synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){

需要同步操作的代码

}

同步锁:

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象 可以是任意类型。

  2. 多个线程对象 要使用同一把锁。

代码

 * //面试题: 线程安全 和 线程同步
 * 一。线程安全
 * 多线程并发访问同一资源时,可能会发生争抢资源的问题,从而引发线程安全的问题
 *
 * "电影院售票案例":多个人一起买票,有可能出现买到同一张票,也有可能出现负数的票,这些都是线程不安求
 *
 * 二.线程同步
 * 线程同步,是解决线程安全的一种机制
 * 出现 “有序”,“等待”的现象
 *
 * 三。确保线程同步三种方式
 * 方式一:同步代码块
 *  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.为什么要用线程池:

  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 线程池概念

  • 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:

合理利用线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约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接口:用来记录线程任务执行完毕后产生的结果。

使用线程池中线程对象的步骤:

  1. 创建线程池对象。

  2. 创建Runnable接口子类对象。(task)

  3. 提交Runnable接口子类对象。(take task)

  4. 关闭线程池(一般不做)。

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();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值