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

news/2025/3/7 2:58:46/

一、线程(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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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);}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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{@Overridepublic 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{@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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.ppmy.cn/news/147337.html

相关文章

步步高 vivo Xplay X510w 开箱图解 顶级HIFI享受 高清多图 慎入

步步高vivo Xplay拥有5.7英寸 1080p FHD超视网膜全高清显示屏&#xff0c;搭载全新一代高通骁龙600 1.7GHz版本&#xff0c;拥有四个Krait 300核心&#xff0c;搭载频率更高、功耗更低的Adreno 320 GPU&#xff0c;提供强力视觉引擎。前置500W像素 88度广角镜头&#xff0c;后置…

vivoy27android版本,步步高vivo Y27 开启USB调试模式

我们要将步步高vivo Y27与电脑进行连接&#xff0c;就必须要打开步步高vivo Y27系统的调试模式&#xff0c;不同的系统版本打开调试模式的方法有所不同&#xff0c;在这里我们就谈谈步步高vivo Y27各种系统版本打开USB调试模式的方法。 1、针对Android 2.1-2.2版本的系统&#…

vivox5l的Android是5.0,步步高Vivo X5 X5L刷机教程(官方固件rom升级教程)

上一节主要是给大家集中说了一下步步高Vivo X5 X5L的官方rom刷机包&#xff0c;一说到官方的固件包相信大家心里都应该有底了&#xff0c;官方的固件包也就是相当于手机出厂时配备的原版系统了&#xff0c;也是最适合手机的系统包&#xff0c;当咱们的手机系统出问题的时候&…

vivo X90、vivo X90 Pro和vivo X90 Pro+的区别 参数对比哪个好

vivo X90系列共提供了四款配色&#xff1a;华夏红&#xff08;素皮&#xff09;&#xff0c;原黑&#xff08;素皮&#xff09;&#xff0c;冰蓝&#xff08;AG玻璃&#xff09;&#xff0c;至黑&#xff08;AG玻璃&#xff09;。 vivo X90&#xff1a;厚度: 至黑、冰蓝&#x…

【C++】C++ 11 智能指针

【C】C 11 智能指针 文章目录 【C】C 11 智能指针1.为什么需要智能指针2. C中智能指针和指针的区别是什么&#xff1f;3. C中的智能指针有哪些&#xff1f;分别解决的问题以及区别&#xff1f;&#xff08;1&#xff09;auto_ptr&#xff08;C98的方案&#xff0c;C11已经弃用&…

【CORS策略: Multiple CORS header ‘Access-Control-Allow-Origin‘ not allowed导致的跨域问题】

引起跨域CORS报错的原因有很多种&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 这里我的报错属于最后一条。 探索的原因 正常axios请求接口&#xff0c;调试接口时出现以下问题 Access to XMLHttpRequest at http://192.168.31.111:9000/api/user/sessio…

向后切片,正向切片和其他形式的切片

向后切片,正向切片和其他形式的切片 向后切片Backward Slices 假设我们希望确定哪些语句影响节点 n。 这是由 n 和在 n 处引用的变量。 我们只是&#xff1a; 从 n 回溯控制和流依赖边。 我们保留由此到达的节点。 一般后向切片 通常&#xff0c;我们的切片标准是一个节点和一…

009 - STM32学习笔记 - 中断

009 - STM32学习笔记 - 中断 这节的内容&#xff0c;野火的官方视频我反复看了好几次&#xff0c;但是感觉火哥在这块讲解的特别绕&#xff0c;理解起来很吃力&#xff0c;后来在看了一下其他老师的视频&#xff0c;结合一些书本资料和官方手册&#xff0c;才搞清楚STM32中断该…