java.util.concurrent结构图
1.线程的创建和启动
(1)继承Thread类创建线程类
(2)实现Runnable接口创建线程类,将其对象作为Thread对象的target
(3)实现Callable接口,用FutureTask类包装Callable对象并将其作为Thread对象的target
2.线程的生命周期
(1)使用new关键字新建了线程--新建状态
(2)调用start()方法--就绪状态
(3)线程获得了CPU--运行状态
(4) > 线程调用sleep或join或suspend方法
> 线程调用阻塞式IO
> 线程在等待通知(notify)或同步监视器 --阻塞状态
阻塞状态会返回到就绪状态
(5) > run()方法执行完成或发生错误
> 调用stop()方法 --死亡状态
死亡状态不能用start()使它重新启动
3.线程锁
(1)同步监视器 synchronized(obj) {}
(2)同步方法 synchronized 修饰方法,监视器为this
线程会在以下情况释放同步监视器:
> 线程结束
> 线程使用了wait()方法
以下情况不会释放同步监视器:
> 调用Thread.sleep() Thread.yield() suspend方法
(3)同步锁
Lock类用Java写成,增强了Synchronized的功能
lock()方法只能被一个线程执行,因此lock()后的代码只能由该线程执行
ReentrantLock 可重入锁,表示同一个线程可以多次获得一个锁。
Example:
class Parent {
protected Lock lock = new ReentrantLock();
public void test() {
lock.lock();
try {
System.out.println("Parent");
} finally {
lock.unlock();
}
}
}
class Sub extends Parent {
@Override
public void test() {
lock.lock();
try {
super.test();
System.out.println("Sub");
} finally {
lock.unlock();
}
}
}
public class AppTest {
public static void main(String[] args) {
Sub s = new Sub();
s.test();
}
}
Synchronized 也是可重入的偏向锁-->同一个线程多次获得锁不需要CAS操作,
轻量锁(自旋锁)-->通过执行无用操作占用CPU时间片,短时间内获取锁,
重量锁
属于JVM层面的优化,是互斥锁的具体实现方式
(4)死锁
两个线程互相等待对方释放同步监视器时就会发生死锁
4.控制线程
(1)join线程 join() 阻塞调用线程,直到被join方法加入的join线程完成
(2)后台线程 setDaemon(true) 前台线程死亡,后台线程随之死亡
(3)static sleep() 当前正在执行的线程暂停一段时间
(4)static yield() 当前正在执行的线程转入就绪状态
(5)改变线程优先级 setPriority() getPriority()
(6)线程的协调运行
使用syncoronized关键字同步:
> wait() 导致当前线程等待
> notify() 唤醒此同步监视器上等待的任意线程
> notifyAll() 唤醒此同步监视器上等待的所有线程
以上三个方法属于Object类
使用Lock对象同步:
使用lock.newCondition()新建condition对象
> await() 方法
> signal() 唤醒在此Lock对象上等待的单个线程
> signalAll() 唤醒在此Lock对象上等待的所有线程
5.线程通信
Java采用了共享内存模型,其中所有实例域,静态域和数组元素存储在堆内存中,可以在线程之间共享
局部变量,方法参数,异常处理器参数在栈内存中,不会在线程之间共享
每个线程都有一个私有的本地内存,保存了共享变量的副本。由JMM(Java内存模型)自动完成与主内存的同步
注意本地内存是一个抽象概念,它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
6.非锁实现
(1)volatile关键字
使用该关键字会添加lock指令:
将当前处理器缓存行的数据会写回到系统内存
这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效
保证了变量的可见性,但不保证操作的原子性。
比如i++ 由 1)从主内存读取保存到本地内存 2)修改本地内存的值 3)写回主内存 组成
如果在第一个步骤后中断,则修改的是旧的值,写回主内存的也是旧的值
(2)原子类型
CAS操作:
有三个操作数--内存位置V,旧的预期值A和新值B。比较V与A,若相同,则使用B更新
原子操作即反复进行CAS操作,直至成功为止
Java中提供了原子类
AtomicInteger,AtomicLong,AtomicBoolean,AtomicReference
AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
提供了以下方法:
get() 获取值
set() 设置值
getAndSet() 设定新值并返回旧值
lazySet() 启动后台线程进行set()操作,不会发生阻塞
addAndGet() 相当于 i = i + delta 并返回i
getAndAdd() 相当于 t = i; i = newValue; return t;
incrementAndGet() 相当于 ++i
getAndIncrement() 相当于 i++
decrementAndGet() 相当于 --i
getAndDecrement() 相当于 i--
compareAndSet(int expect, int update) CAS操作
weakCompareAndSet(int expect, int update) 类似于上一个方法,但可能存在指令重排序导致操作失败
对于数组类型,第一个参数为int i 表示索引位置
7.线程组
默认情况下子线程与父线程属于同一线程组
可以用ThreadGroup类的构造方法新建线程组
利用Thread(ThreadGroup group, ...)构造方法新建加入到某一线程组的线程。
但不可以中途改变某线程所属的组
8.线程池
线程池预先创建一定数量休眠状态的线程。可以提高响应速度,有效控制并发线程的数量。
以下为Executor框架的类图:
Executors工厂类包含了以下方法:
newCachedThreadPool() 创建可缓存的线程池,由JVM动态调整大小
newFixedThreadPool(int nThreads) 创建具有固定线程数的线程池
newSingleThreadExecutor() 创建单线程的线程池,保证按照提交顺序执行任务
newScheduledThreadPool(int corePoolSize) 创建具有固定线程数的线程池,可以定时以及周期性执行任务
newSingleThreadScheduledExecutor() 创建单线程线程池,可以定时以及周期性执行任务
ExecutorService:
> submit(task) 向线程池提交任务
ScheduledExecutorService:
> schedule 指定在delay延迟后执行
> scheduleAtFixedRate (command, initialDelay, period, unit) 、
initialDelay后开始执行,每period为一个周期。当执行任务的时间大于指定的间隔时间时,等待该线程执行完毕。
> scheduleWithFixedDelay (command, initialDelay, delay, unit)
initialDelay后开始执行,一次任务结束后间隔delay再执行一次
使用shutdown()关闭线程池
9.多线程相关容器
1)同步容器
所有涉及到更新其中内容的方法都是同步的,将所有对容器的访问都串行化
主要包括两类:
> Vector、Stack、HashTable
> Collections类中提供的静态工厂方法创建的类
比如 Collections.synchronized(hashmap)
2)并发容器
> ConcurrentHashMap--HashMap
> ConcurrentLinkedQueue--Queue
> CopyOnWriteArrayList
> CopyOnWriteArraySet--基于CopyOnWriteArrayList,不允许有重复数据
ConcurrentHashMap将HashMap分为16个桶(默认值),每次只锁一个桶。实现了高并发
迭代时,ConcurrentHashMap没有使用快速失败迭代器,而是采用了弱一致迭代器。
弱一致迭代器可能会也可能不会反映迭代器迭代过程中的插入操作,但是一定会反映迭代器还没有到达的键的更新或删除操作,并且对任何值最多返回一次。
CopyOnWrite即写时复制的容器,当向一个容器添加元素的时候,先复制当前容器,向新容器添加。添加完后再将原容器引用指向新的容器。因此读的时候可以实现并发。
3)可阻塞队列
若BlockingQueue为空,从BlockingQueue取操作将会被阻断进入等待状态,直到BlockingQueue不为空才会被唤醒,若BlockingQueue已满,任何存操作也会被阻断,直到BlockingQueue不为满
BlockingQueue有以下方法:
前两列继承自Queue接口,后两列为可阻塞的方法
BlockingQueue有四个具体的实现类,根据不同需求,选择不同的实现类:
> ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小。其所含的对象是以FIFO(先入先出)顺序排序的。
> LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定。其所含的对象是以FIFO顺序排序的。
> PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。
> SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。