Java 为什么多线程都是默认先运行完 main?
package com.qfedu.com;
/**
* @Author pengyu
* @Date 2022/8/10 19:39
*/
class A extends Thread {
@Override
public void run() {
System.out.println("run");
}
@Override
public synchronized void start() {
System.out.println("start");
}
}
public class Demo05 {
public static void main(String[] args) {
System.out.println("jjjj");
A a = new A();
new Thread(a).start();
/**
* 一个 Java程序从 main 开始之后,进程启动,为整个程序提供各种资源,
* 而此时将启动一个线程,这个线程就是主线程,它将调度资源,进行具体的操作。
* Thread、Runnable 的开启的线程是主线程下的子线程,
* 是父子关系,此时该 Java 程序即为多线程的,
* 这些线程共同进行资源的调度和执行。
* Java 中 main 方法启动的是一个进程也是一个主线程,
* main 方法里面的其他线程均为子线程,所以一般其他线程总是在 main 线程后执行。
* 控制台输出:
* jjjj
* run
*/
}
}
守护线程
setDaemon(boolean on)
- 将此线程标记为daemon线程或用户线程。当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
- 线程启动前必须调用此方法。
参数
如果 true,将此线程标记为守护线程
虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕
示例
package com.qfedu.demo;
/**
* @Author pengyu
* @Date 2022/8/10 19:56
*/
class B implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我是B:" + i);
}
}
}
class A implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是A:" + i);
}
}
}
public class Demo01 {
public static void main(String[] args) {
B b = new B();
Thread thread = new Thread(b);
//如果 true ,将此线程标记为守护线程
//当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
thread.setDaemon(true);
thread.start();
new Thread(new A()).start();
}
}
线程的等待与唤醒
方法
public final void wait()
:当前线程进入等待状态public final native void notify()
:随机唤醒一条锁对象对应线程中的一条(此线程必须是睡眠状态)public final void notifyAll( )
:唤醒所有锁(效率低)
因为wait需要释放锁,所以必须在synchronized中使用,没有锁时使用会抛出IllegalMonitorStateException
(正在等待的对象没有锁)
线程状态
当我们的线程被创建并启动以后,并不是一启动就进入了执行状态,也不是一直处于执行状态。下面来看一下线程的生命周期吧。
线程状态 | 发生条件 |
---|---|
NEW新建 | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable可运行 | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked锁阻塞 | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting无限等待 | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting及时等待 | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated被终止 | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
jion方法
只是线程中的一个方法,可以先执行当前线程再执行父线程
public final void join()
:让线程在当前线程优先执行,直至线程执行完毕时,再执行当前线程.public final void join(long millis)
:让线程执行millis毫秒,然后将线程执行器抛出,给其他线程争抢
示例
package com.qfedu.demo;
import com.qfedu.test.Factory;
/**
* @Author pengyu
* @Date 2022/8/11 19:12
*/
class Father implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程" + i);
}
}
}
class Son implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是子线程" + i);
}
}
}
public class Demo04 {
public static void main(String[] args) throws InterruptedException {
Father father = new Father();
new Thread(father).start();
Son son = new Son();
Thread thread = new Thread(son);
thread.start();
/**
* 不使用join方法,可能会出现主线程先打印
* 加了就会先打印子线程,并且打印完
*/
//thread.join();
for (int i = 0; i < 10; i++) {
System.out.println("我是主线程" + i);
}
}
}
生产者——消费者问题
我们需要有个中间的桥梁——车类
package com.qfedu.test;
/**
* @Author pengyu
* @Date 2022/8/10 20:42
*/
public class Goods {
private String name;
//这里我把价格的英文写成age了,不好意思,应该是price
private int age;
private boolean production;
public Goods(String name, int age, boolean production) {
this.name = name;
this.age = age;
this.production = production;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isProduction() {
return production;
}
public void setProduction(boolean production) {
this.production = production;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", age=" + age +
", production=" + production +
'}';
}
}
消费者
package com.qfedu.test;
/**
* @Author pengyu
* @Date 2022/8/10 20:43
* 消费者
*/
public class Customer implements Runnable {
private Goods goods;
public Customer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while (true) {
synchronized (goods) {
if (goods.isProduction()) {
try {
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("消费者购买了" + goods.getName() + ",价格:" + goods.getAge());
//改变是否生产属性进行if判定
goods.setProduction(true);
//唤醒工厂线程开始生产
goods.notify();
}
}
}
}
}
生产者
package com.qfedu.test;
/**
* @Author pengyu
* @Date 2022/8/10 20:44
* 生产者
*/
public class Factory implements Runnable {
private Goods goods;
public Factory(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while (true) {
int count = 0;
synchronized (goods) {
if (goods.isProduction()) {
if (count % 2 == 0) {
goods.setName("破二手车");
goods.setAge(90);
} else {
goods.setName("保时捷");
goods.setAge(90);
}
System.out.println("生产了" + goods.getName() + ",价格" + goods.getAge());
//改变是否生产属性进行if判定
goods.setProduction(false);
count++;
//唤醒消费者线程
goods.notify();
} else {
//生产者线程等待
try {
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
测试
package com.qfedu.test;
/**
* @Author pengyu
* @Date 2022/8/10 20:53
*/
public class Test01 {
public static void main(String[] args) {
Goods car = new Goods("雪佛兰", 1900, false);
Customer customer = new Customer(car);
new Thread(customer).start();
Factory factory = new Factory(car);
new Thread(factory).start();
}
}
线程池
阿里规范
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
Positive example 1:
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
Positive example 2:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
线程池的优势:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- **提高响应速度。**当任务到达时,任务可以不需要等到线程创建就能立即执行。
- **提高线程的可管理性。**线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的使用
线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
可以看到,其需要如下几个参数:
- corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
- maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
- keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
- unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
- workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
- threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
- handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
语法格式
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {
@Override
public void run() {
... // 线程执行的任务
}
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
示例
package com.qfedu.demo;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Author pengyu
* @Date 2022/8/11 18:53
* 线程池的创建
*/
public class Demo05 {
public static void main(String[] args) {
//corePoolSize :核心线程数 5个 一个公司最大容量10人 核心只有5个人 只有这5个人干活
//maximumPoolSize:线程池最多可以运行线程数 设置为10 约 5个亿左右
//keepAliveTime:存活时间,空闲下来的线程得回收。按照秒来计算。活动状态,比较活跃 不回收
//如果你不干活超过100秒,我就会回收掉这个线程!!!
//unit 是keepAliveTime 单位 可以是秒 小时 天 毫秒, 纳秒!!!
//workQueue 阻塞队列
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10));
for (int i = 0; i < 10; i++) {
int finalI = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + finalI);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}