线程、线程池

线程的六种状态

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。
我画了25张图展示线程池工作原理和实现原理,建议先收藏再阅读
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));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值