3.1.1、线程安全
多个线程访问某个类时,不管运行环境和调度方式,这个类始终表现出正确的行为,称为线程安全 。
3.1.2、多线程编程3个核心概念
-
原子性:同数据库,要么全执行,要么全失败。(锁和同步)
-
可见性:并发访问共享变量时,对共享变量的修改,其他都能看到。(volatile修饰)
-
顺序性:jvm优化,调整顺序代码,程序的执行顺序并非按照代码的先后顺序。
3.1.3、线程之间的状态
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
3.1.3.2、线程阻塞情况
线程阻塞 | 描述 |
---|---|
1.等待阻塞 | 运行的线程执行wait()方法,会释占有的资源,jvm会将线程放入“等待池中”。不能自动唤醒,必须依靠其他线程调用notify()/notifyall() |
2.同步阻塞 | 运行的线程在获取对象的同步锁时,若该同步锁被其他线程占用,jvm会把该线程放入“锁池中” |
3.其他阻塞 | 运行的线程执行sleep()/join()方法,或者发出IO请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时,join()等待线程终止或者超时,或者IO处理完毕时,线程重新转入就绪状态。 |
3.1.4、java四种线程池简介
四种线程池内部构造都是来自同一个方法:
public ThreadPoolExecutor(int corePoolSize, //核心线程数 int maximumPoolSize,//最大线程数 long keepAliveTime,//存活时间 TimeUnit unit,//时间单位 BlockingQueue<Runnable> workQueue,//任务队列 ThreadFactory threadFactory) { //拒绝策略 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
BlockingQueue: 任务队列,如果当前线程池中核心线程数达到了corePoolSize时,且当前所有线程都属于活动状态时,则将新的任务添加到该队列中。基本上有以下几个实现: 1) ArrayBlockQueue:基于数组结构的有界队列,此队列按FIFO(first in first out)原则对任务进行排序。如果队列已满,新的任务将会被采取拒绝策略对待。 2)LinkedBlockingQueue: 基于链表的无界队列,按FIFO原则排序。因为是无界的,所以不存在满的情况,此时拒绝策略无效 3) PriorityBlockingQueue:具有优先级的队列的有界队列,可以自定义优先级,默认为自然排序。
Handler:拒绝策略,当线程池和workQueue都满了的情况下,对新任务采取的处理策略,有四种默认实现: 1) AbortPolicy:拒绝任务,且还抛出RejectedExecutionException异常,线程池默认策略 2) CallerRunPolicy:拒绝新任务进入,如果该线程池还没有被关闭,那么这个新的任务在执行线程中被调用 3) DiscardOldestPolicy: 如果执行程序尚未关闭,则位于头部的任务将会被移除,然后重试执行任务(再次失败,则重复该过程),这样将会导致新的任务将会被执行,而先前的任务将会被移除。 4)DiscardPolicy:没有添加进去的任务将会被抛弃,也不抛出异常。基本上为静默模式。
3.1.4.1、fixThreadPool 正规线程
它是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来。FixedThreadPool只有核心线程,且该核心线程都不会被回收,这意味着它可以更快地响应外界的请求。jdk实现如下:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
3.1.4.2、caCheThreadPool 缓存线程池
只有非核心线程,最大线程数很大(Int.Max(values)),它会为每一个任务添加一个新的线程,这边有一个超时机制,当空闲的线程超过60s内没有用到的话,就会被回收。缺点就是没有考虑到系统的实际内存大小。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
3.1.4.3、singleThreadPoll 单线程线程池
看这个名字就知道这个家伙是只有一个核心线程,就是一个孤家寡人,通过指定的顺序将任务一个个丢到线程,都乖乖的排队等待执行,不处理并发的操作,不会被回收。缺点就是一个人干活效率慢。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
3.1.4.4、ScheduledThreadPoll
这个线程池就厉害了,是唯一一个有延迟执行和周期重复执行的线程池。它的核心线程池固定,非核心线程的数量没有限制,但是闲置时会立即会被回收 。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
注意: