创建线程的四种方式
1、继承Thread类创建线程类
2、实现Runnable接口
3、通过Callable和Future创建线程
4、创建线程池
创建线程的主要三种方式
继承Thread类创建线程类 (extends)
public class FirstThreadTest extends Thread{
int i = 0;
//重写run方法,run方法的方法体就是现场执行体
public void run()
{
for(;i<100;i++){
log.info(getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i< 100;i++)
{
log.info(Thread.currentThread().getName()+" : "+i);
if(i==20)
{
new FirstThreadTest().start();
new FirstThreadTest().start();
}
}
}
}
通过Runnable接口创建线程类
public class RunnableThreadTest implements Runnable
{
private int i;
public void run()
{
for(i = 0;i <100;i++)
{
log.info(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i < 100;i++)
{
log.info(Thread.currentThread().getName()+" "+i);
if(i==20)
{
RunnableThreadTest runner= new RunnableThreadTest();
new Thread(runner,"新线程1").start();
new Thread(runner,"新线程2").start();
}
}
}
}
实现callable接口
通过Callable和Future创建线程
public interface Callable
{
V call() throws Exception;
}
public class CallableThreadTest implements Callable<Integer>
{
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++)
{
log.info(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20)
{
new Thread(ft,"有返回值的线程").start();
}
}
try
{
log.info("子线程的返回值:"+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++)
{
log.info(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()。
创建四种线程池的方式
1、new Thread的弊端
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom(out of memory)。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
2、Java 线程池
线程池的七大参数
corePoolSize 核心线程大小
线程池中最小的线程数量,即使处理空闲状态,也不会被销毁,除非设置了allowCoreThreadTimeOut。
CPU密集型:核心线程数 = CPU核数 + 1
IO密集型:核心线程数 = CPU核数 * 2+1
核心线程数 = CPU核数 / (1-阻塞系数)
例如阻塞系数 0.8,CPU核数为4,则核心线程数为20
maximumPoolSize 线程池最大线程数量
一个任务被提交后,首先会被缓存到工作队列中,等工作队列满了,则会创建一个新线程,处理从工作队列中的取出一个任务。
keepAliveTime 空闲线程存活时间
当线程数量大于corePoolSize时,一个处于空闲状态的线程,在指定的时间后会被销毁。
unit 空间线程存活时间单位
keepAliveTime的计量单位
workQueue 工作队列,
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。
①ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。
②LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。
④PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
六、threadFactory 线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
七、handler 拒绝策略
当工作队列中的任务已满并且线程池中的线程数量也达到最大,这时如果有新任务提交进来,拒绝策略就是解决这个问题的,jdk中提供了4中拒绝策略:
①CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
②AbortPolicy
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
③DiscardPolicy
该策略下,直接丢弃任务,什么都不做。
④DiscardOldestPolicy
该策略下,抛弃最早进入队列的那个任务,然后尝试把这次拒绝的任务放入队列。
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
线程池的作用:
线程池作用就是限制系统中执行线程的数量。
用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了否则进入等待队列。
为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
比较重要的几个类:
ExecutorService: 真正的线程池接口。
ScheduledExecutorService: 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor: ExecutorService的默认实现。
ScheduledThreadPoolExecutor: 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
线程池七大参数参考这个文章
https://blog.csdn.net/HBliucheng/article/details/111414164