创建线程的几种方式
1.实现 Runnable 接口
public class MyThread implements Runnable{ // 实现Runnable接口,作为线程的实现类
private String name ; // 表示线程的名称
public MyThread(String name){
this.name = name ; // 通过构造方法配置name属性
}
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
};
public class RunnableDemo01{
public static void main(String args[]){
MyThread mt1 = new MyThread("线程A ") ; // 实例化对象
MyThread mt2 = new MyThread("线程B ") ; // 实例化对象
Thread t1 = new Thread(mt1) ; // 实例化Thread类对象
Thread t2 = new Thread(mt2) ; // 实例化Thread类对象
t1.start() ; // 启动多线程
t2.start() ; // 启动多线程
}
};
2.继承 Thread 类
class MyThread extends Thread{ // 继承Thread类,作为线程的实现类
private String name ; // 表示线程的名称
public MyThread(String name){
this.name = name ; // 通过构造方法配置name属性
}
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
};
public class ThreadDemo02{
public static void main(String args[]){
MyThread mt1 = new MyThread("线程A ") ; // 实例化对象
MyThread mt2 = new MyThread("线程B ") ; // 实例化对象
mt1.start() ; // 调用线程主体
mt2.start() ; // 调用线程主体
}
};
3. 实现Callable接口
@Data
public class Callable01 implements Callable<String> {
private String name;
private Long sleep;
@Override
public String call() throws Exception {
System.out.println(name + ":开始运行");
Thread.sleep(sleep);
return name + "运行完成";
}
}
- Callable: 返回结果并且可能抛出异常的任务。
- 可以获得任务执行返回值;
- 通过与Future的结合,可以实现利用Future来跟踪异步计算的结果。
Runnable 和 Thread
核心源码如下【进行过代码裁剪】:继承于Thread并实现它的run方法能创建一个线程,实际这个run方法就是Runnable所提供的代理对象所有的,因此我们可以得到Runnable的全部优点
public class Thread implements Runnable {
private Runnable target;
private native void start(){
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
}
- Runable的优势总结
1.【多线程内部数据共享】适合多个相同程序代码的线程去处理同一个资源(多线程内的数据共享)
2.【继承】避免java特性中的单根继承限制
3.【解耦】增加程序健壮性,数据被共享时,仍然可以保持代码和数据的分离和独立
4.【设计】更能体现java面向对象的设计特点
错误的线程并发案例【Thread实现】
每个线程拥有自己的变量,线程之间没有共享数据
public class Test03ThreadMain extends Thread {
private int count = 5;
@Override
public void run() {
for (int i = 0; i < 7; i++) {
if (count > 0) {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "----currentIndex:" + (count--));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Test03ThreadMain h1 = new Test03ThreadMain();
Test03ThreadMain h2 = new Test03ThreadMain();
Test03ThreadMain h3 = new Test03ThreadMain();
h1.start();
h2.start();
h3.start();
}
}
正确的线程并发案例【Runable实现】
public class Test03ThreadRunable implements Runnable{
private int count = 5;
@Override
public void run() {
for (int i = 0; i < 7; i++) {
if (count > 0) {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "----currentIndex:" + (count--));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 一个执行任务,让多个线程进行执行
public static void main(String[] args) {
Test03ThreadRunable runable = new Test03ThreadRunable();
new Thread(runable, "1号窗口").start();
new Thread(runable, "2号窗口").start();
new Thread(runable, "3号窗口").start();
}
}
Runnable和Callable
- 1、Callable规定的方法是call(),Runnable规定的方法是run().
- 2、Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
- 3、call方法可以抛出异常,run方法不可以
- 4、运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
Future接口
- Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、等待完成和得到计算的结果。
- 当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
- 如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
- 一旦计算完成了,那么这个计算就不能被取消。
FutureTask类
- FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。
- FutureTask可以用来包装Callable或者Runnbale对象。因为FutureTask实现了Runnable接口,所以FutureTask也可以被提交给Executor(如上面例子那样)。
Callable两种执行方式
1、借助FutureTask执行
-
FutureTask类同时实现了两个接口,Future和Runnable接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
//定义实现Callable接口的的实现类重写call方法。 public class MyCallableTask implements Callable<Integer>{ @Override public Integer call() throws Exception { //TODO 线程执行方法 } } --------------------------------------------------------- //创建Callable对象 Callable<Integer> mycallabletask = new MyCallableTask(); //开始线程 FutureTask<Integer> futuretask= new FutureTask<Integer>(mycallabletask); new Thread(futuretask).start(); -------------------------------------------------------- 通过futuretask可以得到MyCallableTask的call()的运行结果: futuretask.get();
2、借助线程池来运行
```java
ExecutorService exec = Executors.newCachedThreadPool();
Future<Integer> future = exec.submit(new MyCallableTask());
```
举例说明
例1:
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException,TimeoutException{
//创建一个线程池
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> future = executor.submit(()-> {
TimeUnit.SECONDS.sleep(5);
return "CallableTest";
});
System.out.println(future.get());
executor.shutdown();
}
}
例2:Callable任务借助FutureTask运行:
public class CallableAndFutureTask {
Random random = new Random();
public static void main(String[] args) {
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
return random.nextInt(10000);
}
};
FutureTask<Integer> future = new FutureTask<Integer>(callable);
Thread thread = new Thread(future);
thread.start();
try {
Thread.sleep(2000);
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
例3:Callable任务和线程池一起使用,然后返回值是Future
public class CallableAndFuture {
Random random = new Random();
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
Future<Integer> future = threadPool.submit(new Callable<Integer>() {
public Integer call() throws Exception {
return random.nextInt(10000);
}
});
try {
Thread.sleep(3000);
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
例4:当执行多个Callable任务,有多个返回值时,我们可以创建一个Future的集合
class MyCallableTask implements Callable<String> {
private int id;
public OneTask(int id){
this.id = id;
}
@Override
public String call() throws Exception {
for(int i = 0;i<5;i++){
System.out.println("Thread"+ id);
Thread.sleep(1000);
}
return "Result of callable: "+id;
}
}
public class Test {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<Future<String>>();
for (int i = 0; i < 5; i++) {
results.add(exec.submit(new MyCallableTask(i)));
}
for (Future<String> fs : results) {
if (fs.isDone()) {
try {
System.out.println(fs.get());
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("MyCallableTask任务未完成!");
}
}
exec.shutdown();
}
}
CompletableFuture
CompletableFuture 是 JDK1.8 版本新引入的类,提供了非常强大的 Future 的扩展功能,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
构造方法
构造一个CompletableFuture
- CompletableFuture.supplyAsync(Supplier supplier):提供返回值;异步执行一个任务,默认用 ForkJoinPool.commonPool();
- CompletableFuture.supplyAsync(Supplier supplier,Executor executor):提供返回值;异步执行一个任务,但是可以自定义线程池;
- CompletableFuture.runAsync(Runnable runnable,Executor executor) :没有返回值;异步执行一个任务,但是可以自定义线程池;
- CompletableFuture.runAsync(Runnable runnable):没有返回值;异步执行一个任务, 默认用 ForkJoinPool.commonPool();
package com.example.thread.callable;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import lombok.Data;
import lombok.SneakyThrows;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Supplier;
/**
* @Package: com.example.thread.callable
* @ClassName: Supplier01
* @Author: xu kang kai
* @Description:
* @Date: 2022/12/7 14:34
* @Version: 1.0
*/
@Data
public class Supplier01 implements Supplier<String> {
private String name;
private Long sleep;
@SneakyThrows
@Override
public String get() {
System.out.println(name + ":开始运行");
Thread.sleep(sleep);
return name + "运行完成";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<CompletableFuture<String>> results = new ArrayList<>();
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
Supplier01 call1 = new Supplier01();
call1.setName("线程" + i);
call1.setSleep(2000L);
CompletableFuture<String> objectCompletableFuture = CompletableFuture.supplyAsync(call1).whenCompleteAsync((x, y) -> {
System.out.println(x);
countDownLatch.countDown();
});
results.add(objectCompletableFuture);
}
// 为了获取所有线程执行完的结果 用CountDownLatch 和下面的 CompletableFuture.allOf.都是可以的join()
// CompletableFuture<Void> allCF = CompletableFuture.allOf(results.toArray(new CompletableFuture[0]));
// 阻塞,直到所有任务结束。任务complete就会执行, handler里面不一定会执行..
// allCF.join();
for (CompletableFuture<String> result : results) {
// 如果执行成功
result.thenAccept((re) -> {
System.out.println("price: " + re);
});
// 如果执行异常:
result.exceptionally((e) -> {
e.printStackTrace();
return null;
});
// 直接获取结果
// System.out.println(result.get());
}
countDownLatch.await();
}
}
线程的几种状态
在Java程序中,一个线程对象只能调用一次start()方法启动新线程,并在新线程中执行run()方法。一旦run()方法执行完毕,线程就结束了。
因此,Java线程的状态有以下几种:
-
New:新创建的线程,尚未执行;
-
Runnable:运行中的线程,正在执行run()方法的Java代码;
-
Blocked:运行中的线程,因为某些操作被阻塞而挂起;
-
Waiting:运行中的线程,因为某些操作在等待中;
-
Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
-
Terminated:线程已终止,因为run()方法执行完毕。
当线程启动后,它可以在Runnable、Blocked、Waiting和Timed Waiting这几个状态之间切换,直到最后变成Terminated状态,线程终止。
线程终止的原因有:
线程正常终止:run()方法执行到return语句返回;
线程意外终止:run()方法因为未捕获的异常导致线程终止;
对某个线程的Thread实例调用stop()方法强制终止(强烈不推荐使用)。
一个线程还可以等待另一个线程直到其运行结束。例如,main线程在启动t线程后,可以通过t.join()等待t线程结束后再继续运行:
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("hello");
});
System.out.println("start");
t.start();
t.join();
System.out.println("end");
}
}
当main线程对线程对象t调用join()方法时,主线程将等待变量t表示的线程运行结束,即join就是指等待该线程结束,然后才继续往下执行自身线程。所以,上述代码打印顺序可以肯定是main线程先打印start,t线程再打印hello,main线程最后再打印end。
如果t线程已经结束,对实例t调用join()会立刻返回。此外,join(long)的重载方法也可以指定一个等待时间,超过等待时间后就不再继续等待。
中断线程
中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,
目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.start();
Thread.sleep(1); // 暂停1毫秒
t.interrupt(); // 中断t线程
t.join(); // 等待t线程结束
System.out.println("end");
}
}
class MyThread extends Thread {
public void run() {
int n = 0;
while (!isInterrupted()) {
n ++;
System.out.println(n + " hello!");
}
}
}
仔细看上述代码,main线程通过调用t.interrupt()方法中断t线程,但是要注意,interrupt()方法仅仅向t线程发出了“中断请求”,至于t线程是否能立刻响应,要看具体代码。而t线程的while循环会检测isInterrupted(),所以上述代码能正确响应interrupt()请求,使得自身立刻结束运行run()方法。
线程池
几种常见线程池
- newCachedThreadPool
数量无上限,该线程池会根据需要创建,但是优先使用之前构造的线程。
这些池通常将提高执行许多短期异步任务的程序的性能,如果没有可用的现有线程,则将创建一个新线程并将其添加到池中。
当60S内未使用的线程将被终止并从缓存中删除。 因此,保持空闲时间足够长的池不会消耗任何资源。
构造函数
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- newFixedThreadPool
数量固定大小,该线程池重用在共享的无边界队列上运行的固定数量的线程。
在任何时候,最多nThreads(构造函数的参数,核心线程数与最大线程数相等)个线程都是活动的处理任务。
如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到某个线程可用为止。
如果在关闭之前执行过程中由于执行失败导致任何线程终止,则在执行后续任务时将使用新线程代替。 池中的线程将一直存在,直到明确将其关闭
构造函数
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
ThreadPoolExecutor的重要参数
ThreadPoolExecutor主要有6个参数,构造方法如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
名称 | 含义 |
---|---|
corePoolSize | 核心线程数 核心线程会一直存活,当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭 |
maximumPoolSize | 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常 |
keepAliveTime | 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize如果allowCoreThreadTimeout=true,则会直到线程数量=0 |
unit | 时间单位 |
workQueue | 任务队列 |
threadFactory | 线程工厂,用来创建新线程 |
handler | 处理被拒绝的任务 |
RejectedExecutionHandler 参数 四种拒绝策略
RejectedExecutionHandler是一个接口:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
里面只有一个方法。当要创建的线程数量大于线程池的最大线程数的时候,新的任务就会被拒绝,就会调用这个接口里的这个方法。
可以自己实现这个接口,实现对这些超出数量的任务的处理。
ThreadPoolExecutor自己已经提供了四个拒绝策略,分别是CallerRunsPolicy,AbortPolicy,DiscardPolicy,DiscardOldestPolicy
这四个拒绝策略其实一看实现方法就知道很简单。
-
策略1:ThreadPoolExecutor.AbortPolicy()
中止策略,将抛出 RejectedExecutionException。(默认情况)
在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。
使用场景:当系统不能承载更大的并发量的时候,能够及时的通过异常发现。下面是他的实现:
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
- 策略2:ThreadPoolExecutor.CallerRunsPolicy()
CallerRunsPolicy在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务。
下面是他的实现:
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
- 策略3:ThreadPoolExecutor.DiscardPolicy()
这个东西什么都没干。
因此采用这个拒绝策略,会让被线程池拒绝的任务直接抛弃,不会抛异常也不会执行。
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
- 策略4:ThreadPoolExecutor.DiscardOldestPolicy()
DiscardOldestPolicy策略的作用是,当任务呗拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
在rejectedExecution先从任务队列总弹出最先加入的任务,空出一个位置,然后再次执行execute方法把任务加入队列。
测试四种策略
先自定义一个Runnable,给每个线程起个名字,下面都用这个Runnable
public class MyThread implements Runnable {
String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+Thread.currentThread().getName() +" 执行:"+name +" run");
}
}
然后构造一个核心线程是1,最大线程数是2的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 0,
TimeUnit.MICROSECONDS,
new LinkedBlockingDeque<Runnable>(2),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 6; i++) {
System.out.println("添加第"+i+"个任务");
executor.execute(new MyThread("线程"+i));
}
自定义拒绝策略
static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
new Thread(r,"新线程"+new Random().nextInt(10)).start();
}
}
二、ThreadPoolExecutor执行顺序
线程池按以下行为执行任务
- 当线程数小于核心线程数时,创建线程。
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满
若线程数小于最大线程数,创建线程
若线程数等于最大线程数,抛出异常,拒绝任务
如何设置参数
默认值
corePoolSize=1
queueCapacity=Integer.MAX_VALUE
maxPoolSize=Integer.MAX_VALUE
keepAliveTime=60s
allowCoreThreadTimeout=false
rejectedExecutionHandler=AbortPolicy()
需要根据几个值来决定
- tasks :每秒的任务数,假设为500~1000
- taskcost:每个任务花费时间,假设为0.1s
- responsetime:系统允许容忍的最大响应时间,假设为1s
做几个计算
corePoolSize = 每秒需要多少个线程处理? threadcount = tasks/(1/taskcost) =tasks*taskcout = (500~1000)*0.1 = 50~100 个线程。corePoolSize设置应该大于50 根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可 queueCapacity =
(coreSizePool/taskcost)responsetime 计算可得 queueCapacity = 80/0.11 = 80。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost) 计算可得
maxPoolSize = (1000-80)/10 = 92 最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数
rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
keepAliveTime和allowCoreThreadTimeout采用默认通常能满足
以上都是理想值,实际情况下要根据机器性能来决定。如果在未达到最大线程数的情况机器cpu
load已经满了,则需要通过升级硬件(呵呵)和优化代码,降低taskcost来处理。