1,什么是多线程
一个进程中可以并发多个线程,每条线程并行执行不同的任务,多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
2,线程的生命周期
线程的五种基本状态
-
新建状态(New):线程对象创建后,就是这种状态。
-
就绪状态(Runnable):也称可运行状态,在线程对象调用start()方法后,就进入就绪状态,等待获取CPU资源。
-
运行状态(Running):运行状态,线程真正的执行状态,当获取CPU资源后,对象调用run()方法后进入,就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。
-
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,进入阻塞状态,直到其再次进入就绪状态,获取到CPU资源,才可被再次执行,根据产生阻塞的原因,阻塞状态可细划分为三种。
-
等待阻塞:运行状态中的线程执行wait()方法,进入等待状态。
-
同步阻塞:线程在获取synchronized同步锁失败(锁被其他线程占用),就会进入同步阻塞状态。
-
其他阻塞:通过调用线程的sleep()或者join()发出I/O请求时,当sleep()状态超时或join()等待线程终止或者超时时、或者I/O处理完毕时,线程就会重新进入就绪状态。
-
-
死亡状态(Dead):线程执行完了或者因为异常退出了run()方法,该线程生命周期结束。
3,线程的创建
1,继承Thread类
继承Thread类,重写该类的run()方法,不推荐,由于类的单继承性,不利于类的扩展。
package com.pactera.test;
public class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
package com.pactera.test;
public class ThreadTest1 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "+++" + i);
if (i == 3) {
//创建线程1
Thread thread1 = new MyThread1();
//创建线程2
Thread thread2 = new MyThread1();
//线程1进入就绪状态
thread1.start();
//线程2进入就绪状态
thread2.start();
}
}
}
}
2,实现Runnable接口
实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。
package com.pactera.test;
public class MyRunnable implements Runnable {
@Override
public void run() {
//线程的方法执行体
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
package com.pactera.test;
public class ThreadTest1 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "+++" + i);
if (i == 3) {
//创建实例
MyRunnable myRunnable = new MyRunnable();
//Thread对象才是真正的实例
//创建线程1
Thread thread1 = new Thread(myRunnable);
//创建线程2
Thread thread2 = new Thread(myRunnable);
//线程1进入就绪状态
thread1.start();
//线程2进入就绪状态
thread2.start();
}
}
}
}
本质上Thread也实现了Runnable接口,并在run()方法里进行了一个判断
@Override
public void run() {
if (target != null) {
target.run();
}
}
这里的target就是一开始传入的myRunnable,所以这里run()方法执行的依然是MyRunnable类里面的run()方法。
3,实现Callable接口
创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
package com.pactera.test;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int j = 0;
for (int i = 0; i < 10; i++) {
j++;
System.out.println(Thread.currentThread().getName() + "---" + i);
}
return j;
}
}
package com.pactera.test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest1 {
public static void main(String[] args) {
//创建实例
MyCallable myCallable = new MyCallable();
//使用FutureTask类包装
FutureTask<Integer> future1 = new FutureTask<Integer>(myCallable);
FutureTask<Integer> future2 = new FutureTask<Integer>(myCallable);
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "+++" + i);
if (i == 3) {
//创建线程1
Thread thread1 = new Thread(future1);
//创建线程2
Thread thread2 = new Thread(future2);
//线程1进入就绪状态
thread1.start();
//线程2进入就绪状态
thread2.start();
}
}
//获取新创建的线程中call()方法返回的结果
try {
//在所有的线程执行完成之后这里才会执行,否则就是阻塞状态,所以结果会一致
Integer i1 = future1.get();
Integer i2 = future2.get();
System.out.println("线程1返回的值为:" + i1);
System.out.println("线程2返回的值为:" + i2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
callable与runnable的区别
-
callable的执行方法体是call(),而runnable是run()
-
callable的任务执行后可返回值,runnable不能
-
call()方法可以抛出异常,run()方法不能
FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。
4,使用线程池
在实际工作中,一般都使用线程池来创建线程,这样的好处是:
-
线程的频繁创建和销毁对系统的资源是一种很大的占用,使用线程池可重复利用已创建的线程。
-
可以控制线程的并发数,规定线程池的大小,可以防止大量线程因争夺CPU资源而造成堵塞。
-
线程池提供更加灵活的线程管理,定时、定期、单线程等。
4,线程池的创建
线程池的结构:
-
Executor:负责线程的使用与调度的根接口
-
ExecutorService:Executor的子接口,线程池的主要接口
-
AbstractExecutorService:实现了ExecutorService接口,基本实现了ExecutorService其中声明的所有方法,另有添加其他方法
-
ThreadPoolExecutor:继承了AbstractExecutorService,主要的常用实现类
-
ScheduledExecutorService:继承了ExecutorService,负责线程调度的接口
-
ScheduledThreadPoolExecutor:继承了ThreadPoolExecutor同时实现了ScheduledExecutorService
Java为我们提供了一个类来创建线程池Executors,主要创建的四种类型线程池如下:
-
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
-
newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
-
newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
-
newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
但是不推荐使用这种方式来创建线程池,因为在创建时无法传入一些核心的配置参数,全都使用默认的,容易造成系统资源浪费,比较好的做法是使用ThreadPoolExecutor来创建,它提供的有参构造如下。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
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;
}
-
corePoolSize - 线程池核心池的大小,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存
keepAliveTime
限制。除非将allowCoreThreadTimeOut
设置为true
。 -
maximumPoolSize - 线程池的最大线程数。
-
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
-
unit - keepAliveTime 的时间单位。
-
workQueue - 用来储存等待执行任务的队列。
-
threadFactory - 线程工厂。
-
handler-拒绝策略。