线程与进程
线程是程序的最小执行单元
进程是操作系统进行资源分配和调度的一个基本单位
关联:一个程序至少有一个进程,一个进程又至少包含一个线程
引入线程的目的是充分利用CPU资源,使其可以并行处理多个任务,减少时间消耗
线程分类
用户线程User Thread
一般是程序中创建的线程
守护线程Daemon Thread
为用户线程服务的线程当所有非守护线程结束时才会被终止。如JVM的垃圾回收、内存管理等。
通过thread.setDaemon(true)来将一个线程变成守护线程
Thread类的常用方法
Thread.sleep()方法
1、sleep方法是Thread类的静态方法,是为了保证该操作只对当前线程有效,避免线程安全问题,其它几个常用静态方法类似。 2、让当前正在运行的线程暂时停止运行,一段时间后会继续执行。
Thread.yield()方法
当前处于运行状态的线程主动放弃占用的CPU资源,转变为就绪状态,让其他先线程执行(让步)
join方法
在当前线程执行过程中引入另一个线程,并且当前线程需要等待另一个线程执行完毕后才能继续执行
Thread.currentThread()方法
获取当前正在运行的线程
停止线程的方法
Thread.stop方法:已废弃
使用一个标识来表示线程的状态,通过更改它的值来控制线程的运行和停止
interrupt 中断方法
线程间通信
wait方法
notify方法
这三个方法用于协调多个线程对共享数据的存取(获取锁和释放锁),所以必须先获得锁(即在synchronized语句块内使用),否则会抛出IllegalMonitorStateException异常。
notifyAll方法
定时任务
Timer & TimerTask类
Timer:任务调度器,通过schedule(TimerTask task, long delay)等方法进行调度。 TimerTask:实现了Runnable接口。表示需要调度的任务,里面有一个run方法定义具体的任务。
Timer缺点:内部是单线程,因此如果有异常产生,线程将退出,整个定时任务就会失败
线程池
ScheduledExecutorService,它是ExecutorService的子接口。弥补了Timer的缺陷
通过 scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)和scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)等方法来实现定时任务调度
异步任务
只需创建并启动一个线程执行该任务即可,或者使用线程池更好
并发编程
JUC
线程池
作用
1、减少资源消耗。通过重复利用池中已创建的线程,减少频繁创建、销毁线程带来的资源消耗。 2、提高响应速度。当线程池中有空闲线程,任务到来时无需创建线程就能立即被执行。 3、提高线程的可管理性。由线程池对池中的线程进行统一的管理和监控,可以防止无限制创建线程造成的资源浪费。
ExecutorService接口
ThreadPoolExecutor类
Executor的子类
Executors工具类
包含创建线程池的工厂(静态)方法
线程池大小配置
一般需要根据任务类型来配置线程池大小: 1、如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+12、如果是IO密集型任务,参考值可以设置为2*NCPU
通过Runtime.getRuntime().availableProcessors()获得当前CPU个数
原子操作类,如AtomicInteger
使用CAS无锁技术保证变量的操作在多线程环境下正常,比synchronized控制的粒度更细、量级更轻,并且在多核处理器具有高性能
并发集合
ConcurrentHashMap
Java7引入分段锁技术Segment
Java8开始加入红黑树
CopyOnWriteArrayList
从字面上看就是”写时复制“。原理是当需要对集合中元素进行增删改操作时首先复制一个副本,对副本进行操作
适用于“读多写少”的并发场景
BlockingQueue
阻塞队列: 1)当队列为空时,获取元素的线程会等待队列变为非空;2)当队列满时,存储元素的线程会等待队列可用。内部通过Lock与Condition(即通过等待/通知机制)实现,通常用于生产者-消费者场景
ArrayBlockingQueue:有界队列,即队列容量有限,内部是数组结构
LinkedBlockingQueue:既可以是有界队列,又可以是无界队列。若创建时不指定容量,则默认是Integer.MAX_VALUE
SynchronousQueue:同步队列
不保存元素
PriorityBlockingQueue:优先级队列
属于无界队列,按照队列中的任务优先级由高到低进行处理
通过Comparator比较器来决定优先级
ThreadLocal线程本地变量
1、它为每个线程都提供一个独立的变量副本,各个线程都可以改变自己的变量副本,各个线程间互不影响。因此可以解决并发问题。 2、实现的思路:①在ThreadLocal类中有一个静态内部类ThreadLocalMap,用于存储每一个线程的变量副本,键为ThreadLocal对象,而值则是对应线程的变量副本;②每个ThreadLocal实例都有一个唯一的threadLocalHashCode(这个值将会用于在ThreadLocalMap中找到ThreadLocal对应的value值)。3、工作原理:①在Thread类中维护一个ThreadLocalMap变量(与线程进行绑定);②取值get操作:从ThreadLocal中取变量值时先获取当前线程,通过当前线程得到与之关联的ThreadLocalMap,然后再从ThreadLocalMap中根据当前ThreadLocal获取到其中的变量值(如果ThreadLocalMap为空,则返回初始化方法initialValue()的值);set操作类似。
CountDownLatch (倒计数器)
利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他几个任务执行完毕之后才能执行,就可以使用CountDownLatch来实现。
两个关键方法:await()等待其它线程执行完毕才开始执行、countDown()当有一个线程执行完毕就减1,当减到0时当前线程开始执行。
CyclicBarrier(回环栅栏)
通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。
关键方法:await()方法,此方法作用是等待其它线程都到同一状态时开始同时执行。类似于长跑比赛中,当所有运动员都准备完毕才开始比赛。
Fork/Join 多线程并行框架
利用分而治之的思想,将大任务分成小任务执行,然后合并结果;分别对应fork、join两个操作。
Fork/Join框架的核心是ForkJoinPool类,它是对AbstractExecutorService类的扩展。 ForkJoinPool实现了工作窃取(work-stealing)算法,并可以执行ForkJoinTask任务。
线程同步
volatile
线程每次都从主内存中读取变量,改变后再写回到主内存。其作用如下: 1、使变量在多个线程间具有可见性;2、禁止JVM进行指令重排序(保证有序性)。
不能保证原子性,非线程安全
应用场景
一写多读,保证变量可见性
开销较低的读写锁策略
只在写操作时需要加synchronized同步锁,读操作不需要
synchronized
在JVM层面实现了对临界资源的同步互斥访问,锁的释放不用人工干预,由虚拟机完成
1、在volatile基础上增加了互斥锁,所谓“互斥”就是同一时间只能有一个线程操作该资源; 2、在JDK1.5以后,为了弥补synchronized的不足,引入了Lock来代替它,将同步锁对象换成了Condition对象,并且Condition对象可以有多个。
用法
同步代码块
通常将外界资源作为锁的对象
synchronized(obj) { // 同步操作代码}
用于保护外界资源不被多个线程并发修改
与同步方法比较而言,使用同步代码块的好处在于其他线程仍可以访问同步代码块以外的代码
同步方法
锁的对象是当前调用对象this
public synchronized void test() { // 同步操作代码}
用于保护对象属性值不会被多个线程并发修改,因此需要保证调用方法的对象是同一个才有意义
同步静态方法
锁的对象是类的Class对象
public static synchronized void test() { // 同步代码}
用于保护类的静态属性值不会被多个线程并发修改
缺点: 1、无法知道是否成功获取到锁2、如果是多个线程需要同时进行读操作,一个线程读操作时其它线程只有等待(互斥)
Lock接口
JDK层面实现的互斥锁,比起synchronized可控性更强,弥补了synchronized的不足。使用后必须手动释放锁,否则可能会导致死锁
包含lock(如果没获取到,会一直阻塞直到成功获取到锁)、tryLock(尝试获取锁,如果没获取到不会一直阻塞,可以指定等待时间)、unlock(释放锁)几个常用方法,都需要显示调用。
其实现类有:ReadLock(读锁)、WriteLock(写锁)、ReentrantLock(可重入锁)等
Condition接口
提供了类似Object类中wait、notify、notifyAll的方法,主要包括await、signal、signalAll方法, 与synchronized和wait、notify/notifyAll的搭配类似,这些方法与Lock锁配合使用也可以实现等待/通知机制
无锁技术CAS
CAS即Compare And Swap的缩写。Java中的原子操作类如AtomicInteger底层就是依赖它实现的。 实现原理:CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
线程死锁
产生死锁的四个必要条件
互斥条件:资源不能被共享。即任一时刻一个资源只能给一个进程使用,其他进程只能等待,直到资源被占有者释放。不可剥夺条件:已经分配的资源不能从相应的进程中被强制地剥夺,而只能由获得该资源的进程自愿释放。请求和保持条件:已经得到资源的进程可以再次申请新的资源。循环等待条件:系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。
预防死锁的方法
合理对资源进行动态分配,以避免死锁
破坏死锁产生的四个必要条件