线程的六种状态
1.新建(New)
线程创建后尚未启动。
2.可运行(Runnable)
一旦调用了start方法,线程就处于可运行状态。可运行状态的线程可能正在运行,也可能还没有运行而正在等待 CPU 时间片。
3.阻塞(Blocked)
处于阻塞状态的线程并不会占用CPU资源。
4.无限期等待(Waiting)
处于这种状态的线程不会被分配 CPU 时间片,需要等待其它线程显式地唤醒。
以下方法会让线程进入无限期的等待状态
Object.wait() 方法 结束:Object.notify() / Object.notifyAll()
Thread.join() 方法 结束:被调用的线程执行完毕
LockSupport.park() 方法 结束:LockSupport.unpark(currentThread)
5.限时等待(Timed Waiting)
处于这种状态的线程也不会被分配CPU 时间片,在一定时间之后会被系统自动唤醒。
以下方法会让线程进入限期等待状态:
Thread.sleep(time) 方法 结束:sleep时间结束
Object.wait(time) 方法 结束:wait时间结束,或者Object.notify() / notifyAll()
LockSupport.parkNanos(time)/parkUntil(time) 方法 结束:park时间结束,或者LockSupport.unpark(当前线程)
6.死亡(Terminated)
可以是线程结束任务之后自己结束,或者产生了异常而结束。
线程的状态切换
线程创建的三种方式
1.继承Thread类创建线程类
(1)创建Thread类的子类,并重写run方法,run方法的方法体代表该线程需要完成的任务;
(2)创建Thread类的实例,即创建线程对象;
(3)调用线程对象的start()方法启动线程;
2.实现Runnable接口,创建线程
(1)定义Runnable接口的实现类,并重写run ()方法;
(2)创建实现类的实例对象,将此实例作为Thread类的target创建Thread对象,该Thread对象才是真正的线程对象;
(3)调用start()方法启动线程。
3.实现Callable接口,通过FutureTask包装器创建线程
(1)创建Callable接口的实现类,重写call()方法,call()方法的执行体也是执行该线程任务,且call()方法有返回值;
(2)使用FutureTask类包装Callable对象,该FutureTask封装类Callable的call()方法的返回值;
(3)使用FutureTask对象作为Thread类的target创建线程;
(4)调用线程的start()方法启动线程。
示例:
public class Callable1208 implements Callable <Integer>{
int i = 0;
@Override
public Integer call() throws Exception {
for(;i < 20;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
public class Test{
public static void main(String[] args){
/**
* 通过实现Callable接口创建线程(将Callable实现类作为Thread的target,并接受线程体的返回值)
*/
Callable1208 target = new Callable1208();
FutureTask<Integer> f = new FutureTask<Integer>(target);
Thread thread = new Thread(f,"新线程");
thread.start();
try {
System.out.println(f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
注:
继承Thread类与实现Runnable接口创建线程的区别:
1.继承Thread类为单继承,实现Runable接口为多实现,灵活性上,实现Runnable接口创建线程更灵活;
2.线程类继承自Thread相对于Runable来说,使用线程的方法更方便一些;
3.实现Runnable接口的线程类的多个线程、可以更方便的访问同一个变量,而Thread类需要内部类来进行替换.
实现Runnable接口与实现Callable接口创建线程的区别:
1.Callable接口的实现类重写call()方法,有返回值,而Runnable接口实现类重写run()方法没有返回值;
2.call()方法可抛出异常,run()方法是不能抛出异常的;
3.运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
线程模型
内核线程模型
定义:
内核线程模型即完全依赖操作系统内核提供的内核线程(Kernel-Level Thread ,KLT)来实现多线程。在此模型下,线程的切换调度由系统内核完成,系统内核负责将多个线程执行的任务映射到各个CPU中去执行。
程序使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),每个轻量级进程都由一个内核线程支持。
优点:
一个用户线程对应一个LWP,因此某个LWP在调用过程中阻塞了不会影响整个进程的执行。
缺点:
1.由于是基于内核线程实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调用的代价相对较高,需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换。
2.每一个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持轻量级进程的数量是有限的。
用户线程模型
定义:
狭义上的用户线程(User Thread,UT)指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。
优点:
速度快且消耗低,可以支持规模更大的线程数量。
缺点:
没有系统内核的支援,所有的线程操作都需要用户程序自己处理。线程的创建、切换和调度都是需要考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难,甚至不可能完成。因而使用用户线程实现的程序一般都比较复杂。
混合线程模型
定义:
将内核线程与用户线程一起使用的实现方式。在这种混合实现下,既存在用户线程,也存在轻量级进程。集成了以上两种优点
线程调度
协同式调度
定义:
协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另外一个线程上。
优点:
实现简单,没有线程同步的问题
缺点:
线程执行时间不可控制,如果一个线程编写有问题,一直不告知系统进行线程切换,导致阻塞。
抢占式调度
定义:
抢占式调度的多线程系统,每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。
优点:
线程的执行时间是系统可控的,不会有一个线程导致整个进程阻塞的问题
缺点:
相对更复杂,需要考虑线程同步问题
线程调度
协同式调度
定义:
协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另外一个线程上。
优点:
实现简单,没有线程同步的问题
缺点:
线程执行时间不可控制,如果一个线程编写有问题,一直不告知系统进行线程切换,导致阻塞。
实现线程安全的几种方法
1.同步代码块
使用了一个锁对象–同步锁,当开启多个线程的时候,多个线程就开始抢夺CPU的执行权,当线程遇到同步代码块,先检查是否有锁对象,若有则获取该锁对象,执行同步代码块中的代码。若没有则等待其余线程执行完毕释放锁对象。
使用方法:
synchronized(锁对象) {
可能会出现线程安全问题的代码(访问共享数据的代码)
}
2.同步方法
把访问了共享数据的代码抽取出来,放到一个synchronized 同步方法中。
使用方法:
修饰符 synchronized 返回值类型 方法名称(参数列表) {
method body
}
3.Lock锁机制
在可能产生线程安全问题的代码前该对象调用lock方法获取锁,使用完后调用unlock释放锁,来保证线程安全
常用的辅助类
CountDownLatch:递减计数器
countDownLatch.countDown():数量 - 1
countDownLatch.await():等待计数器归零,然后再向下执行
CyclicBarrier:递加计数器
cyclicBarrier.await():当前计数器数量+1,达到自定义值即结束
Semaphore:信号量,就是一组通行证
semaphore.acquire():获得,假设如果已经满了,等待,等待被释放为止
semaphore.release():释放,会将当前的信号量 - 1,然后唤醒等待的线程
作用:多个共享资源互斥的使用,并发限流,控制最大的线程数
线程池参数
public ThreadPoolExecutor(
//线程池中的常驻核心线程数
int corePoolSize,
//线程池能够容纳同时执行的最大线程数,此值大于等于1
int maximumPoolSize,
//多余的空闲线程存活时间,当空间时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止
long keepAliveTime,
//keepAliveTime的单位
TimeUnit unit,
// 任务队列,被提交但尚未被执行的任务.
BlockingQueue<Runnable> workQueue,
// 表示生成线程池中工作线程的线程工厂,用户创建新线程,一般用默认即可
ThreadFactory threadFactory,
// 拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示 数(maxnumPoolSize)时如何来拒绝.
RejectedExecutionHandler handler) {...
线程池工作原理
1.通过execute方法提交任务时,当线程池中的线程数小于corePoolSize时,新提交的任务将通过创建一个新线程来执行,即使此时线程池中存在空闲线程。
2.若线程池中线程数量达到corePoolSize时,新提交的任务将被放入workQueue中,等待线程池中线程调度执行。
3.若workQueue也已满,且maximumPoolSize大于corePoolSize时,新提交的任务将通过创建新线程执行。
4.当线程池中的线程执行完任务空闲时,会从workQueue中取头结点任务执行。
5.当线程池中线程数达到maxmumPoolSize,且workQueue也存满时,新提交的任务由RejectedExecutionHandler执行拒绝操作。
6.当线程池中线程数超过corePoolSize,并且未配置allowCoreThreadTimeOut=true,空闲时间超过keepAliveTime的线程会被销毁,保持线程池中线程数为corePoolSize。
7.当设置allowCoreThreadTimeOut=true时,任何空闲时间超过keepAliveTime的线程都会被销毁。
线程池使用实例
1、新建线程池管理类ThreadPoolManager
public class ThreadPoolManager {
public static final int CORE_POOL_SIZE = 5;
public static final int MAXIMUM_POOL_SIZE = 10;
public static final int KEEP_ALIVE_TIME = 60;
private static ThreadPoolExecutor executor;
public static ThreadPoolExecutor getInstance() {
if (executor == null) {
synchronized (ThreadPoolManager.class) {
if (executor == null) {
executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10));
}
}
}
return executor;
}
}
2、新建类implements Runnable
public class PayTask implements Runnable {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(PayTask.class);
private PayService payService;
private GateWayFacade gateWay;
private PayDao payDao;
private Map<String, Object> params = new HashMap();
public PayTask(PayService payService,GateWayFacade gateWay,PayDao payDao,Map<String, Object> param){
this.params = param;
this.payService = payService;
this.gateWay = gateWay;
this.payDao = payDao;
}
@Override
public void run() {
// 业务代码
logger.info("PayTask结束");
}
}
3、运行
ThreadPoolExecutor executor = ThreadPoolManager.getInstance();
executor.execute(new PayTask(payService,gateWay,payDao,paramMap));