多线程-----2.线程的创建(四种方式)
二、线程的创建
创建线程主要有四种方式:
- 通过继承Thread类来进行创建
- 通过实现Runnable接口进行创建
- 通过callable接口进行创建
- 通过线程池来进行创建
1.Thread创建线程
◆自定义线程类继承Thread类
◆重写run()方法,编写线程执行体
◆创建线程对象,调用start()方法启动线程
示例:
public class TestThread {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();}
}class MyThread extends Thread {@Overridepublic void run() {System.out.println("线程创建成功!");}
}
运行结果:
注意:一定要用start()方法来开启线程,而不是直接调用run()方法,直接调用run()方法是不能开启线程的。
代码示例:
当线程调用的是run()方法时,只会有一个线程执行:
public class TestRun extends Thread {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("我还能学~~" + i);}}public static void main(String[] args) {TestRun tr = new TestRun();tr.run();for (int i = 0; i < 5; i++) {System.out.println("学不动了~~" + i);}}
}
执行结果:
可以看出先把run()方法中的循环执行完,之后才执行main方法中的循环。
当代码变成start()方法调用时,结果就不一样了
public class TestRun extends Thread {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("我还能学~~" + i);}}public static void main(String[] args) {TestRun tr = new TestRun();tr.start();for (int i = 0; i < 5; i++) {System.out.println("学不动了~~" + i);}}
}
运行结果:
如果循环次数5不易看出,可以增加循环的次数。从结果可以看出,"学不动了~~"和"我还能学~~"的有交叉了,与方法调用的顺序没有关系了,其实是开启了新的线程。
2.实现Runnable接口进行创建
◆定义MyRunnable:类实现Runnable接口
◆实现run()方法,编写线程执行体
◆创建线程对象,调用start()方法启动线程,
这里需要用到Thread创建对象,之后将接口对象作为参数传到new Thread()中,例如new Thread(Runnable接口对象)
代码示例:
public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("我还能学~~" + i);}}public static void main(String[] args) {//创建线程对象,调用start()方法启动线程MyRunnable myRunnable = new MyRunnable();//通过new Thread()创建一个代理对象Thread thread = new Thread(myRunnable);thread.start();for (int i = 0; i < 10; i++) {System.out.println("学不动了~~" + i);}}
}
运行结果:
如果循环次数10不易看出,可以增加循环的次数。从结果可以看出,"学不动了~~"和"我还能学~~"的有交叉了,与方法调用的顺序没有关系了,其实是开启了新的线程。
3.实现Callable接口进行创建
◆定义MyCallable:类实现Callable接口
◆实现call()方法,编写线程执行体
◆创建线程对象,调用start()方法启动线程
注意:在调用callable接口时,调用方法与Runnable接口不同,需要引入FutureTask task= new FutureTask(myCallable);将接口进行封装之后再交给线程代理。
代码示例:
public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {System.out.println("call()");return "1024";}public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable myCallable = new MyCallable();FutureTask<String> task = new FutureTask<String>(myCallable);new Thread(task, "A").start();new Thread(task, "B").start();String string = task.get(); //获取返回值System.out.println(string);}
}
运行结果:
4.实现线程池进行创建
在阿里java开发手册中关于线程的创建,需要用线程池进行创建,并且是强制的。
(1)了解线程池
线程池:三大方法、7大参数、4种拒绝策略
线程池的好处:
1、降低资源的消耗
2、提高响应的速度
3、方便管理。
线程复用、可以控制最大并发数、管理线程。
(2)线程池:三大方法
三大方法:
ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程
ExecutorService threadPoo2 = Executors.newFixedThreadPool(5); // 创建一个固定的线程池的大小
ExecutorService threadPool3 = Executors.newCachedThreadPool(); // 可伸缩的,遇强则强,遇弱则弱
代码示例:
public class ThreadPool {public static void main(String[] args) {ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程ExecutorService threadPoo2 = Executors.newFixedThreadPool(5); // 创建一 个固定的线程池的大小ExecutorService threadPool3 = Executors.newCachedThreadPool(); // 可伸缩的,遇强则强,遇弱则弱try {for (int i = 0; i < 5; i++) {//使用线程池来创建线程threadPool.execute(() -> {System.out.println(Thread.currentThread().getName() + " ok");});}} catch (Exception e) {e.printStackTrace();} finally {//线程池用完,程序结束,关闭线程池threadPool.shutdown();}}
}
运行结果:
注意:线程池使用完毕必须在finally中进行关闭,否则程序不会停止。
(3)7大参数
源码分析:
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小 int maximumPoolSize, // 最大核心线程池大小 long keepAliveTime, // 超时了没有人调用就会释放 TimeUnit unit, // 超时单位 BlockingQueue<Runnable> workQueue, // 阻塞队列 ThreadFactory threadFactory, // 线程工厂:创建线程的,一般不用动 RejectedExecutionHandler handle // 拒绝策略) {if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException();this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
七大参数:
int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大核心线程池大小
long keepAliveTime, // 超时了没有人调用就会释放
TimeUnit unit, // 超时单位
BlockingQueue workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handle // 拒绝策略
(4)4种拒绝策略
jdk默认提供了四种拒绝策略:
- CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
- AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
- DiscardPolicy - 直接丢弃,其他啥都没有
- DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入
四种拒绝策略是相互独立无关的,选择何种策略去执行,还得结合具体的业务场景。实际工作中,一般直接使用 ExecutorService 的时候,都是使用的默认的 defaultHandler ,也即 AbortPolicy 策略。
(5)用线程池创建多线程
示例代码:
public class ThreadPool {public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!);try {// 最大承载:Deque + max // 超过 RejectedExecutionExceptionfor (int i = 0; i < 9; i++) {// 使用了线程池之后,使用线程池来创建线程threadPoolExecutor.execute(() -> {System.out.println(Thread.currentThread().getName() + " ok");});}} catch (Exception e) {e.printStackTrace();} finally {// 线程池用完,程序结束,关闭线程池 \threadPoolExecutor.shutdown();}}
}
运行结果:
可以看出最大线程数不超过5,并且本来循环应该有9次,但输出结果只有8次(每次执行的结果可能不一样,由cpu决定),因为拒绝策略是DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入,所以可能存在丢失,如果换成AbortPolicy就会报错。
换成AbortPolicy拒绝策略的运行结果:
谢谢阅读,无误点赞,有误还望评论区指正。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
