多线程学习总结
一、线程的状态和新建
1、线程的状态的5个阶段:
新生:这种情况指的是,通过New关键字创建了Thread类(或其子类)的对象
就绪:这种情况指的是Thread类的对象调用了start()方法
运行:这时的线程指的是获得CPU的RUNNABLE线程,RUNNING状态是所有线程都希望获得的状态
死亡:处于RUNNING状态的线程,在执行完run方法之后,就变成了DEAD状态了
阻塞:这种状态指的是处于RUNNING状态的线程,出于某种原因,比如调用了sleep方法、等待用户输入等而让出当前的CPU给其他的线程。当sleep的线程醒来(sleep的时间到了)、获得了用户的输入、调用了join的其他线程结束、获得了对象锁时,线程重新变成就绪状态
2、线程的方法调用
Thread.sleep(int milliseconds);
t.start();#启动线程
t.join();#强制执行t这个线程
t.wait();#一直等,直到有人通知
t.wait(long milliseconds);#到时间自动恢复执行,或有人通知立即执行
t.notify();#通知另一个人
t.notifyAll();#通知其他所有人
t.interrupt(); #在其他线程中使用,中断t线程
t.setDaemon(true);#在start方法前调用setDaemon,用来结束无限循环的线程
3、创建线程的方式
创建java.lang.Thread子类,重写Thread的run方法
public class Thread1 extends Thread{
//创建线程子类
@Override
public void run() {
setName("sub1");
for (int i = 0; i <10 ; i++) {
System.out.println(this.getName()+"\t"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread1().start();
}
}
匿名内部类创建Thread对象,重写run方法
public class Thread2 {
public static void main(String[] args) {
//创建匿名内部类
Thread t = new Thread(){
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println("sub2"+"\t"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
}
}
创建Runnable接口对象,作为Thread类的构造参数传入
public class Thread2 {
public static void main(String[] args) {
//创建Runnable接口对象,作为java.lang.Thread的构造参数传入
Runnable run1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println("sub:"+"\t"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t = new Thread(run1);
t.start();
}
}
开发中:优先选择:实现Runnable接口的方式
原因:1. 实现Runnable接口的方式没类的单继承性的局限性
2. 实现Runnable接口的方式更适合来处理多个线程共享数据的情况。
联系:public class Thread implements Runnable
三种方式都需要重写run()方法,将线程要执行的逻辑声明在run()中。启动线程,都是调用的Thread类中的start()。
通过创建一个实现Callable的实现类创建线程
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@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;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
说明:
- 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
-
- call()可以返回值的。
-
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
-
- Callable是支持泛型的
使用线程池
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
好处:
- 1.提高响应速度(减少了创建新线程的时间)
- 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 3.便于线程管理
-
corePoolSize:核心池的大小
-
maximumPoolSize:最大线程数
-
keepAliveTime:线程没任务时最多保持多长时间后会终止
二、线程池
程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待状态。如果有新任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理。
Java标准库提供了ExecutorService接口表示线程池
1、固定线程池
固定线程数量,并发需求超出最大线程数,则需要等其他线程释放了才能拿到,相对安全
ExecutorService fixedPool = Executors.newFixedThreadPool(int size);
2、缓存线程池
不固定线程数,小项目用,保持最小容量,根据闲置时间关掉线程
ExecutorService cachedPool = Executors.newCachedThreadPool();
3、单线程池
只有一条线程,一般用来维护系统的核心业务
ExecutorService singlePool = Executors.newSingleThreadExecutor();
4、submit提交线程池任务
submit是ExecutorService接口中定义的一个方法,以Future的形式返回线程的执行结果
xxxPool.submit(Runnable task);
Future<T> future = xxxPool.submit(Callable<T> call);
String result = future.get();
5、手动创建线程池
- 固定:nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue(),缺点为容易任务堆积造成OOM溢出
- 单:1, 1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue(),缺点为容易任务堆积造成OOM溢出
- 缓存:0,Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue(),缺点为创建大量线程造成OOM溢出
ThreadPoolExecutor()方法
参数如下:
ThreadPoolExecutor参数如下:
corePoolSize
:核心线程数
maximumPoolSize
:最大线程数
keepAliveTime
:当线程池线程数量大于corePoolSize
时候,多出来的空闲线程的存活时间
unit
:参数keepAliveTime
的时间单位,TimeUnit
枚举类有天
,小时
,分
,秒
,毫秒
,微秒
,纳秒
7种可以选择。
workQueue
:线程池使用的缓冲队列
threadFactory
:线程工厂,主要用来创建线程
handler
:拒绝策略,拒绝处理任务时的策略
static class Task implements Runnable{
@Override
public void run() {
try {
System.out.println(100);
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5,
60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(5000));
while(true){
threadPool.execute(new Task());
}
}
三、计划线程池
ScheduledExecutorService是继承于ExecutorService的一个接口,ExecutorService是继承于Executor的一个接口,创建一个ScheduledThreadPool仍然是通过Executors类
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(int size);
1、延迟多久执行一次(一次性)
scheduledPool.schedule(Runnable run,int delay,TimeUnit unit);
ScheduledFuture<T> future = scheduledPool.scheduled(Callable<T> run
,int delay,TimeUnit unit);
2、每隔多久执行一次
runnable是任务,initialdelay多长时间后首次执行,每隔period执行一次(不管任务有没有结束)
scheduledPool.scheduleAtFixedRate(Runnable run,int initialDelay,int period,TimeUnit unit);
3、执行完延迟多久执行下一次
runnable是任务,initialdelay多长时间后首次执行,任务结束后隔多久执行下一次
scheduledPool.scheduleWithFixedDelay(Runnable run,int initialDely,int delay,TimeUnit unit);
4、释放线程池
xxxPool.shutdown();