day18_多线程

news2024/11/15 12:12:15

今日内容

零、 复习昨日
一、作业
二、线程安全的集合
三、死锁
四、线程通信
五、线程池

零、 复习昨日

见晨考

一、线程安全[重点]

1.0 线程不安全

当前线程的数据被其他线程修改

1.1 线程安全

  • 临界资源:共享资源(同⼀个对象),一次只可以有一个线程操作,才可以保证准确性

  • 原子操作:不可拆分的步骤,被视作一个整体。其步骤不能打乱

线程不安全: 1) 完整的步骤可能会被破坏 2) 线程内的数据可能被别的线程修改

1.2 实现线程安全方式

  • 同步方法
    • 给方法加锁,即设置同步锁关键词synchronized
    • 锁对象是当前对象,即this
  • 同步代码块
    • 将需要安全的代码使用同步代码块包裹,设置锁对象.
    • 锁可以是任意对象
    • 但是线程之前应该是同一把锁才能锁住,保证安全

其实就是给需要"同步",需要"安全",需要"步骤一致,不能打乱"的代码加锁.

需求: 要求打印机类的两个方法分别被两个线程同时执行.但是方法执行时不能被破坏,即方法执行时不能被另外一个线程抢走资源,要保证方法执行时步骤完整性.

1.2.1 同步方法

同步方法就是给方法设置synchronized关键词

注意事项:

  • 方法要同步就都要上锁
  • 需要同步的方法锁的得是同一个锁对象,即得是同一把锁
  • 同步方法锁对象是this,即得是同一个this才能锁住
public class Printer {

    /**
     * 同步方法,就是给方法设置synchronized关键字
     * 同步方法锁对象是this
     * 需要同步的代码必须是同一把锁(即相同锁对象)才能保证同步,否则无效
     */
    public synchronized void print1() {
        System.out.print("1 " );
        System.out.print("2 " );
        System.out.print("3 " );
        System.out.print("4 " );
        System.out.println();
    }
    public synchronized void print2() {
        System.out.print("A " );
        System.out.print("B " );
        System.out.print("C " );
        System.out.print("D " );
        System.out.println();
    }
}
public class TestSync1 {

    public static void main(String[] args) {
        Printer printer = new Printer( );
        // 另一个对象
        // Printer printer2 = new Printer( );

        // 线程1执行打印1任务
        new Thread(){
            @Override
            public void run() {
                while (true){
                    printer.print1();
                }
            }
        }.start();

        // 线程2执行打印2任务
        new Thread(){
            @Override
            public void run() {
                while (true){

                    printer.print2();
                    // 另一个对象调用方法,虽然方法加锁了,但是锁对象不一样,没用!!!
                    // printer2.print2();
                }
            }
        }.start();
    }
}

1.2.2 同步代码块

ps: 代码块其实就是{}包裹着一段代码

同步代码块,就是使用synchronized关键词+{}包裹着的一段代码

作用: 被同步代码块包裹的代码是线程安全的,即同步,运行时是原子操作

语法: synchronized(对象){ 需要同步的代码 }

注意事项:

  • 锁对象是任意对象
  • 但是需要同步的方法锁对象得是同一个
public class Printer2 {
   private Object obj = new Object();
    /**
     * synchronized (锁对象){}
     * 锁对象可以是任意对象,但是需要同步的方法锁对象得是同一个
     */
    public void print1() {
        synchronized (obj){
            System.out.print("1 ");
            System.out.print("2 ");
            System.out.print("3 ");
            System.out.print("4 ");
            System.out.print("\r\n ");
        }
    }

    public void print2() {
        synchronized (obj) {
            System.out.print("A ");
            System.out.print("B ");
            System.out.print("C ");
            System.out.print("D ");
            System.out.print("\r\n ");
        }
    }
}
public class TestSync1 {

    public static void main(String[] args) {
        Printer2 printer = new Printer2( );
        // 线程1执行打印1任务
        new Thread(){
            @Override
            public void run() {
                while (true){
                    printer.print1();
                }
            }
        }.start();

        // 线程2执行打印2任务
        new Thread(){
            @Override
            public void run() {
                while (true){
                    printer.print2();
                }
            }
        }.start();
    }
}

1.2.3 区别

  • 同步方法
    • 是在方法上加synchronized,即整个方法上锁
    • 锁对象是this
    • 锁整个方法,锁的范围大
  • 同步代码块
    • 是在方法内部给局部代码添加synchronized,即局部代码上锁
    • 锁对象是任意对象
    • 锁部分代码,锁的范围小

总结: 同步方法会锁整个方法,同步代码块锁部分代码,如果方法内不是必须所有都同步,那么同步代码块最合适.

1.3 转账案例

需求: 假设银行账户类,属性有卡号,余额,名字 ; 另外有ATM机器,机器内能接收账户,给ATM指定取的钱数.ATM支持多线程操作.

账户类

public class Account {

    private String num;
    private double money;
    private String name;

    public Account() {
    }

    public Account(String num, double money, String name) {
        this.num = num;
        this.money = money;
        this.name = name;
    }

    public String getNum() {
        return num;
    }

    public void setNum(String num) {
        this.num = num;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

使用Runnable接口实现

public class ATM implements Runnable {
    private Account account;
    private double money;// 要取出的钱

    // 取钱动作要并发执行

    /**
     * 这里不能使用同步方法,因为创建两个线程对象
     * 两个线程对象启动线程,调用run,那么run上面的synchronized锁对象
     * 就不是同一个,锁不住,无法保证线程安全
     * ---------------------------------------
     * 换成同步代码块,保证线程运行时,synchronized()中的锁是同一个对象就行
     */
    @Override
    public /*synchronized*/ void run() {
        while (true) {
            synchronized (account) {
                // 要取的钱<=账户余额
                if (money <= account.getMoney( )) {
                    //t1,t2进

                    // t1抢到
                    // 获得线程对象
                    Thread thread = Thread.currentThread( );
                    System.out.println(thread.getName( ) + "正在取出" + money + "元");
                    // 账户余额要减少
                    double a = account.getMoney( );
                    double b = a - money;
                    account.setMoney(b);
                    // t1结束,t2抢回去

                    // t1又抢回来,取出最新值-100
                    // 获得最新余额
                    double balance = account.getMoney( );
                    System.out.println(thread.getName( )+"账户余额:" + balance);
                } else {
                    System.out.println("余额不足!");
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace( );
                }
            }
        }
    }

    public ATM(Account account, double money) {
        this.account = account;
        this.money = money;
    }

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}
public class TestATM {
    public static void main(String[] args) {
        // 来张银行卡
        Account account = new Account("666666", 1000, "张三");

        // 来一个ATM机,给设置账户,设置要取的钱
        ATM atm = new ATM(account, 100);
        // 创建线程并启动
        new Thread( atm ,"ATM1号").start();


        // 再来一个ATM机,给设置账户,设置要取的钱
        ATM atm2 = new ATM(account, 100);
        // 创建线程并启动
        new Thread( atm2 ,"ATM2号").start();
    }
}

练习: 实现继承Thread实现

1.4 火车票案例

售票: 有一个窗口类售票,总共100张票,窗口不止一个可以多个窗口同时售票. 要保证票不能卖超,不能卖重复的票

package com.qf.exercise2;
/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc 售票窗口
 */
public class TicketWindow extends Thread{

    private static int ticketNum = 100;

    public TicketWindow(String name){
        super(name);
    }

    /**
     * 售票功能,需要并行操作(多个窗口同时售卖)
     */
    @Override
    public void run() {
        while (true) {
            synchronized(TicketWindow.class) {
                if (ticketNum > 0) {
                    System.out.println(getName( ) + "正在售出第" + ticketNum + "张票");
                    ticketNum--;
                    // 让线程陷入阻塞,模拟出票时间
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace( );
                    }
                } else {
                    System.out.println("票已售罄!");
                    break;
                }
            }
        }
    }
}

public class TestTicket {
    public static void main(String[] args) {
        new TicketWindow("窗口[1]").start();
        new TicketWindow("窗口[2]").start();
        new TicketWindow("窗口[3]").start();
    }
}

1.5 sleep问题[重点]

非线程安全的情况下,sleep会让出系统资源,让线程执行

线程安全的情况下,即加锁的情况下(在同步方法/代码内)使用sleep,不会让出系统资源,别的线程不能执行 --> 抱着锁睡

    public static void main(String[] args) {
        new Thread( "线程1" ){
            @Override
            public void run() {

                // 加锁
                synchronized (Demo2.class) {
                    for (int i = 1; i < 1000; i++) {
                        if (i == 5) {
                            // 加锁后休眠,不释放资源
                            // 即其他线程无法执行,都处于阻塞状态
                            try {
                                Thread.sleep(5000);
                            } catch (InterruptedException e) {
                                e.printStackTrace( );
                            }
                        }
                        System.out.println(getName( ) + "--->" + i);
                    }
                }
            }
        }.start();

        new Thread( "线程2" ){
            @Override
            public void run() {
                //  // 加锁
                synchronized (Demo2.class) {
                    for (int i = 1; i < 1000; i++) {
                        System.out.println(getName( ) + "--->" + i);
                    }
                }
            }
        }.start();

    }

1.6 保证线程安全的方式

不只有synchronized同步方法可以保证线程安全,其实还有很多

ThreadLocal 线程本地变量

Volatile 共享变量

Lock 锁

ReentrantLock 重入锁

二、线程安全的类[了解]

image-20230303094122011

image-20230303094159112

ArrayList是线程不安全
Vector 是线程安全

image-20230303094527278

HashMap 是线程不安全 , 快
Hashtable 是线程安全 , 慢

image-20230303095012216

比HashMap安全,比Hashtable快,即安全又快的集合ConcurrentHashMap[很重要]

ConcurrentHashMap类所在包是java.util.concurrent, 是并发包,简称JUC

三、死锁[了解]

死锁是指两个或多个线程(或进程)在互相等待对方释放资源的情况下陷入无限等待的状态。每个线程都在等待其他线程所持有的资源,同时又不释放自己已经持有的资源,导致所有线程都无法继续执行下去。

互相持有对方的锁还不释放

public class MyLock {
    // 左筷子锁
    static Object zuo = new Object();
    // 右筷子锁
    static Object you = new Object();

}
  public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                synchronized (MyLock.LEFT){
                    System.out.println("女朋友拿到左筷子" );
                    synchronized (MyLock.RIGHT){
                        System.out.println("女朋友拿到右筷子-吃饭" );
                    }
                }

            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                synchronized (MyLock.RIGHT){
                    System.out.println("男朋友拿到右筷子" );
                    synchronized (MyLock.LEFT){
                        System.out.println("男朋友拿到左筷子-吃饭" );
                    }
                }

            }
        }.start();
    }

解决死锁问题的方法通常包括以下几种:

  1. 预防死锁:通过破坏死锁四个必要条件之一来预防死锁。例如,避免使用不必要的资源互斥,确保线程在申请资源时不保持已有资源,引入资源的优先级等。
  2. 避免死锁:使用资源分配算法来避免系统进入死锁状态。如银行家算法(Banker’s algorithm)。
  3. 检测与恢复:通过周期性检测系统中的死锁状态,然后采取相应的恢复措施。常用的方法是使用图论中的资源分配图(Resource Allocation Graph)进行检测,并采取抢占资源或终止某些线程的策略来解除死锁。
  4. 忽略死锁:某些情况下,可以通过对系统进行建模与分析,确定死锁发生的概率非常低,因此可以选择忽略死锁的处理。

四、线程通信[熟悉]

4.1 介绍[重要]

线程通信的前提是得保证线程安全(同步)

线程通信,就是线程之间产生联系.

即通知,例如线程A执行到一定时候会停下,同时通知另外的线程B执行,
线程B执行到一定时候,也停下,通知线程A执行

以上操作需要**Object类**的方法

  • wait() 让当前线程等待
  • notify() 唤醒一个处于等待状态的线程
  • notifyAll() 唤醒所有处于等待状态的线程

4.2 两个线程通信

需求: 之前打印机方法,让print1()和print2()方法交替执行

package com.qf.notify;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc 通信
 */
public class Printer {

    /**
     * 打印机执行的标志
     * 此标志如果是1,说明该打印机1执行,否则打印机1停下
     * 此标志如果是2,说明该打印机2执行,否则打印机2停下
     */
    private int flag = 1;

    public synchronized void print1() throws InterruptedException {
        if (flag != 1) { // 判断不是自己执行时
            // 线程停下不执行
            // 锁对象是谁,谁去wait方法
            this.wait();
        }
        System.out.print("1 " );
        System.out.print("2 " );
        System.out.print("3 " );
        System.out.print("4 " );
        System.out.print("\r\n" );

        // 改变标志
        flag = 2;
        // 通知其他处于等待状态的线程,起来干活
        // 锁对象是谁,谁去notify方法
        this.notify();
    }

    public synchronized void print2() throws InterruptedException {
        if ( flag != 2) {
            this.wait();
        }
        System.out.print("A " );
        System.out.print("B " );
        System.out.print("C " );
        System.out.print("D " );
        System.out.print("\r\n" );

        flag = 1;
        this.notify();
    }
}
public class TestNotify {
    public static void main(String[] args) {
        Printer printer = new Printer( );

        new Thread(){
            @Override
            public void run() {
                while(true){
                    try {
                        printer.print1();
                    } catch (InterruptedException e) {
                        e.printStackTrace( );
                    }
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                while(true){
                    try {
                        printer.print2();
                    } catch (InterruptedException e) {
                        e.printStackTrace( );
                    }
                }
            }
        }.start();
    }
}

换用同步代码块实现

package com.qf.notify;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc 通信
 */
public class Printer2 {
    Object obj = new Object();

    private int flag = 1;

    public void print1() throws InterruptedException {
        synchronized (obj) {
            if (flag != 1) { // 判断不是自己执行时
                // 线程停下不执行
                // 锁对象是谁,谁去wait方法
                obj.wait( );
            }
            System.out.print("1 ");
            System.out.print("2 ");
            System.out.print("3 ");
            System.out.print("4 ");
            System.out.print("\r\n");

            // 改变标志
            flag = 2;
            // 通知其他处于等待状态的线程,起来干活
            // 锁对象是谁,谁去notify方法
            obj.notify( );
        }
    }

    public void print2() throws InterruptedException {
        synchronized (obj) {
            if (flag != 2) {
                obj.wait( );
            }
            System.out.print("A ");
            System.out.print("B ");
            System.out.print("C ");
            System.out.print("D ");
            System.out.print("\r\n");

            flag = 1;
            obj.notify( );
        }
    }
}

总结

  • 通信的代码(wait和notify等)需要放在同步方法或者同步代码块里面
  • 通信的代码(wait和notify等)必须使用当前锁对象来调用

4.3 练习

创建A1 A2 两个线程,分别打印1-10,11-20,保证A1执行完再 执行A2线程,A2执行完再执行A1

A1 —> 1
A2 —>11
A1 —> 2
A2 —>12

package com.qf.exercise3;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
public class A {

    private int flag = 1;

    public synchronized void a1() throws InterruptedException {
        for (int i = 1; i < 11; i++) {
            if (flag != 1) {
                this.wait();
            }
            System.out.println("A1 -->" + i);
            flag = 2;
            this.notify();
        }
    }

    public synchronized void a2() throws InterruptedException {
        for (int i = 11; i < 21; i++) {
            if (flag != 2) {
                this.wait();
            }
            System.out.println("A2 -->" + i);
            flag = 1;
            this.notify();
        }
    }
}
package com.qf.exercise3;

import com.qf.notify.Printer;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
public class TestNotify {
    public static void main(String[] args) {
        A a = new A( );
        new Thread("打印机1") {
            @Override
            public void run() {
                try {
                    a.a1( );
                } catch (InterruptedException e) {
                    e.printStackTrace( );
                }
            }
        }.start( );

        new Thread("打印机2") {
            @Override
            public void run() {
                try {
                    a.a2( );
                } catch (InterruptedException e) {
                    e.printStackTrace( );
                }
            }
        }.start( );
    }
}

4.4 总结[重要]

特殊的:

  • wait和notify方法需要在同步方法或者同步代码块内执行
  • wait和notify方法需要使用当前锁对象调用
  • wait会让当前线程进入等待状态,让出资源,其他线程可以执行

问:wait和sleep有什么区别?

答:

  • 相同点:

    • wait和sleep都可以让当前线程进入阻塞状态
  • 不同点

    • wait是Object类的方法,sleep是Thread类方法
    • 但是wait阻塞当前线程,会让出系统资源,其他线程可执行;但是sleep阻塞当前线程,会持有锁不释放,其他线程无法执行
    • wait需要在同步方法或同步代码快中使用,但是sleep可以在同步或非同步都可以使用

问:为什么wait和notify方法要设计在Object类中?

答: 因为锁可以是任意对象,又因为wait和notify需要被 锁对象调用,那么wait和notify方法也能被任意对象调用,所以就设计在Object类中,因为Object类是所有类的父类

总结

0 线程方法

  • Thread(),创建线程
  • Thread(String name),创建线程并设置名字
  • Thread(Runnable r),创建线程
  • Thread(Runnable r,String name),创建线程,并设置名字
  • start 开启线程,run()执行线程任务
  • sleep() 线程休眠
  • static currentThread() 获得当前线程对象

1什么叫线程不安全?如何保证安全?

  • 线程并发执行,多条线程处理的是同一个任务
  • 此时就有可能出现,一个线程在执行时,另外线程过来抢走也执行,导致再抢回来时数据不正确
  • 简单说: 线程互相争抢,导致数据不正确
  • 如何保证线程安全? 给执行的代码加锁,同步锁(synchronized)
  • 一种是同步方法,锁住整个方法,范围较大
  • 一种是同步代码块,锁住局部代码,范围较小

2什么是死锁

  • 死锁是发生在线程安全情况下
  • 死锁是多个线程互相持有对方的资源
  • 且不释放

3如何实现线程通信

  • 使用等待唤醒机制实现通信
  • 使用方法wait,notify

4wait和sleep的区别

  • wait和sleep都可以让线程处于阻塞(等待)状态
  • 在加锁情况下: wait等待时释放锁,sleep等待时不释放资源

5同步方法锁对象是谁? 一定是this

6wait和notify这些线程通信的方法,为什么设计在Object类中?

  • wait和notify必须写在同步方法/同步代码块内
  • 换句话说: wait和notify用的前提是必须上锁
  • wait和notify还必须被锁对象调用
  • 锁对象又可以任意对象
  • Object又是任意类的父类
  • 所以必须设计在Object类

public synchronized void test(){
}

public void test(){
synchronized(锁){}
}

五、线程池[面试]

5.1 线程池概念

  • 如果有非常多的任务需要非常多的线程来完成,每个线程的工作时间不长,就需要创建很多线程,工作完又立即销毁[线程频繁创建和销毁线程]
  • 频繁创建和销毁线程非常消耗性能,那么线程池,就是可以创建一些线程,放在"池子"中,用的时候去池子取一个线程去使用,使用完再放回去,线程可以重用
  • 线程池,底层其实就是集合队列,里面存储线程对象,用的时候去抽即可,就不要频繁创建线程了

使用线程池的好处是

  • 减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资
    源不足的问题。
  • 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存(OOM Out Of Memory)或者“过度切换”的问题
  • –> 以上摘自阿里官方手册

5.2 线程池原理

image-20230301230326324

将任务(task)提交(submit/execute)给线程池(threadpool),由线程池分配线程,运行任务,任务结束后,线程重新放入线程池供后续线程使用

5.3 创建线程池的方式

使用线程池创建线程,执行任务

JDK提供了关于创建线程池的方式

  • Executors: 通过该类提供的静态方法来获得不同特点的线程池对象
    • newFixedThreadPool
    • newCachedThreadPool
    • newScheduledThreadPool
    • newSingleThreadExecutor
  • ThreadPoolExecutor: 通过submit(Runnable task) 来提交任务,执行任务

线程池执行任务时,可以采用两种方法:

execute(): 没有返回值,无法判断任务是否执行成功

submit():会返回Future对象,通过该对象判断任务是否执行成功

线程池使用完要关闭时:

shutdown() 关闭线程池

5.4 不同特点的线程池

通过Executors调用以下静态方法获得不同特点的线程池对象

方法类型解释
newFixedThreadPool固定大小线程池池中包含固定数目的线程,空闲线程一直保留。只有核心线程,线程数量固定,任务队列为LinkedBlockingQueue
newCachedThreadPool动态大小的线程池,原则上无上限无核心线程,非核心线程数量无限,执行完闲置60s后回收,任务队列SynchronousQueue
newScheduledThreadPool可以执行定时任务的线程池用于调度执行的固定线程池,执行定时或周期性任务。和弦线程数量固定,非核心线程数量无线,执行完闲置10ms后回收,任务队列为DelayedWorkQueue
newSingleThreadExecutor单线程线程池只有一个线程的池,会顺序执行提交的任务,只有一个核心线程,无非核心线程,任务队列为LinkdBlockingQueue
newSingleThread
ScheduledExecutor
单线程定时任务线程池
newWorkStealingPool1.8提供新的方式创建线程池

以上线程池操作在阿里java开发手册中是不建议用的…

说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
-----------------------
OOM 内存溢出,即系统资源耗尽

分别演示不同特点的线程池:

public class Demo1 {

    public static void main(String[] args) {
        show3();
    }

    // 演示可调度线程池
    public static void show3(){
        // 创建可调度线程池
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);

        // 将任务提交给线程池
        for (int i = 0; i < 5000; i++) {
            threadPool.schedule(new Runnable( ) {
                @Override
                public void run() {
                    String name = Thread.currentThread( ).getName( );
                    System.out.println(name+"-执行任务" );
                }
            },5, TimeUnit.SECONDS);
        }


        // 关闭线程池
        threadPool.shutdown();
    }

    // 演示可变大小线程池
    public static void show2(){
        // 创建可变大小,线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();
        // 将任务提交给线程池
        for (int i = 0; i < 5000; i++) {
            threadPool.execute(new Runnable( ) {
                @Override
                public void run() {
                    String name = Thread.currentThread( ).getName( );
                    System.out.println(name+"-执行任务" );
                }
            });
        }


        // 关闭线程池
        threadPool.shutdown();


    }

    // 演示固定大小线程池
    public static void show1(){
        // 创建固定大小(3个线程),线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        // 将任务提交给线程池
        for (int i = 0; i < 50; i++) {
            threadPool.execute(new Runnable( ) {
                @Override
                public void run() {
                    String name = Thread.currentThread( ).getName( );
                    System.out.println(name+"-执行任务" );
                }
            });
        }
       // 关闭线程池
        threadPool.shutdown();
    }
}

5.5 ThreadPoolExecutor[重要]

  • ThreadPoolExecutor 很重要,有7个参数
参数名解释备注
int corePoolSize指定线程池的线程数量(核心线程数)不能小于0
int maximumPoolSize指定线程池可支持的最大线程数最大数量>=核心线程数
long keepAliveTime指定临时线程的最大存活时间不能小于0
TimeUnit unit指定存活时间的单位(秒,分,时,天)时间单位
BlockingQueue workQueue指定任务队列
ThreadFactory threadFactory指定哪个线程工厂创建线程
RejectedExecutionHandler handler指定线程忙,任务队列满的时候新任务来了怎么办?拒绝策略

这几个参数解释(某大型火锅店会例子)

  • 核心线程数5, 即店里面的固定员工5个
  • 最大线程数15,即突然顾客太多,5个人忙不过来,临时招聘10个人来干活
  • 最大存活时间,即顾客不多的时候,这些临时工可以待多长时间
  • 时间单位
  • 任务队列10,即集合, 固定员工加上临时工还处理不了顾客,在店门口放几10张凳子
  • 线程工厂, 如何创建出的线程? 即怎么找到的员工
  • 拒绝策略. 当固定员工,临时工,以及门口的凳子都坐满了,不让吃了,不让排队,直接拒绝

问: 什么时候创建临时线程?
答: 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建线程

问:什么时候开始拒绝任务?
答:核心线程和临时线程都在忙,任务队列也满了,新的任务过来就会拒绝

package com.qf.threadpool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
public class TestThreadPool {

    public static void main(String[] args) {

        /**
         * int corePoolSize, 核心线程数    - 5
         * int maximumPoolSize, 最大线程数 - 15
         * long keepAliveTime,  生存时间   - 1
         * TimeUnit unit,      时间单位     - 天
         * BlockingQueue<Runnable> workQueue,  阻塞队列 处理不及时的线程进阻塞队列
         * ThreadFactory threadFactory,   线程工厂
         * RejectedExecutionHandler handler , 拒绝策略
         */
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
        // 至少需要5个参数
        // new ThreadPoolExecutor(5,15,1, TimeUnit.MINUTES,queue);
        // 多1个线程工厂,可以使用最简单的默认工厂
        // new ThreadPoolExecutor(5,15,1, TimeUnit.MINUTES,queue, Executors.defaultThreadFactory());
        // 多1个拒绝策略,默认的拒绝策略是 抛出异常
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
                15,
                1, TimeUnit.MINUTES,
                queue,
                Executors.defaultThreadFactory( ),
                new ThreadPoolExecutor.AbortPolicy( ));

        for (int i = 1; i < 55; i++) {
            executor.execute(new Runnable( ) {
                @Override
                public void run() {
                    Thread thread = Thread.currentThread( );
                    String name = thread.getName( );
                    System.out.println( name+"执行任务");
                }
            });
        }
        executor.shutdown();
    }
}

面试肯定会问,重点是理解,要能说出来!

六、总结

关于线程API
1) 创建线程构造方法
2) 启动线程start,运行线程任务run
3) 获得线程对象Thread.currentThread()
4) 线程休眠sleep()
----------------------
关于线程同步:
1 什么是线程不安全: 线程数据被其他线程篡改?
	为什么被篡改? 是因为当前线程执行过程中,别的线程抢走资源也执行
2 什么是线程安全: 当前线程执行时,不要让别的线程抢走资源,这样就不会篡改数据
3 如何做到? 就是给方法加锁
4 两种方案: 同步方法,同步代码块
5 注意事项: 保证锁对象是同一个
----------------------
关于通信:
1) 什么叫线程通信?
2) 如何做到的? 调用哪些方法做到...
3) wait和notify使用时有注意事项:
   是不是必须要写在同步内?
   被谁调用?
   wait和sleep有什么异同?
--------------------
了解死锁,了解其他线程安全的方式,了解jdk中线程安全的类   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1212148.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JS-项目实战-代码优化-事件动态绑定

1、鼠标悬浮和离开事件.js //当页面加载完成后执行后面的匿名函数 window.onload function () {//get:获取 Element:元素 By:通过...方式//getElementById()根据id值获取某元素let fruitTbl document.getElementById("fruit_tbl");//table.rows:获取这个表格…

【Maven】进阶

文章目录 1. 聚合2. 继承3. 属性变量定义与使用4. 版本管理5. 资源配置6. 多环境配置7. 跳过测试&#xff08;了解&#xff09; 1. 聚合 为了防止某个模块&#xff08;dao&#xff09;更新了&#xff0c;重新编译了&#xff0c;导致和其他模块不兼容&#xff0c;需要用一个roo…

使用boost库

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;…

tsmc12 nm boundary cell注意事项

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 往期文章导读: boundary cell添加失败问题整理 注意N/P的区别 针对上下两边的boundary cell,有N/P类型的区别,看版图衬底形状上下是不对称的,而且P

深入理解SqueezeSegV3点云分割

文章&#xff1a;Squeezesegv3: Spatially-adaptive convolution for efficient point-cloud segmentation 代码&#xff1a;https://github.com/chenfengxu714/SqueezeSegV3 一、摘要 激光雷达点云分割是许多应用中的一个重要问题。对于大规模点云分割&#xff0c;一般是投…

零代码数字孪生设计平台的功能特点

在当今数字化的时代&#xff0c;企业的转型已经成为必然的趋势。而在这个过程中&#xff0c;3D数字孪生无代码编辑工具正成为企业实现数字化转型的新价值点。客户可以无需任何专业知识和专业软件的支持&#xff0c;仅仅通过互联网和浏览器即可根据购买要求对自己的产品/设备/园…

vite => .env 文件配置和使用

.env.development .env.production VITE_API_BASE_URL /api # 开发环境代理地址 .env.development 是在开发环境中的代理地址 .env.production 是在线上的代理地址 &#xff08; 两个 .env 内部的变量都是一样的 vite 会在你开发环境和线上环境自动做切换 &#xff09; …

彩虹桥架构演进之路-性能篇

一、前言 一年前的《彩虹桥架构演进之路》侧重探讨了稳定性和功能性两个方向。在过去一年中&#xff0c;尽管业务需求不断增长且流量激增了数倍&#xff0c;彩虹桥仍保持着零故障的一个状态&#xff0c;算是不错的阶段性成果。而这次的架构演进&#xff0c;主要分享一下近期针对…

【华为内部资料】《高速数字电路设计教材》(可下载)

与数字技术或软件相比&#xff0c;模拟技术人才的培养和造就仍然需要一定的实践和时间&#xff0c;但无论数字技术发展到任何阶段将永远离不开模拟技术。 由于难度系数较大的原因&#xff0c;有时即便投入很多精力&#xff0c;如果缺乏耐心、毅力和必要的条件&#xff0c;投入…

猫罐头买什么牌子的?宠物店最受欢迎的5款猫罐头推荐!

有多少铲屎官为了让猫咪健康快乐的成长&#xff0c;在猫罐头上费尽心机。钱是砸进去了&#xff0c;但是自己猫胖的胖、瘦的瘦&#xff0c;甚至有的毛病多&#xff0c;出现身体各种问题。 作为一个经营宠物店7年的店长&#xff0c;某宝有大促的时候我总能捡漏&#xff0c;囤到一…

物联网AI MicroPython学习之语法 GPIO输入输出模块

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; GPIO 介绍 模块功能: GPIO通用输入输出。 接口说明 GPIO - 构建GPIO对象 函数原型&#xff1a;Pin(port, dir , pull)参数说明&#xff1a; 参数类型必选参数&#xff1f;说明portintY对应开发板的引脚号…

【论文精读】VOYAGER: An Open-Ended Embodied Agent with Large Language Models

Understanding LSTM Networks 前言Abstract1 Introduction2 Method2.1 Automatic Curriculum2.2 Skill Library2.3 Iterative Prompting Mechanism 3 Experiments3.1 Experimental Setup3.2 Baselines3.3 Evaluation Results3.4 Ablation Studies3.5 Multimodal Feedback from …

【MySQL】MVCC(多版本并发控制)详解

MVCC MVCC概述 MVCC&#xff0c;全称 Multi-Version Concurrency Control &#xff0c;即多版本并发控制。MVCC 是一种并发控制的方法&#xff0c;一般在数据库管理系统中&#xff0c;实现对数据库的并发访问&#xff0c;在编程语言中实现事务内存。 MVCC就是在ReadCommitte…

金蝶云星空表单插件获取控件值

文章目录 金蝶云星空表单插件获取控件值获取主键获取文本获取日期获取数值获取基础资料 金蝶云星空表单插件获取控件值 获取主键 正确&#xff1a; this.View.Model.GetPKValue();错误&#xff1a; 获取文本 this.View.Model.GetValue("FBILLNO")获取日期 thi…

一题带你写出图论算法模板!!!

这题是道基础的图论算法题目 注释很重要&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 在做这道题之前&#xff0c;我们先了解一下基础的图论算法吧&#xff01;&#xff01;&#xff01; 1.floyd&#xff1a; 这样可以求出所有点…

了解一下知识付费系统的开发流程和关键技术点

知识付费系统的开发既涉及到前端用户体验&#xff0c;又需要强大的后端支持和复杂的付费逻辑。在这篇文章中&#xff0c;我们将深入探讨知识付费系统的开发流程和关键技术点&#xff0c;并提供一些相关的技术代码示例。 1. 需求分析和规划&#xff1a; 在着手开发知识付费系…

day21_mysql

今日内容 零、 复习昨日 第一阶段: Java基础知识(会编程,懂编程) 第二阶段: Web开发(前端,后端,数据库) 一、MySQL 一、引言 二、数据库 2.1 概念 ​ 数据库是“按照数据结构来组织、存储和管理数据的仓库。是一个长期存储在计算机内的、有组织的、有共享的、统一管理的数据集合…

搭建成功simulink-stm32硬件在环开发环境

本次实验所使用的软件版本和硬件平台参数如下&#xff1a; Matlab版本: 2021b STM32硬件平台&#xff1a;YF_STM32_Alpha 1R4(参考自STM32 Nucleo F103RB官方开发板) YF_STM32_Alpha开发板 STM32 Nucleo F103RB 开发板 2.1 STM32硬件支持包下载 读者朋友平时使用的是和谐版M…

基于springboot实现体育场馆运营平台项目【项目源码】

基于springboot实现体育场馆运营管理系统演示 系统开发平台 在该数码论坛系统中&#xff0c;Eclipse能给用户提供更多的方便&#xff0c;其特点一是方便学习&#xff0c;方便快捷&#xff1b;二是有非常大的信息储存量&#xff0c;主要功能是用在对数据库中查询和编程。其功能…

基于SpringBoot+Vue的在线外卖管理系统

基于SpringBootVue的在线外卖管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 下单界面 登录界面 商家界面 摘要 本文介绍了一种基于Spring Boot和…