JAVA创建线程的三种方式、创建线程池的四种方式
概要:
java创建线程的三种方式:
继承Thread类创建线程类
实现Runnable接口
通过Callable和Future创建线程
java创建线程池的四种方式:
newCachedThreadPool 创建一个可缓存的线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行
newSingleThreadExecutor 创建一个单线程化的线程池,它只会唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行
线程池的优点:
重用存在的线程,减少对象创建、消亡的开销,性能佳
可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
提供定时执行、定期执行、单线程、并发数控制等功能
创建三种线程方式代码
package org.jeemp.thread;
/**
* @author JackRen
* @date 2021-03-04 9:47
* @description:
*/
public class myThread extends Thread {
int i = 0;
//重写run方法,run方法的方法体就是现场执行体
public void run(){
for(;i<100;i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args){
for(int i = 0;i< 100;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
if(i==20){
new myThread().start();
new myThread().start();
}
}
}
}
package org.jeemp.thread;
/**
* @author JackRen
* @date 2021-03-04 9:56
* @description:
*/
public class RunnableThread implements Runnable {
private int i;
@Override
public void run() {
for(i = 0;i <100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args){
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20){
RunnableThread runner= new RunnableThread();
new Thread(runner,"新线程1").start();
new Thread(runner,"新线程2").start();
}
}
}
}
package org.jeemp.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author JackRen
* @date 2021-03-04 10:00
* @description:
*/
public class CallableThread implements Callable<Integer> {
public static void main(String[] args) {
CallableThread ctt = new CallableThread();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20){
new Thread(ft,"有返回值的线程").start();
}
}
try{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e){
e.printStackTrace();
} catch (ExecutionException e){
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception {
int i = 0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
三种方式对比:
1、采用实现Runnable、Callable接口的方式创建多线程
优势:线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2、使用继承Thread类的方式创建多线程
优势:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势:线程类已经继承了Thread类,所以不能再继承其他父类。
3、Runnable和Callable的区别
(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3) call方法可以抛出异常,run方法不可以。
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果future.get()。
创建四种线程池的方式
package org.jeemp.thread.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author JackRen
* @date 2021-03-04 13:40
* @description:
*/
public class ExecutorsPools {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
/*
* 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
* 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
* @author JackRen
* @date 2021/3/4
* @return
**/
public void cachedThreadPool() {
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
}
/*
* 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
* 因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
* 定长线程池的大小最好根据系统资源进行设置
* @author JackRen
* @date 2021/3/4
* @return
**/
public void fixedThreadPool () {
for (int i=0;i<10;i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try{
System.out.println(index);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
/*
* 创建一个定长线程池,支持定时及周期性任务执行
* 表示延迟3秒执行
* @author JackRen
* @date 2021/3/4
* @return
**/
public void scheduledThreadPool () {
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
},3, TimeUnit.SECONDS);
}
/*
* 创建一个定长线程池,支持定时及周期性任务执行
* 定期执行
* 表示延迟1秒后每3秒执行一次
* ScheduledExecutorService比Timer更安全,功能更强大
* @author JackRen
* @date 2021/3/4
* @return
**/
public void scheduledAtFixedRate () {
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
},1,3, TimeUnit.SECONDS);
}
/*
* 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
* @author JackRen
* @date 2021/3/4
* @return
**/
public void singleThreadExecutor() {
for (int i=0;i<10;i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try{
System.out.println(index);
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}
总结:
线程池的作用:
线程池作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。