线程相关知识点,行走江湖良药~

线程

状态

//新建:NEW
//就绪:READY
	线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
//运行:RUNNABLE
	可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
//阻塞:BLOCKED
	处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时
进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。
阻塞的情况分三种:
	(). 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waittingqueue)中,使本线程进入到等待阻塞状态;
	(). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),,则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;
	(). 其他阻塞: 通过调用线程的 sleep()join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入
就绪状态。
//等待:WAITING
//超时等待: TIMED_WAITING
//终结:TERMINATED

阻塞和等待状态有什么区别

1、阻塞: 当前线程试图获取锁,而锁被其他线程持有着,则当前线程进入阻塞状态。也就是线程和其他线程抢锁没抢到,就处于阻塞状态了;(此时线程还没进同步代码块)
2、等待: 线程抢到了锁进了同步代码块,(由于某种业务需求)某些条件下Object.wait()或join了,就处于了等待状态。(此时线程已经进入了同步代码块)。是一种主动行为,你不知道它什么时候被阻塞,也不清楚它什么时候会恢复阻塞。

volatile

当前Java内存模型下,线程可以把变量保存到本地内存(如寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还在继续使用它在寄存器中变量值的拷贝,造成数据的不一致。

volatile关键字就是解决这个问题,指示JVM这个变量不稳定,每次使用它都要到主存中进行读取。除此之外还有一个重要的作用是防重排。

并发执行的三个重要特性:

1、原子性
	要么所有的操作都得到执行并且不会收到任何因素干扰而中断,要么所有的操作都不执行。
	可使用synchronized来保证代码原子性。
2、可见性
	当对一个共享变量进行了修改后,那么另外的线程都是立即可以看到修改后的最新值。
	volatile可以保证可见性。
3、有序性
	代码在执行过程中的先后顺序,Java在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序,即指令重排。
	volatile可以禁止指令重排优化。

线程的上下文切换

	多线程编程中一般线程的个数都大于CPU核心的个数,而一个CPU黑犀牛在任意时刻之恶能被一个线程使用,为了让这些线程都能得到有效执行,CPU采用的策略是为每个线程分配时间片并轮转的形式。
	当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
	概括:当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,一边下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文的切换。

线程池

核心参数

1/* corePoolSize:核心线程数。*/
    当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的核心线程能够执行新任务也会创建线程,等到 需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads() 方法,则线程池会提前创建并启动所有基本线程。
2/*maximumPoolSize:最大线程数。*/
	线程池允许创建的最大线程数。
3/*keepAliveTime:线程活动保持时间 */
	线程池的工作线程空闲后,保持存活的时间。
4/*unit:keepAliveTime的时间单位*/ 
	Time.SECONDS()5/*workQueue:任务队列*/
	用于保存等待执行的任务的阻塞队列。有四种:ArrayBlockingQueueLinkedBlockingQueueSynchronousQueuePriorityBlockingQueue6/*threadFactory:线程工厂*/
	它是ThreadFactory类型的变量,用来创建新线程。默认使用 Executors.defaultThreadFactory() 来创建线程。
7/*handler:据决策略*/ 
	RejectedExecutionHandler 拒绝策略。当队列和线程满了后,采取一种策略处理提交的新任务。

实际应用

private static final int CORE_POOL_SIZE = 5;//核⼼线程数为 5
    private static final int MAX_POOL_SIZE = 10;//最⼤线程数 10
    private static final int QUEUE_CAPACITY = 100;//容量100
    private static final Long KEEP_ALIVE_TIME = 1L;//等待时间为 1L
    public static void main(String[] args) {
        //通过ThreadPoolExecutor构造函数⾃定义参数创建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy());//饱和策略
        for (int i = 0; i < 10; i++) {
            //创建WorkerThread对象(WorkerThread类实现了Runnable接⼝)
            Runnable worker = new MyRunnable("" + i);
            executor.execute(worker);//执⾏Runnable
        }
        executor.shutdown();//终⽌线程池
        while (!executor.isTerminated()) {
        }
        System.out.println("结束");
    }

阻塞队列

阻塞队列:通过加锁的方式让队列的生产者或者消费者处于等待状态,加锁只能单个线程执行
ArrayBlockingQueue(有界队列)通过lock实现线程安全,只有一把锁,所以产生者和消费者只能有一个处于工作状态
LinkedBlockingQueue(无界队列)通过链表实现队列,生产者和消费者各拥有一把锁
LinkedBlockingDeque 通过双向链表实现,优点是可以实现栈的功能

ArrayBlockingQueue

是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
ArrayBlockingQueue是一个用数组实现的有界阻塞队列。
队列慢时插入操作被阻塞,队列空时,移除操作被阻塞。
按照先进先出(FIFO)原则对元素进行排序。
默认不保证线程公平的访问队列。
公平访问队列:按照阻塞的先后顺序访问队列,即先阻塞的线程先访问队列。
非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格。有可能先阻塞的线程最后才访问访问队列。
公平性会降低吞吐量。

LinkedBlockingQueue

一个基于链表结构的阻塞队列,此队列按 FIFO 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool() 使用了这个队列。(newFixedThreadPool 用于创建固定线程数)

LinkedBlockingQueue具有单链表和有界阻塞队列的功能。
队列慢时插入操作被阻塞,队列空时,移除操作被阻塞。
默认和最大长度为Integer.MAX_VALUE,相当于无界(值非常大:2^31-1)。

非阻塞队列

非阻塞队列:通过cas的方式来保证线程安全,多个线程可以并发生产或者消费
比如:
ConcurrentLinkedQueue通过cas的方式实现线程安全,所以多个线程可以并发操作队列
ConcurrentLinkedDeque内部实现:双向链表+cas

拒绝策略

1CallerRunsPolicy:提交任务的线程自己去执行该任务。
2AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException3DiscardPolicy:直接丢弃任务,没有任何异常抛出。
4DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

线程池执行流程

1、当我们提交任务,线程池会根据 corePoolSize 大小创建若干任务数量线程执行任务
2、当任务的数量超过 corePoolSize 数量,后续的任务将会进入阻塞队列阻塞排队。
3、当阻塞队列也满了之后,那么将会继续创建(maximumPoolSize-corePoolSize)个数量的线程来执行任务,如果任务处理完成,maximumPoolSize-corePoolSize 额外创建的线程等待 keepAliveTime 之后被自动销毁。
4、如果达到 maximumPoolSize,阻塞队列还是满的状态,那么将根据不同的拒绝策略对应处理。

线程池工作原理

1、创建线程之后,开始等待请求。
2、当调用execute()方法添加一个请求任务时,线程池做出如下判断:
	2.1、如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
	2.2、如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
	3.3、如果这个时候队列满了且正在运行的数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个项目;
3、如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池就启动饱和策略和拒绝策略来执行
4、当一个线程完成任务时,它会从队列中取下一个任务来执行。
5、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
    5.1、如果当前运行的线程数大于corePoolSize,那么这个线程就会被停掉;
    5.2、所有的线程池的任务完成后,它最终会收缩到corePoolSize的大小。

线程池生命周期

RUNNING : 接收新的任务并处理队列中的任务
SHUTDOWN : 不接收新的任务,但是处理队列中的任务
STOP : 不接新的任务,不处理队列中的任务
TIDYING : 所有的任务处理完成,有效的线程数时
TERMINATED : terminated()方法执行完毕
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值