一.多线程的四种实现方式
1.通过继承Threa类
-
实现步骤
-
自定义一个类MyThread类,用来继承与Thread类
-
在MyThread类中重写run()方法
-
在测试类中创建MyThread类的对象
-
使用对象调用start方法,启动线程。
-
-
缺点
-
这种方法是显式创建线程,有可能导致创建线程和销毁线程所花的时间以及系统资源开销过大,从而导致内存消耗完或者"过度切换"的问题
-
-
MyThread类
package com.test.multithreading; /** * @author welcome */ public class MyThread extends Thread{ public MyThread() { } public MyThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(this.getName()+" : "+i); } } }
-
测试类
package com.test.dy; import com.test.multithreading.MyThread; /** * @author welcome */ public class Demo01 { public static void main(String[] args) { MyThread t01=new MyThread(); MyThread t02=new MyThread(); MyThread t03=new MyThread(); t01.start(); t02.start(); t03.start(); t01.setName("线程1"); t02.setName("线程2"); Thread.currentThread().setName("主线程"); for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName()+" : "+i); } } }
2.实现Runnable接口
-
实现步骤
-
自定义一个MyRunnable类来实现Runnable接口
-
在MyRunnable类中重写run()方法
-
创建Thread对象,并把MyRunnable对象作为Tread类构造方法的参数传递进去
-
使用对象调用start方法,启动线程。
-
-
缺点
-
这种方法是显式创建线程,有可能导致创建线程和销毁线程所花的时间以及系统资源开销过大,从而导致内存消耗完或者"过度切换"的问题
-
-
MyRunnable类
package com.test.multithreading; /** * @author welcome */ public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName()+" : "+i); } } }
-
测试类
package com.test.dy; import com.test.multithreading.MyRunnable; /** * @author welcome */ public class Demo02 { public static void main(String[] args) { MyRunnable myRun=new MyRunnable(); Thread t01=new Thread(myRun,"线程1"); Thread t02=new Thread(myRun,"线程2"); Thread t03=new Thread(myRun,"线程3"); t01.start(); t02.start(); t03.start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+" : "+i); } } }); } }
3.通过Callable和Future接口创建线程
-
实现步骤
-
自定义一个MyCallable类来实现Callable接口
-
在MyCallable类中重写call()方法
-
创建FutureTask,Thread对象,并把MyCallable对象作为FutureTask类构造方法的参数传递进去,把FutureTask对象传递给Thread对象。
-
使用对象调用start方法,启动线程。
-
-
优点
-
可以借助FutureTask类,获取返回结果
-
相比run()方法,可以有返回值
-
方法可以抛出异常
-
支持泛型的返回值
-
-
缺点
-
这种方法是显式创建线程,有可能导致创建线程和销毁线程所花的时间以及系统资源开销过大,从而导致内存消耗完或者"过度切换"的问题
-
在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
-
-
MyCallable类
package com.test.multithreading; import java.util.concurrent.Callable; /** * @author welcome */ public class MyCallable implements Callable { @Override public Object call() throws Exception { int sum=0; for (int i = 1; i <=100; i++) { if (i%2==0){ System.out.println(i); sum+=i; } } return sum; } }
-
测试类
package com.test.dy; import com.test.multithreading.MyCallable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author welcome */ public class Demo03 { public static void main(String[] args) { MyCallable myCallable=new MyCallable(); FutureTask futureTask=new FutureTask<>(myCallable); new Thread(futureTask).start(); try { Object sum = futureTask.get(); System.out.println("总合:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
-
Future接口
-
可以对具体Runnable、Callable任务的执行结果进行取消、查询是 否完成、获取结果等。
-
FutrueTask是Futrue接口的唯一的实现类。
-
FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
-
4.通过线程池进行实现
-
Executors创建线程常用的4种方法
-
公共类
package com.test.multithreading.executorsandpool; import java.util.concurrent.atomic.AtomicInteger; /** * @author welcome */ public class CreateThreadPollDemo { public static final int SLEEP_GAP=1000; public static class TargetTask implements Runnable{ static AtomicInteger taskNo=new AtomicInteger(1); private String taskName; public TargetTask() { taskName="task-"+taskNo; taskNo.incrementAndGet(); } @Override public void run() { System.out.println(taskName+" is doing..."); try { Thread.sleep(SLEEP_GAP); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(taskName+" end..."); } } }
-
newSingleThreadExecutor创建“单线程化线程池”
-
代码
package com.test.dy.executorsandpooltest; import com.test.multithreading.executorsandpool.CreateThreadPollDemo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author welcome */ public class PoolTest1 { public static void main(String[] args) { ExecutorService pool=Executors.newSingleThreadExecutor(); for(int i=0;i<3;i++) { pool.execute(new CreateThreadPollDemo.TargetTask()); pool.submit(new CreateThreadPollDemo.TargetTask()); } pool.shutdown(); } }
-
特点
-
单线程化的线程池中的任务是按照提交的次序顺序执行的
-
只有一个线程的线程池
-
池中的唯一线程的存活时间是无限的
-
当池中的唯一线程正繁忙时,新提交的任务实例会进入内部的阻塞队列中,并且其阻塞队列是无界的
-
适用场景:任务按照提交次序,一个任务一个任务地逐个执行的场景
-
-
-
newFixedThreadPool创建“固定数量的线程池
-
代码
package com.test.dy.executorsandpooltest; import com.test.multithreading.executorsandpool.CreateThreadPollDemo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author welcome */ public class PoolTest2 { public static void main(String[] args) { ExecutorService pool= Executors.newFixedThreadPool(3);//最多有三个线程同时执行 for (int i = 0; i < 5; i++) { pool.execute(new CreateThreadPollDemo.TargetTask()); pool.submit(new CreateThreadPollDemo.TargetTask()); } pool.shutdown(); } }
-
特点
-
如果线程数没有达到“固定数量”,每次提交一个任务线程池内就创建一个新线程,直到线程达到线程池固定的数量
-
线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
-
在接收异步任务的执行目标实例时,如果池中的所有线程均在繁忙状态,新任务会进入阻塞队列中(无界的阻塞队列)
-
-
适用场景:
-
需要任务长期执行的场景
-
CPU密集型任务
-
-
缺点:
-
内部使用无界队列来存放排队任务,当大量任务超过线程池最大容量需要处理时,队列无限增大,使服务器资源迅速耗尽
-
-
-
newCachedThreadPool创建“可缓存线程池”
-
代码
package com.test.dy.executorsandpooltest; import com.test.multithreading.executorsandpool.CreateThreadPollDemo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author welcome */ public class PoolTest3 { public static void main(String[] args) { ExecutorService pool= Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) { pool.execute(new CreateThreadPollDemo.TargetTask()); pool.submit(new CreateThreadPollDemo.TargetTask()); } pool.shutdown(); } }
-
特点
-
在接收新的异步任务target执行目标实例时,如果池内所有线程繁忙,此线程池就会添加新线程来处理任务
-
线程池不会对线程池大小进行限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小
-
如果部分线程空闲,也就是存量线程的数量超过了处理任务数量,就会回收空闲(60秒不执行任务)线程
-
-
适用场景:
-
需要快速处理突发性强、耗时较短的任务场景,如Netty的NIO处理场景、REST API接口的瞬时削峰场景
-
-
缺点
-
线程池没有最大线程数量限制,如果大量的异步任务执行目标实例同时提交,可能会因创建线程过多而导致资源耗尽
-
-
-
newScheduledThreadPool创建“可调度线程池”
-
代码
package com.test.dy.executorsandpooltest; import com.test.multithreading.executorsandpool.CreateThreadPollDemo; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * @author welcome */ public class PoolTest4 { public static void main(String[] args) throws InterruptedException { ScheduledExecutorService pool= Executors.newScheduledThreadPool(2); for(int i=0;i<2;i++) { pool.scheduleAtFixedRate(new CreateThreadPollDemo.TargetTask(), 0, 500, TimeUnit.MILLISECONDS); //参数1: task任务 //参数2: 首次执行任务的延迟时间 //参数3: 周期性执行的时间 //参数4: 时间单位 } Thread.sleep(3000);//主线程睡眠时间越长 周期次数越多 pool.shutdown(); } }
-
特性
-
延时性
-
周期性
-
-
缺点
-
线程数量无上界,会导致创建大量的线程,从而导致OOM(内存耗尽)
-
-
-
newFixedThreadPool newSingleThreadExecutor 阻塞队列无界,会堆积大量任务导致OOM(内存耗尽) newCachedThreadPool newScheduledThreadPool 线程数量无上界,会导致创建大量的线程,从而导致OOM
-
-
ThreadPoolExecutor创建线程
-
参数介绍
corePoolSize 核心线程池的大小。 maximumPoolSize 最大线程池的大小。 keepAliveTime 线程池维护线程所允许的空闲时间 unit 线程池维护线程所允许的空闲时间的单位 workQueue 用来暂时保存任务的工作队列。 handler 线程池对拒绝任务的处理策略 threadFactory 线程工厂 -
代码
import java.io.IOException; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class ThreadPoolExecutorTest { public static void main(String[] args) throws IOException { ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), new NameTreadFactory(), new MyIgnorePolicy()); // 预启动所有核心线程 executor.prestartAllCoreThreads(); for (int i = 1; i <= 10; i++) { MyTask task = new MyTask(String.valueOf(i)); executor.execute(task); } //阻塞主线程 System.in.read(); } static class NameTreadFactory implements ThreadFactory { private final AtomicInteger mThreadNum = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement()); System.out.println(t.getName() + " has been created"); return t; } } public static class MyIgnorePolicy implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { doLog(r, e); } private void doLog(Runnable r, ThreadPoolExecutor e) { System.err.println(r.toString() + " rejected"); System.out.println("completedTaskCount: " + e.getCompletedTaskCount()); } } static class MyTask implements Runnable { private String name; public MyTask(String name) { this.name = name; } @Override public void run() { try { System.out.println(this.toString() + " is running!"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } public String getName() { return name; } @Override public String toString() { return "MyTask [name=" + name + "]"; } } }
-
优点
-
降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗
-
提高响应速度,当任务到达时任务不需要等到线程创建就能立即执行
-
提高线程的可管理性,使用线程池进行统一的分配,调优和监控
-
-