Thread
我们通过 new 一个 Thread 的子类,并调用其 start 方法来启动一个线程
public class MyThread extends Thread {
@Override
public void run() {
...
}
}
public static void main(String[] args) {
new MyThread().start();
}
问题:为什么调用的是 start 方法而不是 run 方法
1、当 new 一个 Thread 后,线程就进入了新建状态
2、这时调用 start 会启动一个线程并使线程进入了就绪状态,当分配到时间片后线程就可以开始运行了。 start 会执行线程的相应准备工作,然后自动执行 run 方法
3、但如果直接执行 run 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
它是一个同步方法,它会去调用 start0 方法,该方法是一个 native 方法,通过该方法会让虚拟机帮我们运行线程
Runnable
在 Thread 中,有两个构造方法
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
我们可以传入一个 Runnable 接口来创建线程
public class MyRunnableThread implements Runnable {
@Override
public void run() {
...
}
}
public static void main(String[] args) {
new Thread(new MyRunnableThread()).start();
}
使用 匿名内部类 与 Lambda 表达式简化:
public static void main(String[] args) {
// 优化1
new Thread(new Runnable() {
@Override
public void run() {
...
}
}).start();
// 优化2
new Thread(() -> {
...
}).start();
}
使用该方式创建线程的优点:接口灵活方便,可以使一个对象被多个线程共享
底层原理:静态代理模式
Callable
Callable 是 J.U.C 引入的一个创建线程的方式
问题:Callable 与 Runnable 的区别
1)Callable 可以通过 call 获得返回值,Runnable 需要借助共享变量获取
2)call 可以抛出异常,而 Runnable 只能通过 setDefaultUncaughtExceptionHandler() 的方式才能在主线程中捕捉到子线程异常
在 Runnable 中的体系中,Runnable 有一个子类接口:RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
而 RunnableFuture 有一个实现类:
public class FutureTask<V> implements RunnableFuture<V> {
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
...
}
在该类中,可以传入一个 Callable 接口来创建一个对象,这个对象可以作为 Runnable 接口的实现传入 Thread 借此创建线程
简单的说就是:通过 FutureTask 让 Callable 和 Runnable 产生联系
创建一个 Callable 接口,显示定义返回值类型,并覆写 call,抛出异常
实现 Callable 接口,设置返回值类型,覆写 call 方法并抛出异常
class MyCallableThread implemets Callable<Boolean> {
@Override
public Integer call() throws Exception {
return 1024;
}
}
public static void main(String[] args) {
FutureTask<Integer> future = new FutureTask<>(new MyCallableThread());
new Thread(future).start();
// 获取返回结果
Integer o = future.get();
}
线程池
通过线程池,我们提前创建多个线程放在其中,在时候的时候直接从池中获取,用完放回池中,可以在并发下节省很多时间
使用线程池的优点:
- 提高响应速度,减少了创建新线程的时间
- 降低资源消耗,重复利用线程池中线程
- 便于线程管理
线程池的主要作用:
- 进行线程的复用
- 控制最大并发数
- 进行线程的管理
线程池的创建
利用 Executors 工具类可以创建线程池(不推荐)
Executors.newSingleThreadExecutor(); // 单实例线程
Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池
Executors.newCachedThreadPool(); // 可伸缩的
...
在 J.U.C 中,有一个 Executor 接口,它类似于 Collection,其有一个子类接口:ExecutorService,创建的线程池的类型即为 ExecutorService
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 使用线程池中的线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
// 关闭线程池
threadPool.shutdown();
}
自定义线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
在使用 Executors 工具类创建线程池,都是利用 new ThreadPoolExecutor 来创建线程池
一般情况下,我们都使用该方式自定义线程池:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
七大参数说明:
参数 | 说明 |
---|---|
corePoolSize | 核心线程数量 |
maximumPoolSize | 最大线程数 |
keepAliveTime | 空闲线程存活时间 |
TimeUnit unit | 时间单位 |
BlockingQueue workQueue | 阻塞队列 |
ThreadFactory threadFactory | 线程工厂,用来创建线程 |
RejectedExecutionHandler handle | 拒绝策略,阻塞队列满时的拒绝方式 |
演示说明:
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3), // 需要设置阻塞队列大小
Executors.defaultThreadFactory(), // 默认g
new ThreadPoolExecutor.AbortPolicy // 拒绝策略,抛出异常
);
// 最大承载:阻塞队列 + max 的值,超过最大承载则使用拒绝策略
for (int i = 0; i < 9; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "ok");
});
}
threadPool.shutdown();
}
拒绝策略
在自定义线程池的时候,设置了线程池的最大线程数,以及一个固定大小的阻塞队列
当线程池所有线程都在工作,并且阻塞队列也满了的时候,如果还有线程需要开启,则需要使用拒绝策略来拒绝线程池的访问
四种拒绝策略:
- AbortPolicy:抛出异常 RejectedExecutionException
- CallerRunsPolicy:将任务退回到调用者
- DiscardPolicy:丢弃无法处理的任务,不予任何处理也不抛出异常
- DiscardOldestPolicy:丢弃队列中等待最久的任务,把当前任务加入队列中尝试再次提交当前任务
最大线程数的设定
问题:应该如何自定义线程池中的最大线程数
根据任务的类型进行确认
- CPU 密集型:CPU 核数 + 1
- IO 密集型:2 * CPU 核数
Runtime().getRuntime().availableProcessors(); // 获取 CPU 核数