java创建多线程有如下四种方式:
- 继承Thread类创建线程
- 实现Runnable接口创建线程
- 使用Callable和Future创建线程
- 使用线程池创建(使用java.util.concurrent.Executor接口)
1.继承Thread类创建线程
public class test1 {
//继承Thread类,重写run方法
public static class thread extends Thread{
public Lock lock;
public void run(){
for (int i=0;i<500;i++)
System.out.println(Thread.currentThread().getName()+i);
}
}
public static void main(String[] args) {
thread t1 =new thread();
thread t2 =new thread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
2.实现Runnable接口创建线程
public class test1 {
//实现Runnable接口创建线程
public static class thread implements Runnable{
public Lock lock;
@Override
public void run(){
for (int i=0;i<500;i++)
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
public static void main(String[] args) {
thread mythread = new thread();
Thread thread1=new Thread(mythread,"线程1");
Thread thread2=new Thread(mythread,"线程2");
Thread thread3=new Thread(mythread,"线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
3.使用Callable接口和FutureTask类来创建线程
public class test1 {
//使用Callable接口和FutureTask类来创建线程
public static class thread implements Callable<String> {
@Override
public String call(){
for (int i=0;i<500;i++)
System.out.println(Thread.currentThread().getName()+" : "+i);
return "zhang";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
thread mythread = new thread();
FutureTask<String> futureTask = new FutureTask<>(mythread);
Thread thread1=new Thread(futureTask,"线程1");
Thread thread2=new Thread(futureTask,"线程2");
Thread thread3=new Thread(futureTask,"线程3");
thread1.start();
thread2.start();
thread3.start();
System.out.println(futureTask.get());
}
}
FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。 FutureTask可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。
思考?
同时实现Runnable和Callable接口会怎么样?
public class MyThread implements Callable<String>,Runnable{//Callable是一个泛型接口
@Override
public String call() throws Exception {//返回的类型就是传递过来的V类型
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
}
return "Hello Tom";
}
public static void main(String[] args) throws Exception {
MyThread myThread=new MyThread();
FutureTask<String> futureTask=new FutureTask<>(myThread);
Thread t0=new Thread(myThread,"线程0");
Thread t1=new Thread(futureTask,"线程1");
Thread t2=new Thread(futureTask,"线程2");
Thread t3=new Thread(futureTask,"线程3");
t0.start();
t1.start();
t2.start();
t3.start();
System.out.println(futureTask.get());
}
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
4. 使用线程池创建(使用java.util.concurrent.Executor接口)
线程池Executor底层实现类的ThredPoolExecutor的构造函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}
各个参数的意义:
- corePoolSize- 池中所保存的线程数,包括空闲线程。需要注意的是在初创建线程池时线程不会立即启动,直到有任务提交才开始启动线程并逐渐时线程数目达到corePoolSize。若想一开始就创建所有核心线程需调用prestartAllCoreThreads方法。
- maximumPoolSize-池中允许的最大线程数。需要注意的是当核心线程满且阻塞队列也满时才会判断当前线程数是否小于最大线程数,并决定是否创建新线程。
- keepAliveTime - 当线程数大于核心时,多于的空闲线程最多存活时间
- unit - keepAliveTime 参数的时间单位。
- workQueue - 当线程数目超过核心线程数时用于保存任务的队列。主要有3种类型的BlockingQueue可供选择:无界队列,有界队列和同步移交。将在下文中详细阐述。从参数中可以看到,此队列仅保存实现Runnable接口的任务。
- threadFactory - 执行程序创建新线程时使用的工厂。
- handler - 阻塞队列已满且线程数达到最大值时所采取的饱和策略。java默认提供了4种饱和策略的实现方式:中止、抛弃、抛弃最旧的、调用者运行。
Executor执行Runnable任务
通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCachedThreadPool{
public static void main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++){ //执行三个任务,那么线程池中最多创建三个线程
executorService.execute(new TestRunnable());
System.out.println("************* a" + i + " *************");
}
executorService.shutdown();
}
}
class TestRunnable implements Runnable{
public void run(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName() + "线程被调用了。");
}
}
}
上面是某次执行后的结果,可以看出,当有任务需要执行时,首先选择线程池中一个执行完任务的线程复用执行,如果线程池中没有可利用的线程,就创建一个新的线程。
线程池中submit()和execute()方法有什么区别?
execute():只能执行Runnable类型的任务。
submit():可以执行Runnable和Callable类型的任务。
executorService.execute(new TestRunnable());
executorService.submit(new TestRunnable());
线程池
为啥使用线程池:
系统在运行的时候,会处理很多运行时间比较短的线程,有时候花在创建和销毁的时间比线程运行的时间还要长,频繁的创建线程不仅会消耗系统资源,还会降低系统的稳定性。
线程池带来的好处:
- 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 增加线程的可管理型。线程是稀缺资源,使用线程池可以进行统一分配,调优和监控。
- 可以控制最大并发数。
线程池的核心参数:
- threadFactory(线程工厂):用于创建工作线程的工厂。
- corePoolSize(核心线程数):当线程池运行的线程少于 corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。
- workQueue(队列):用于保留任务并移交给工作线程的阻塞队列。
- maximumPoolSize(最大线程数):线程池允许开启的最大线程数。
- handler(拒绝策略):往线程池添加任务时,将在下面两种情况触发拒绝策略:1)线程池运行状态不是 RUNNING;2)线程池已经达到最大线程数,并且阻塞队列已满时。
- keepAliveTime(保持存活时间):如果线程池当前线程数超过 corePoolSize,则多余的线程空闲时间超过 keepAliveTime 时会被终止。
线程池的工作流程:
线程池目前的5个状态:
- RUNNING:接受新任务并处理排队的任务。
- SHUTDOWN:不接受新任务,但处理排队的任务。
- STOP:不接受新任务,不处理排队的任务,并中断正在进行的任务。
- TIDYING:所有任务都已终止,workerCount 为零,线程转换到 TIDYING 状态将运行 terminated() 钩子方法。
- TERMINATED:terminated() 已完成。
状态时间转换
Executors 提供了哪些创建线程池的方法?(很多,上面用到的)
newCachedThreadPool: 按需要创建新线程的线程池。核心线程数为0,最大线程数为 Integer.MAX_VALUE,keepAliveTime为60秒,工作队列使用同步移交 SynchronousQueue。该线程池可以无限扩展,当需求增加时,可以添加新的线程,而当需求降低时会自动回收空闲线程。适用于执行很多的短期异步任务,或者是负载较轻的服务器。
在我们实际使用中,线程池的大小配置多少合适?
计算密集型:设置 线程数 = CPU数 + 1,通常能实现最优的利用率。
I/O密集型:,网上常见的说法是设置 线程数 = CPU数 * 2 ,这个做法是可以的,但个人觉得不是最优的。
在我们日常的开发中,我们的任务几乎是离不开I/O的,常见的网络I/O(RPC调用)、磁盘I/O(数据库操作),并且I/O的等待时间通常会占整个任务处理时间的很大一部分,在这种情况下,开启更多的线程可以让 CPU 得到更充分的使用,一个较合理的计算公式如下:
线程数 = CPU数 * CPU利用率 * (任务等待时间 / 任务计算时间 + 1)
例如我们有个定时任务,部署在4核的服务器上,该任务有100ms在计算,900ms在I/O等待,则线程数约为:4 * 1 * (1 + 900 / 100) = 40个。