Java学习路线(20)——多线程

news2024/10/6 22:19:16

一、线程(Thread)

1、概念: 是一个程序内部的一条执行路径

2、分类

  • 单线程: 程序中只有一条执行路径
  • 多线程: 程序中有多条执行路径

二、多线程的创建

1、Thread的概念: Java通过java.lang.Thread类代表线程,按照面向对象原则,Thread类提供多线程的实现方法。

2、方法一:继承Thread类

(1)实现流程

  • 定义一个线程子类继承Thread类,重写run()方法
  • 创建线程实现类对象
  • 调用线程对象start()方法启动线程(启动后依旧执行run()方法)

(2)使用示例

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread thread = new MyThread("线程1运行中...");
        MyThread thread2 = new MyThread("线程2运行中...");
        thread.start();
        thread2.start();
    }
}

class MyThread extends Thread{

    private String message;

    public MyThread(){}

    public MyThread(String message){
        this.message = message;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(message);
        }
    }
}

/*打印输出*/
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...

(3)优缺点

  • 优点: 编码简单
  • 缺点: 线程类继承了Thread类,无法继承其它类,不利于扩展

(4)相关问题

问:能不能将主线程任务放置到子线程之前?
答: 若想实现多线程,那么主线程的任务必须放在子线程启动之后,否则CPU先执行完主线程任务,再执行子线程。

问:为什么不直接调用run(),而是调用start()?
答: 如果调用run(),则会当做普通方法执行,实际上还是单线程,在start()开启后才算是多线程执行。

3、方法二:实现Runnable接口

(1)实现方式

  • 定义一个线程任务类实现Runnable接口,重写run()方法
  • 创建线程任务类对象
  • 将线程任务类对象交付Thread处理
  • 调用start()启动线程

(2)使用示例

public class ThreadDemo {
    public static void main(String[] args) {
        RunnableThread run = new RunnableThread("线程1运行中...");
        RunnableThread run1 = new RunnableThread("线程2运行中...");
        Thread t = new Thread(run);
        Thread t1 = new Thread(run1);
        t.start();
        t1.start();
    }
}

class RunnableThread implements Runnable{

    private String message;

    public RunnableThread(){}

    public RunnableThread(String message){
        this.message = message;
    }

    @Override
    public void run() {
        System.out.println(message);
    }
}

/*打印输出*/
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程1运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...
线程2运行中...

(3)优缺点

  • 优点: 线程任务类只是实现接口,可以继承其它类和实现其它接口,扩展性强
  • 缺点: 编程多一层对象包装,如果线程有执行结果是不可以直接返回的。

(4)匿名内部类实现方案

public class ThreadDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程1运行中");
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程2运行中");
            }
        });

        t1.start();
        t2.start();
    }
}

4、方法三:实现Callable接口

(1)使用场景: 当需要线程直接返回结果的场景。

(2)实现方式

  • 得到任务对象
    • 定义类实现Callable接口,重写call方法,封装任务
    • 用FutureTask把Callable对象封装成线程任务对象
  • 将线程任务对象交付Thread处理
  • 调用start(),执行任务
  • 通过调用

(3)实现示例

public class CallableDemo {
    public static void main(String[] args) {
        /*获取线程任务类对象*/
        Callable<String> myCallable = new MyCallable(10);
        Callable<String> myCallable1 = new MyCallable(20);

        /*FutureTask 封装现成任务类对象*/
        FutureTask<String> f1 = new FutureTask<>(myCallable);
        FutureTask<String> f2 = new FutureTask<>(myCallable1);

        /*交给Thread执行*/
        Thread t = new Thread(f1);
        Thread t1 = new Thread(f2);

        t.start();
        t1.start();

        /*FutureTask 调用get方法获取返回值*/
        try {
            System.out.println(f1.get());
            System.out.println(f2.get());
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}

class MyCallable implements Callable<String>{

    private int num;

    public MyCallable() {
    }

    public MyCallable(int num) {
        this.num = num;
    }

    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i < num; i++) {
            sum += i;
        }
        return "子线程执行结果为 " + sum;
    }
}

/*打印输出*/
子线程执行结果为 45
子线程执行结果为 190

三、Thread常用API

1、问题:当有多个线程同时执行时,如何区分线程?

方法说明
String getName()获取当前线程的名称
String setName()重命名当前线程的名称
Thread currentThread()获取当前线程对象

2、实现示例

  public class MultiThreadDemo {
    public static void main(String[] args) {
        /*创建线程*/
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"输出:"+i);
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"输出:"+i);
            }
        });
        /*重命名线程*/
        t1.setName("子线程1");
        t2.setName("子线程2");
        /*启动线程*/
        t1.start();
        t2.start();
    }
}

/*打印输出*/
子线程1输出:0
子线程2输出:0
子线程1输出:1
子线程2输出:1
子线程1输出:2
子线程2输出:2
子线程1输出:3
子线程2输出:3
子线程1输出:4
子线程2输出:4
子线程1输出:5
子线程2输出:5
子线程1输出:6
子线程2输出:6
子线程1输出:7
子线程2输出:7
子线程1输出:8
子线程2输出:8
子线程1输出:9
子线程2输出:9

3、修改线程名称的方案——父类构造器

public class MultiThreadDemo {
    public static void main(String[] args) {
        OThread o1 = new OThread("子线程1");
        OThread o2 = new OThread("子线程2");
        o1.start();
        o2.start();
    }
}

class OThread extends Thread{
    public OThread(){}

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

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"执行输出:" + i);
        }
    }
}

/*打印输出*/
子线程1执行输出:0
子线程2执行输出:0
子线程1执行输出:1
子线程2执行输出:1
子线程1执行输出:2
子线程2执行输出:2
子线程1执行输出:3
子线程2执行输出:3
子线程1执行输出:4
子线程2执行输出:4
子线程1执行输出:5
子线程2执行输出:5
子线程1执行输出:6
子线程2执行输出:6
子线程1执行输出:7
子线程2执行输出:7
子线程1执行输出:8
子线程2执行输出:8
子线程1执行输出:9
子线程2执行输出:9

4、Thread类的线程休眠

方法说明
static void sleep(long time)让当前线程休眠指定时间后再继续执行,单位为毫秒

5、Thread构造器

构造器说明
Thread(String name)为当前线程指定名称
Thread(Runnable target)把Runnable对象交给线程对象
Thread(Runnable target , String name)把Runnable对象交给线程对象,为当前线程指定名称

四、线程安全

1、相关问题

问题1:线程安全问题是什么?

答:多线程同时操作一个共享数据时可能会出现业务安全问题,称为线程安全问题。

问题2:线程安全问题的产生原因?

答:在多线程并发下,多个线程同时访问并修改一个共享数据,便会导致共享数据多次修改的结果。

示例

public class ThreadSafeDemo {

    /*创建一个共享账号,并存入10W元*/
    public static Account account = new Account("ZGYH-1311",100000);

    public static void main(String[] args) {
        CardThread user1 = new CardThread(account,"小明");
        CardThread user2 = new CardThread(account,"小红");

        user1.start();
        user2.start();
    }
}

class Account{
    private String CARD;
    private double money;

    public Account() {
    }

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

    public String getCARD() {
        return CARD;
    }

    public void setCARD(String CARD) {
        this.CARD = CARD;
    }

    public double getMoney() {
        return money;
    }

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

    public void drawnMoney(double money){
        if (money <= this.money){
            this.money -= money;
            System.out.println(Thread.currentThread().getName()+"成功取出"+money+"元");
        }else {
            System.out.println("余额不足,无法取出");
        }
    }
}

class CardThread extends Thread{
    private Account account;

    public CardThread(Account account,String name) {
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
        account.drawnMoney(100000);
    }
}

/*打印输出*/
余额不足,无法取出
小明成功取出100000.0

2、解决方案:线程同步


五、线程同步

1、线程同步的概念: 多线程同步执行相同操作。

2、线程同步的核心思想

  • 加锁: 将共享资源上锁,每一次只允许一个线程访问,只有当前线程访问后其它线程才能访问。

3、同步的实现方式
(1)方式一:同步代码块

  • 作用: 将可能出现线程安全问题的核心代码上锁。
  • 原理: 每次只能一个线程进入,执行完毕后自动解锁,其它线程才可以进来执行。
  • 格式
synchronized(同步锁对象){
	操作共享资源的代码(核心代码)
}
  • 锁对象要求:

理论上,锁对象只要对于当前同时执行的线程来说是同一个对象即可,但对于其它无关线程同样会产生影响,所以锁对象有规范。

规范
a. 建议使用共享资源作为锁对象
b. 对于实例方法,建议采用this关键字
c. 对于静态方法,建议采用.class对象作为锁对象

示例

/*以上面的例子,改变为是将核心代码加同步锁*/
    public void drawnMoney(double money){
        synchronized (this){
            if (money <= this.money){
                this.money -= money;
                System.out.println(Thread.currentThread().getName()+"成功取出"+money+"元");
            }else {
                System.out.println("余额不足,无法取出");
            }
        }
    }

/*打印输出*/
小明成功取出100000.0元
余额不足,无法取出

/*静态方法*/
    public static void print(String message){
        synchronized (Account.class){
            System.out.println(message);
        }
    }

(2)同步方法
使用方法同同步代码块一致。

  • 作用: 把出现线程安全问题的方法使用synchronized 关键字加锁。
  • 格式
修饰符 synchronized 返回值类型 方法名称(形参列表){
	操作代码....
}

示例

	public synchronized void drawnMoney(double money){
           if (money <= this.money){
               this.money -= money;
               System.out.println(Thread.currentThread().getName()+"成功取出"+money+"元");
           }else {
               System.out.println("余额不足,无法取出");
           }
   }
  • 底层原理
    • 同步方法其实底层也是有隐式锁对象的,若锁方法为实例方法则默认使用this作为锁对象,若所方法为静态方法则默认使用.class作为锁对象。

相关问题

问题1:同步代码块与同步方法的比较

答:同步代码块锁范围小,同步方法锁范围大

(3)Lock锁

  • 概念: 为了更清晰的表达如何加锁和释放锁,JDK5以后提供一个新的锁对象Lock,Lock实现提供比使用synchronized方法和语句更加广泛的锁定操作。

  • 对象类型: 接口

  • 创建方式:

构造器说明
ReentrantLock()获取Lock锁实现类
  • 常用API
方法说明
void lock()获取锁
void unlock()释放锁

使用方法

  • 在对象类中声明一个常量Lock
  • 在核心代码处使用lock(),unlock()
class Account {
    private String CARD;
    private double money;

    private final Lock lock = new ReentrantLock();

    public Account() {
    }

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

    public void drawnMoney(double money) {
        lock.lock();
        /*为避免因为异常导致死锁,所以要捕捉异常并解锁*/
        try {
            if (money <= this.money) {
                this.money -= money;
                System.out.println(Thread.currentThread().getName() + "成功取出" + money + "元");
            } else {
                System.out.println("余额不足,无法取出");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void print(String message) {
        synchronized (Account.class) {
            System.out.println(message);
        }
    }
}

六、线程通信

1、概念: 线程之间相互发送数据,通常是通过一个共享数据进行通信。

2、常见通信模型

  • 生产者与消费者模型: 生产者线程负责生产数据,消费者线程负责消费数据
    实现方式: 生产者线程生产完数据后,唤醒消费者,然后等待自己;消费者消费数据后,唤醒生产者,然后等待自己。
    唤醒与等待
方法说明
void wait()当前线程等待唤醒
void notify()当前线程等待唤醒
void notifyAll()当前线程等待唤醒

实现示例

public class ThreadCommunication {

    public static Card account = new Card("ZGYH-1311",0);

    public static void main(String[] args) {
        /*创建爹对象,生产者*/
        Saver saver1 = new Saver(account,"亲爹");
        Saver saver2 = new Saver(account,"干爹");
        Saver saver3 = new Saver(account,"岳父");

        /*创建子辈对象,消费者*/
        Customer customer1 = new Customer(account,"小明");
        Customer customer2 = new Customer(account,"小红");

        saver1.start();
        saver2.start();
        saver3.start();
        customer1.start();
        customer2.start();

    }
}

class Card{
    private String card;
    private double money;

    private final Lock lock = new ReentrantLock();

    public Card(){}

    public Card(String card,double money){
        this.card = card;
        this.money = money;
    }

    public String getCard() {
        return card;
    }

    public double getMoney() {
        return money;
    }

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

    public synchronized void save(double money){
        try {
            if (this.money == 0 ){
                this.money += money;
                System.out.println(Thread.currentThread().getName()+"已向"+this.getCard()+"存入金额"+money+"元,剩余金额:"+this.getMoney());
            }
            this.notifyAll();
            this.wait();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public synchronized void get(double money){
        this.notify();
        try {
            if (this.money != 0){
                this.money -= money;
                System.out.println(Thread.currentThread().getName()+"取出金额"+money+",剩余金额:"+this.getMoney());
            }
            this.notifyAll();
            this.wait();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

class Saver extends Thread{
    private Card account;

    public Saver(){}

    public Saver(Card account,String name) {
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
        while (true){
            account.save(100000);
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class Customer extends Thread{
    private Card account;

    public Customer(){}

    public Customer(Card account,String name) {
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
        while (true){
            account.get(100000);
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

/*打印输出*/
亲爹已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小明取出金额100000.0,剩余金额:0.0
岳父已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小红取出金额100000.0,剩余金额:0.0
岳父已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小明取出金额100000.0,剩余金额:0.0
岳父已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小红取出金额100000.0,剩余金额:0.0
干爹已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小明取出金额100000.0,剩余金额:0.0
岳父已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小红取出金额100000.0,剩余金额:0.0
亲爹已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小明取出金额100000.0,剩余金额:0.0
岳父已向ZGYH-1311存入金额100000.0元,剩余金额:100000.0
小红取出金额100000.0,剩余金额:0.0


七、线程池

1、线程池的概念: 线程池是一个可复用线程的技术。
2、使用线程池的原因: 用户每发起一个请求,后台就创建一个新线程,当下次新任务请求又要创建新县城,而新线程的创建对系统资源的开销是极大的。
3、线程池的工作原理:
(1)线程池由两部分组成:工作线程WorkThread和任务队列WorkQueue
(2)新任务请求会被加入到任务队列中,工作线程由多个线程组成,每一个线程完成任务后,会从任务队列中调取请求进行处理。
4、线程池API
(1)接口: ExecutorService
(2)创建方法:

  • 方法1: 使用ExecutorService的实现类ThreadPoolExecutor创建线程池对象
public ThreadPoolExecutor(int corePoolSize, 	//线程池的线程数量(核心线程)
                              int maximumPoolSize,	//线程池支持的最大线程数
                              long keepAliveTime,	//临时线程的最大存货时间
                              TimeUnit unit,	//存活时间的单位(时、分、秒、天)
                              BlockingQueue<Runnable> workQueue,	//指定任务队列
                              ThreadFactory threadFactory,	//制定线程工厂
                              RejectedExecutionHandler handler)	//指定线程忙,任务满时,新任务请求的处理

新任务拒绝策略

策略说明
ThreadPoolExecutor.AbortPolicy丢弃并抛出RejectedExecutionException异常(默认)
ThreadPoolExecutor.DiscardPolicy直接丢弃任务
ThreadPoolExecutor.DiscardOldestPolicy丢弃队列中等待最久的任务,任晗把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy由主线程负责调用任务的run()方法,从而绕过线程池直接执行

问题1:临时线程什么时候创建?

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

问题2:什么时候回开始拒绝任务?

答:核心线程和临时线程都在忙,任务队列满了,新任务来的时候开始任务拒绝。

(3)ExecutorService常用方法

方法说明
void execute(Runnable command)执行任务,没有返回值,一般用来执行Runnable任务
void execute(Callable<T> task)执行任务,返回未来任务对象获取线程结果,一般用来执行Callable任务
Future submit(Runnable command)Runnable任务加入到任务队列中
Future submit(Callable<T> task)Callable任务加入到任务队列中
void shutdown()任务执行完毕关闭线程池
List<Runnable> shutdownNow()()like关闭,停止正在执行任务,并返回队列中为执行的任务

(4)线程池处理Runnable任务

  • 创建线程池对象
/*创建含有3个核心线程,5个最大线程数,8秒线程存活时间,6个容量的任务队列,默认线程工厂,丢弃任务并抛出异常的线程池*/
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,new ArrayBlockingQueue<>(6), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

(5)线程池处理Callable任务

public class ThreadPoolDemo {
    public static void main(String[] args) {
        /*创建含有3个核心线程,5个最大线程数,8秒线程存活时间,6个容量的任务队列,默认线程工厂,拒绝任务的线程池*/
        ExecutorService executorService = new ThreadPoolExecutor(3,5,5, TimeUnit.SECONDS,new ArrayBlockingQueue<>(6), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

        Runnable runnable = new newRunnable();

        /*模拟10个任务进入线程池*/
        for (int i = 0; i < 10; i++) {
            executorService.execute(runnable);
        }

        executorService.shutdown();
    }
}

class newRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出:" + i + "时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(System.currentTimeMillis()));
        }
    }
}

/*打印输出*/
pool-1-thread-3输出:0时间:2023-06-01 12:00:59.783
pool-1-thread-1输出:0时间:2023-06-01 12:00:59.783
pool-1-thread-2输出:0时间:2023-06-01 12:00:59.783
pool-1-thread-4输出:0时间:2023-06-01 12:00:59.783
pool-1-thread-3输出:1时间:2023-06-01 12:00:59.818
pool-1-thread-2输出:1时间:2023-06-01 12:00:59.818
pool-1-thread-1输出:1时间:2023-06-01 12:00:59.818
pool-1-thread-4输出:1时间:2023-06-01 12:00:59.818
pool-1-thread-3输出:2时间:2023-06-01 12:00:59.818
pool-1-thread-2输出:2时间:2023-06-01 12:00:59.818
pool-1-thread-1输出:2时间:2023-06-01 12:00:59.818
pool-1-thread-4输出:2时间:2023-06-01 12:00:59.818
pool-1-thread-3输出:3时间:2023-06-01 12:00:59.818
pool-1-thread-2输出:3时间:2023-06-01 12:00:59.818
pool-1-thread-4输出:3时间:2023-06-01 12:00:59.818
pool-1-thread-1输出:3时间:2023-06-01 12:00:59.818
pool-1-thread-3输出:4时间:2023-06-01 12:00:59.818
pool-1-thread-4输出:4时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:4时间:2023-06-01 12:00:59.819
pool-1-thread-2输出:4时间:2023-06-01 12:00:59.818
pool-1-thread-4输出:0时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:0时间:2023-06-01 12:00:59.819
pool-1-thread-4输出:1时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:1时间:2023-06-01 12:00:59.819
pool-1-thread-2输出:0时间:2023-06-01 12:00:59.819
pool-1-thread-4输出:2时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:2时间:2023-06-01 12:00:59.819
pool-1-thread-3输出:0时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:3时间:2023-06-01 12:00:59.819
pool-1-thread-2输出:1时间:2023-06-01 12:00:59.819
pool-1-thread-4输出:3时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:4时间:2023-06-01 12:00:59.819
pool-1-thread-3输出:1时间:2023-06-01 12:00:59.819
pool-1-thread-1输出:0时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:4时间:2023-06-01 12:00:59.820
pool-1-thread-2输出:2时间:2023-06-01 12:00:59.819
pool-1-thread-3输出:2时间:2023-06-01 12:00:59.820
pool-1-thread-1输出:1时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:0时间:2023-06-01 12:00:59.820
pool-1-thread-2输出:3时间:2023-06-01 12:00:59.820
pool-1-thread-3输出:3时间:2023-06-01 12:00:59.820
pool-1-thread-1输出:2时间:2023-06-01 12:00:59.820
pool-1-thread-2输出:4时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:1时间:2023-06-01 12:00:59.820
pool-1-thread-3输出:4时间:2023-06-01 12:00:59.820
pool-1-thread-1输出:3时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:2时间:2023-06-01 12:00:59.820
pool-1-thread-1输出:4时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:3时间:2023-06-01 12:00:59.820
pool-1-thread-4输出:4时间:2023-06-01 12:00:59.821
  • 方法2: 使用Executors(线程池工具类)调用方法返回不同特点的线程池对象

常用API

方法说明
static ExecutorService newCachedThreadPool()线程数量随任务增加而增加,如果线程任务执行完毕且空闲一段时间则回收线程
static ExecutorService newFixedThreadPool(int nThreads)创建固定数量的线程池,如果某个线程执行发生异常而结束,则线程池补充新线程替代
static ExecutorService newSingleThreadExecutor()相当于 newFixedThreadPool(1)
static ScheduledExecutorService newScheduledThreadPool(int corePoolSite)创建线程池,可以实现给定延迟后运行任务或定期完成任务

Excutors底层原理: 基于线程池实现类ThreadPoolExecutor创建线程池对象。

使用示例

**任务类 newRunnable **

class newRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出:" + i + "时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(System.currentTimeMillis()));
        }
    }
}

newCacheThreadPool()

public class ExecutorsDemo {
    public static void main(String[] args) {
        Runnable runnable = new newRunnable();
        System.out.println("——————————newCacheThreadPool——————————");
        /*线程数随着任务数量的变化而变化*/
        ExecutorService e = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            e.submit(runnable);
        }
        e.shutdown();
    }
}

/*打印输出*/
——————————newCacheThreadPool——————————
pool-1-thread-2输出:0时间:2023-06-01 13:55:43.601
pool-1-thread-5输出:0时间:2023-06-01 13:55:43.601
pool-1-thread-4输出:0时间:2023-06-01 13:55:43.601
pool-1-thread-2输出:1时间:2023-06-01 13:55:43.638
pool-1-thread-3输出:0时间:2023-06-01 13:55:43.601
pool-1-thread-1输出:0时间:2023-06-01 13:55:43.601
pool-1-thread-2输出:2时间:2023-06-01 13:55:43.638
pool-1-thread-4输出:1时间:2023-06-01 13:55:43.638
pool-1-thread-3输出:1时间:2023-06-01 13:55:43.638
pool-1-thread-5输出:1时间:2023-06-01 13:55:43.638
pool-1-thread-2输出:3时间:2023-06-01 13:55:43.638
pool-1-thread-1输出:1时间:2023-06-01 13:55:43.638
pool-1-thread-3输出:2时间:2023-06-01 13:55:43.639
pool-1-thread-4输出:2时间:2023-06-01 13:55:43.639
pool-1-thread-2输出:4时间:2023-06-01 13:55:43.639
pool-1-thread-5输出:2时间:2023-06-01 13:55:43.639
pool-1-thread-1输出:2时间:2023-06-01 13:55:43.639
pool-1-thread-4输出:3时间:2023-06-01 13:55:43.639
pool-1-thread-3输出:3时间:2023-06-01 13:55:43.639
pool-1-thread-5输出:3时间:2023-06-01 13:55:43.639
pool-1-thread-1输出:3时间:2023-06-01 13:55:43.639
pool-1-thread-4输出:4时间:2023-06-01 13:55:43.639
pool-1-thread-3输出:4时间:2023-06-01 13:55:43.639
pool-1-thread-5输出:4时间:2023-06-01 13:55:43.639
pool-1-thread-1输出:4时间:2023-06-01 13:55:43.639

newFixedThreadPool

public class ExecutorsDemo {
    System.out.println("——————————newFixedThreadPool——————————");
	ExecutorService e2 = Executors.newFixedThreadPool(2);
	for (int i = 0; i < 5; i++) {
           e2.submit(runnable);
        }

        e2.shutdown();
}

/*打印输出*/
——————————newFixedThreadPool——————————
pool-1-thread-1输出:0时间:2023-06-01 14:17:21.888
pool-1-thread-2输出:0时间:2023-06-01 14:17:21.888
pool-1-thread-1输出:1时间:2023-06-01 14:17:21.919
pool-1-thread-2输出:1时间:2023-06-01 14:17:21.919
pool-1-thread-1输出:2时间:2023-06-01 14:17:21.920
pool-1-thread-2输出:2时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:3时间:2023-06-01 14:17:21.920
pool-1-thread-2输出:3时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:4时间:2023-06-01 14:17:21.920
pool-1-thread-2输出:4时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:0时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:1时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:2时间:2023-06-01 14:17:21.920
pool-1-thread-1输出:3时间:2023-06-01 14:17:21.921
pool-1-thread-2输出:0时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:4时间:2023-06-01 14:17:21.921
pool-1-thread-2输出:1时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:0时间:2023-06-01 14:17:21.921
pool-1-thread-2输出:2时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:1时间:2023-06-01 14:17:21.921
pool-1-thread-2输出:3时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:2时间:2023-06-01 14:17:21.921
pool-1-thread-2输出:4时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:3时间:2023-06-01 14:17:21.921
pool-1-thread-1输出:4时间:2023-06-01 14:17:21.921

newSingleThreadExecutor

public class ExecutorsDemo {
        System.out.println("——————————newSingleThreadExecutor——————————");
        ExecutorService e3 = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            e3.submit(runnable);
        }

        e3.shutdown();
}

/*打印输出*/
——————————newSingleThreadExecutor——————————
pool-1-thread-1输出:0时间:2023-06-01 14:36:17.093
pool-1-thread-1输出:1时间:2023-06-01 14:36:17.120
pool-1-thread-1输出:2时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:3时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:4时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:0时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:1时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:2时间:2023-06-01 14:36:17.121
pool-1-thread-1输出:3时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:4时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:0时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:1时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:2时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:3时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:4时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:0时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:1时间:2023-06-01 14:36:17.122
pool-1-thread-1输出:2时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:3时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:4时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:0时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:1时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:2时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:3时间:2023-06-01 14:36:17.123
pool-1-thread-1输出:4时间:2023-06-01 14:36:17.123

newScheduledThreadPool

public class ExecutorsDemo {
        System.out.println("——————————newScheduledThreadPool——————————");
        ExecutorService e4 = Executors.newScheduledThreadPool(20);
        for (int i = 0; i < 5; i++) {
            e4.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"执行了");
                }
            });
        }

        e4.shutdown();
}

/*打印输出*/
——————————newScheduledThreadPool——————————
pool-1-thread-1执行了
pool-1-thread-3执行了
pool-1-thread-2执行了
pool-1-thread-4执行了
pool-1-thread-5执行了

使用Executors工具类可能产生的问题

  • 固定线程池与单例线程池产生的问题: 允许请求任务队列长度为Integer.MAX_VALUE,可能出现OOM错误(内存溢出)
  • 缓冲线程池与计划线程池产生的问题: 创建的线程数量为Integer.MAX_VALUE,线程数量随任务数量增加,可能会出现OOM错误。

八、定时器

1、概念: 是一种控制任务延时调用或周期调用的技术。

2、作用: 定时执行任务

3、实现方式
(1)Timer

构造器说明
Timer()创建计时器对象
方法说明
void schedule(TimerTask task,long delay,long period)创建带有任务,延时时间,间隔周期参数的执行方法

使用示例

public class TimerDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"执行了,执行时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(System.currentTimeMillis()));
            }
        },1000,2000); //延时1s开始,以2s为一个执行周期
    }
}

/*打印输出*/
Timer-0执行了,执行时间:2023-06-01 15:49:56.834
Timer-0执行了,执行时间:2023-06-01 15:49:58.812
Timer-0执行了,执行时间:2023-06-01 15:50:00.818

存在的问题

  • Timer是单线程,当有多个任务执行时,存在延时和设置定时器的时间有差异。
  • 可能因为某个任务异常而终止,影响后续执行。

(2)ScheduleExecutorService

  • 优点: 基于线程池,某个任务执行情况不会影响到其它线程。
方法说明
static ScheduledExecutorService newScheduledTreadPool(int corePoolSize)得到线程池对象
ScheduledFuture<?> scheduleAtFixedRate(Rinnable command,long delay,long period, TimeUnit unit)创建周期调度方法

使用示例


public class ScheduleDemo {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
        service.scheduleAtFixedRate(() -> {
            System.out.println(Thread.currentThread().getName()+"执行了,时间:"+new Date());
        },0,1, TimeUnit.SECONDS);
    }
}

九、补充知识
1、并发: 因CPU同时处理的线程有限,CPU会轮询每一个线程服务,由于CPU切换速度极快,因此我们会认为这些线程都是同时执行的,这就叫并发。
2、并行: CPU可多线程执行的能力叫并发。
3、生命周期: 是线程从创建到销毁的过程。
(1)六种状态

  • NEW——新建状态
  • RUNNABLE——运行状态
  • BLOCKED——锁阻塞状态
  • WATTING——等待状态
  • TIMED_WATING——计时等待状态
  • TERMINATED——终止死亡状态
    在这里插入图片描述

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

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

相关文章

HTTP 教程2

HTTP 消息结构 HTTP是基于客户端/服务端&#xff08;C/S&#xff09;的架构模型&#xff0c;通过一个可靠的链接来交换信息&#xff0c;是一个无状态的请求/响应协议。 一个HTTP"客户端"是一个应用程序&#xff08;Web浏览器或其他任何客户端&#xff09;&#xff…

什么是C/S架构?与B/S架构有什么区别?

1、 1.1、C/S架构的全称是Client/Server&#xff0c;即客户端/服务器体系结构&#xff0c;主要应用于局域网内。 1.2、B/S架构的全称为Browser/Server&#xff0c;即浏览器/服务器结构。百度安全验证https://baijiahao.baidu.com/s?id1742761249590653499&wfrspider&f…

微信小程序——CSS限制文字宽度和行数(溢出显示省略号)

手把手教你学会判断用户在做向上滑动还是向下滑动 知识回调&#xff08;不懂就看这儿&#xff01;&#xff09;场景复现核心干货单行文本溢出显示省略号多行文本溢出显示省略号 知识回调&#xff08;不懂就看这儿&#xff01;&#xff09; 知识专栏专栏链接微信小程序专栏http…

基于HTML+CSS+JavaScript的在线图书阅读网页设计

目 录 1.项目总体设计 2 1.1需求分析 2 1.2网站结构分析 3 1.2.1导航栏 3 1.2.2主体部分 3 1.3网络风格分析 3 1.4网站结构图 3 2.项目详细设计 4 2.1登录页面设计 4 2.2主页页面设计 5 2.3在线读书页面设计 6 3.项目总结 7 4.参考文献 7 此网页能够满足喜欢看书的书友&#x…

深度学习 - 53.Bert 简介与 Keras-Bert 常用示例

目录 一.引言 二.Bert 简介 1.Embedding Layer 2.Encoder layer 3.Pre-training 与 Fine-Tuning 三.Keras-Bert 常用 Demo 1.获取预训练模型 2.加载预训练模型 3.Fill Text 4.IsCorrelation 5.Get Embedding 6.完整代码 四. Fine-Tuning 五.Bert VS OpenAI GPT …

chatgpt赋能python:Python中等待一秒的语句:让你的程序暂停与等待

Python中等待一秒的语句&#xff1a;让你的程序暂停与等待 当编写Python程序时&#xff0c;经常需要添加暂停或延迟功能&#xff0c;以使程序能够在执行某些操作之前或之后等待一段时间。Python拥有一个内置的语句可以实现这种延迟&#xff1a;time.sleep()。 什么是 time.sl…

四信大气环保远程监测平台,实现大气网格化、精准化监测

近年来&#xff0c;随着国民经济快速发展&#xff0c;我国工业化、城镇化进程加快&#xff0c;随之造成的大气污染问题日益严峻&#xff0c;严重影响人们日常生活幸福指数与身体健康。为此&#xff0c;中共中央、国务院先后发布一系列文件&#xff0c;要求加强大气环境监测&…

价格限制与经济福利

价格控制 实行价格控制通常是政府相信市场价格对买方或卖方不公平两种价格控制&#xff1a;价格上限和价格下限 价格上限&#xff1a; 法定最高价格&#xff0c;任何人不得收取或付出高于此的价格。 价格下限&#xff1a; 法定最低价格&#xff0c;任何人不得收取或付出低于…

为什么亚马逊股价会在今年上涨?亚马逊股价2023年还会继续上涨吗?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 为什么亚马逊的股价会上涨&#xff1f; 今年以来&#xff0c;亚马逊&#xff08;AMZN&#xff09;的股价已经上涨了20%以上&#xff0c;涨幅达到了23.3%。而同期标普500指数今年以来仅上涨了8.2%。 猛兽财经认为&#xff0c…

数据链路层:媒体接入控制

1.数据链路层&#xff1a;媒体接入控制 笔记来源&#xff1a; 湖科大教书匠&#xff1a;媒体接入控制的基本概念 湖科大教书匠&#xff1a;随机接入–CSMA/CD协议 湖科大教书匠&#xff1a;随机接入–CSMA/CA协议 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记…

微信小程序——监听页面滑动(二)判断用户在做向上滑动还是向下滑动(onScrollPage scroll-view)

手把手教你学会判断用户在做向上滑动还是向下滑动 知识回调&#xff08;不懂就看这儿&#xff01;&#xff09;场景复现核心干货onPageScroll结合scrollTop实现了解touch方法 知识回调&#xff08;不懂就看这儿&#xff01;&#xff09; 知识专栏专栏链接微信小程序专栏https:…

1.WebGL与Shader介绍

webgl介绍 WebGL是一种用于在网页浏览器中创建交互式3D图形的技术。它基于OpenGL ES 2.0&#xff0c;这是一个广泛使用的嵌入式系统3D图形API。以下是webgl的发展史&#xff1a; WebGL允许开发人员使用JavaScript编写代码来控制GPU&#xff08;图形处理单元&#xff09;&…

字符集、字符编码格式检测和转码

目录 1 locale与字符集 1.1 locale 1.2 字符集 2 常见字符集 2.1 Native ANSI 字符集 2.1.1 ASCII 2.1.2 ISO-8859-1 2.1.3 GB2312&#xff0c;GBK&#xff0c;GB18030 2.2 Unicode 字符集 2.2.1 UCS 2.2.2 UTF - Unicode Transformation Format 2.2.3 UTF-8 2.2.4 B…

Spring Boot如何实现分布式消息队列

Spring Boot如何实现分布式消息队列 在分布式系统中&#xff0c;消息队列是非常重要的一部分&#xff0c;可以帮助开发人员实现异步处理、解耦系统、提高系统可靠性等。本文将介绍如何使用 Spring Boot 实现分布式消息队列。 1. 消息队列的设计 消息队列是一种存储消息的容器…

一键禁掉WIN10自动更新

工作了很久&#xff0c;没备份睡觉去了&#xff0c;一觉起来我东西呢&#xff1f; 正玩着游戏&#xff0c;激战正嗨的时候&#xff0c;蓝屏转圈圈开始更新。 无数次搜索怎么去除WIN10自动更新&#xff0c;每次按照网上的教程操作&#xff0c;结果都是无功而返。 下载了很多工…

Java开发 - 让你少走弯路的Redis主从实现单节点哨兵模式

前言 前一篇中&#xff0c;我们讲解了Redis主从的搭建方式&#xff0c;其实很简单呐有木有&#xff0c;都是配置&#xff0c;连句代码都没有&#xff0c;是不是感觉高估了Redis主从的搭建方式&#xff1f;哈哈&#xff0c;没关系&#xff0c;跟着博主&#xff0c;包你全会。今…

Postgre 提示could not determine data type of parameter $4

目录 场景&#xff1a; 现象&#xff1a; 版本&#xff1a; 分析&#xff1a; 解决方式&#xff1a; 场景&#xff1a; 今天遇到现场环境连接Postgre数据库&#xff0c;日志提示could not determine data type of parameter $4&#xff0c;通过日志复制出完整sql&#xff…

软件测试练手项目,可以写进简历里面的(银行:金融:商城:外卖等等)

目录 一、引言 二、测试任务 三、测试进度 四、测试资源 五、测试策略 六、测试完成标准 七、风险和约束 八、问题严重程度描述和响应时间规范 九、测试的主要角色和职责 ​有需要实战项目的评论区留言吧&#xff01; 软件测试是使用人工或者自动的手段来运行或者测定…

旅游有哪些好玩的地方? 今天用python分析适合年轻人的旅游攻略

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 “旅”是旅行&#xff0c;外出&#xff0c;即为了实现某一目的而在空间上从甲地到乙地的行进过程&#xff1b; “游”是外出游览、观光、娱乐&#xff0c;即为达到这些目的所作的旅行。 二者合起来即旅游。所以&#…

文本三剑客——awk

文本编辑器awk 一、 awk工作原理1.命令格式2.awk常见的内建变量&#xff08;可直接用&#xff09;如下所示 二、awk的基础用法1.输出文件中的某一列2.根据特定条件筛选数据3.按照分隔符进行切割4.在匹配到特定字符串时执行操作5.BEGIN打印模式6.awk的分隔符用法 三、示例演示1.…