并发(concurrency):某段时间内,多个任务被CPU交替处理。
并行(parallelism):CPU同时处理多个任务。
并发打破和程序的封闭性,具有以下挑战:
1、并发程序之间有相互制约(对资源的争抢和彼此的依赖)的关系。
2、并发线程执行过程不连贯、断断续续,需要保存和切换现场。
3、设计合理的并发数,充分并合理利用CPU的执行能力才能真正发挥并发的优势。
线程数量并不是越多越好,要和相应的CPU资源匹配,合适的线程数才能充分利用计算资源。
线程的五个状态:NEW新建状态、RUNNABLE就绪状态、RUNNING运行状态、BLOCKED阻塞状态、DEAD终止状态。
线程被创建且未启动时的状态为NEW;线程调用start()方法之后进入就绪状态为RUNNABLE;线程被分配CPU时间片,开始执行run()方法,线程进入执行状态RUNNING;线程由于某些原因会进入阻塞状态BLOCKED;线程执行完毕,退出run()方法或异常退出即进入终止态DEAD。
有三类原因会让RUNNING中的线程变成BLOCKED:等待阻塞(调用wait方法)、主动阻塞(通过sleep、join主动让出CPU执行权)、同步阻塞(需要等待其他线程释放锁,I/O阻塞、同步块阻塞、锁阻塞)
线程从BLOCKED状态通过“苏醒(sleep时间到了)”、“被唤醒(notify)”、“获得锁”进入的下一个状态是RUNNABLE,线程退出阻塞态进入了准备就绪状态,等待CPU分配时间片后再次获得执行的权利。
高并发场景下如何做到线程安全:要么只读、要么加锁。
只读:只读对象(String、Integer等)、数据只在单线程可见(ThreadLocal、线程局部变量)
加锁:同步与锁机制、直接引用线程安全类(ConcurrentHashMap等)
锁
乐观锁:总是保持乐观预期,总假设最好情况;即总乐观的认为线程每次拿数据时都不会有更新操作,所以“拿”的时候不加锁,当察觉到线程有更新操作时,才会用版本和CAS机制去判断有无冲突。适用于读多写少的场景,可提高效率。
悲观锁:总是保持悲观预期,总假设最坏情况;即总悲观的认为线程每次拿数据时都会有更新操作,所以线程“拿”数据之前先要锁住数据,共享数据每次只能由一个线程独享。无论读写,都要上锁。适用于更新操作很频繁的场景。Java中的ReentrantLock和synchronized关键字等独占锁就是悲观锁的实现。
synchronized锁机制由JVM实现,这个机制是在不断进化的。其底层通过监视锁来实现,即每个对象与生俱来的一个隐藏属性:monitor监视锁。JDK6后JVM对synchronized进行优化,提供了三种锁的实现:偏向锁、轻量级锁、重量级锁,还提供生动升级降级机制,随着锁的竞争加剧和减弱,自动升级或降级锁的级别,从而保证性能。
线程同步
信号量同步
CountDownLatch:倒数计数门闩类,从N开始倒数到0时,才执行积攒的线程。一次性的。
CyclicBarrier:循环栅格类,与上面的类功能相似,但可以通过调用reset重复利用。
Semaphore:信号量类,可定义资源上线,当设置信号量为1时,实现互斥锁。
线程池
线程池的好处:1方便管理和复用线程2环境隔离3实现定时和调度4协调线程
ThreadLocal
一个可以被多个线程共享,在各个线程内修改操作是独立的,可在进入各个线程前统一初始化赋值。
ThreadLocal对象容易造成脏读和内存泄漏,所以在每次用完后,必须及时调用其remove方法,进行及时清理。