1. 线程创建
1.1 继承Thread
继承Thread类,重写run方法
public class ThreadDemo01 extends Thread{
public ThreadDemo01(){
//编写子类的构造方法,可缺省
}
public void run(){
//编写自己的线程代码
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args){
ThreadDemo01 threadDemo01 = new ThreadDemo01();
threadDemo01.setName("我是自定义的线程1");
threadDemo01.start();
System.out.println(Thread.currentThread().toString());
}
}
1.2 实现Runable
实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
public class ThreadDemo02 {
public static void main(String[] args){
System.out.println(Thread.currentThread().getName());
Thread t1 = new Thread(new MyThread());
t1.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+"-->我是通过实现接口的线程实现方式!");
}
}
1.3 实现Callable
通过Callable和FutureTask创建线程
public class ThreadDemo03 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Callable<Object> oneCallable = new Tickets<Object>();
FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable);
Thread t = new Thread(oneTask);
System.out.println(Thread.currentThread().getName());
t.start();
}
}
class Tickets<Object> implements Callable<Object>{
//重写call方法
@Override
public Object call() throws Exception {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+"-->我是通过实现Callable接口通过FutureTask包装器来实现的线程");
return null;
}
}
1.4 线程池
状态名 | 高 3 位 | 接收新任 务 | 处理阻塞队列任 务 | 说明 |
---|---|---|---|---|
RUNNING | 111 | Y | Y | |
SHUTDOWN | 000 | N | Y | 不会接收新任务,但会处理阻塞队列剩余 任务 |
STOP | 001 | N | N | 会中断正在执行的任务,并抛弃阻塞队列 任务 |
TIDYING | 010 | - | - | 任务全执行完毕,活动线程为 0 即将进入 终结 |
TERMINATED | 011 | - | - | 终结状态 |
1.4.0 自定义线程池
步骤1:自定义拒绝策略接口
package com.yuepengfei.java.ThreadPool;
@FunctionalInterface // 拒绝策略
interface RejectPolicy<T> {
void reject(BlockingQueue<T> queue, T task);
}
步骤2:自定义任务队列
package com.yuepengfei.java.ThreadPool;
import org.apache.log4j.Logger;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class BlockingQueue<T> {
private static Logger log = Logger.getLogger(BlockingQueue.class);
// 1. 任务队列
private Deque<T> queue = new ArrayDeque<>();
// 2. 锁
private ReentrantLock lock = new ReentrantLock();
// 3. 生产者条件变量
private Condition fullWaitSet = lock.newCondition();
// 4. 消费者条件变量
private Condition emptyWaitSet = lock.newCondition();
// 5. 容量
private int capcity;
public BlockingQueue(int capcity) {
this.capcity = capcity;
}
// 带超时阻塞获取
public T poll(long timeout, TimeUnit unit) {
lock.lock();
try {
// 将 timeout 统一转换为 纳秒
long nanos = unit.toNanos(timeout);
while (queue.isEmpty()) {
try {
// 返回值是剩余时间
if (nanos <= 0) {
return null;
}
nanos = emptyWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
// 阻塞获取
public T take() {
lock.lock();
try {
while (queue.isEmpty()) {
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
// 阻塞添加
public void put(T task) {
lock.lock();
try {
while (queue.size() == capcity) {
try {
log.debug("等待加入任务队列 {} ...");
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}");
queue.addLast(task);
emptyWaitSet.signal();
} finally {
lock.unlock();
}
}
// 带超时时间阻塞添加
public boolean offer(T task, long timeout, TimeUnit timeUnit) {
lock.lock();
try {
long nanos = timeUnit.toNanos(timeout);
while (queue.size() == capcity) {
try {
if(nanos <= 0) {
return false;
}
log.debug("等待加入任务队列 {} ...");
nanos = fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}");
queue.addLast(task);
emptyWaitSet.signal();
return true;
} finally {
lock.unlock();
}
}
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
// 判断队列是否满
if(queue.size() == capcity) {
rejectPolicy.reject(this, task);
} else { // 有空闲
log.debug("加入任务队列 {}");
queue.addLast(task);
emptyWaitSet.signal();
}
} finally {
lock.unlock();
}
}
}
步骤3:自定义线程池
package com.yuepengfei.java.ThreadPool;
import org.apache.log4j.Logger;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
public class ThreadPool {
private static org.apache.log4j.Logger log = Logger.getLogger(ThreadPool.class);
// 任务队列
private BlockingQueue<Runnable> taskQueue;
// 线程集合
private HashSet<Worker> workers = new HashSet<>();
// 核心线程数
private int coreSize;
// 获取任务时的超时时间
private long timeout;
private TimeUnit timeUnit;
private RejectPolicy<Runnable> rejectPolicy;
// 执行任务
public void execute(Runnable task) {
// 当任务数没有超过 coreSize 时,直接交给 worker 对象执行
// 如果任务数超过 coreSize 时,加入任务队列暂存
synchronized (workers) {
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
log.debug("新增 worker{}, {}");
workers.add(worker);
worker.start();
} else {
// taskQueue.put(task);
// 1) 死等
// 2) 带超时等待
// 3) 让调用者放弃任务执行
// 4) 让调用者抛出异常
// 5) 让调用者自己执行任务
taskQueue.tryPut(rejectPolicy, task);
}
}
}
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity,
RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.taskQueue = new BlockingQueue<>(queueCapcity);
this.rejectPolicy = rejectPolicy;
}
class Worker extends Thread {
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
// 执行任务
// 1) 当 task 不为空,执行任务
// 2) 当 task 执行完毕,再接着从任务队列获取任务并执行
// while(task != null || (task = taskQueue.take()) != null) {
while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
try {
log.debug("正在执行...{}");
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
synchronized (workers) {
log.debug("worker 被移除{}");
workers.remove(this);
}
}
}
}
1.4.1 ThreadPoolExecutor 和Schedulethreadpoolexecutor
- 构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
注:
corePoolSize 核心线程数目 (最多保留的线程数)
maximumPoolSize 最大线程数目
keepAliveTime 生存时间 - 针对救急线程
unit 时间单位 - 针对救急线程
workQueue 阻塞队列
threadFactory 线程工厂 - 可以为线程创建时起个好名字
handler 拒绝策略
- 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
- 当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排
队,直到有空闲的线程。 - 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线
程来救急。 - 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它
著名框架也提供了实现- AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
- CallerRunsPolicy 让调用者运行任务
- DiscardPolicy 放弃本次任务
- DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
- Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方
便定位问题 - Netty 的实现,是创建一个新线程来执行任务
- ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
- 当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由
keepAliveTime 和 unit 来控制。
1.4.2 newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点:
- 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
- 阻塞队列是无界的,可以放任意数量的任务
评价:
- 适用于任务量已知,相对耗时的任务
1.4.3 newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点:
-
核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着
全部都是救急线程(60s 后可以回收)
救急线程可以无限创建
-
队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交
货
评价:
- 整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线
程。 适合任务数比较密集,但每个任务执行时间较短的情况
1.4.4 newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
评语:
- 希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
区别:
- 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一
个线程,保证池的正常工作 - Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
- FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因
此不能调用 ThreadPoolExecutor 中特有的方法
- FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因
- Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
- 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
1.4.5 newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
1.4.6 线程池关闭
-
shutdown
将线程池状态置为
SHUTDOWN
,并不会立即停止:- 停止接收外部submit的任务
- 内部正在跑的任务和队列里等待的任务,会执行完
- 等到第二步完成后,才真正停止
-
shutdownNow
将线程池状态置为stop。企图立即停止,事实上不一定:
- 跟shutdown()一样,先停止接收外部提交的任务
- 忽略队列里等待的任务
- 尝试将正在跑的任务interrupt中断
- 返回未执行的任务列表
它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。 但是大多数时候是能立即退出的
-
awaitTermination(long timeOut, TimeUnit unit)
当前线程阻塞,直到
- 等所有已提交的任务(包括正在跑的和队列中等待的)执行完
- 或者等超时时间到
- 或者线程被中断,抛出InterruptedException
调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事
情,可以利用此方法等待
2. 线程状态
2.1 线程的五种状态
- 新建状态(new)
- 就绪状态(runable)
- 运行状态(running)
- 阻塞状态(blocked)
- 死亡状态(dead)
注意:
yield、sleep、wait
yield和sleep都是Thread的静态方法,wait是object的方法,前两个都不释放锁,wait释放锁。yield的和sleep的区别是,sleep可以设定时间,yield可以理解为sleep(0)
2.2 Thread重要方法
2.2.1 常规方法
-
start方法,开始执行该线程
-
stop方法,强制结束该线程执行
-
join方法,等待该线程结束
-
sleep和yield方法,线程进入等待
-
run()方法
直接执行线程的run()方法,但是线程调用start()方法时也会运行run()方法,区别就是一个是由线程调度运行run()方法,一个是直接调用了线程中的run()方法
2.2.2 interrupt
-
interrupt本质只是一个普通方法,调用时会把线程是否被打断的flag设置为true。这在sleep,join,await,wait等方法中会抛出线程被打断的异常,并将flag设置为false。
-
对park的影响
如下两个条件任何一个成立,park()都不会阻塞:
- 中断标志位存在(wait、join、sleep或Thread.interrupted()都会清除中断标志位)
- _counter为1(之前调用了unpark或者interrupt)
2.2.3 park和unpark
- 每个线程都有自己的一个 Parker 对象,由三部分组成 __counter , _cond 和 _mutex_打个比喻线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
- 调用 park 就是要看需不需要停下来歇息
- 如果备用干粮耗尽,那么钻进帐篷歇息
- 如果备用干粮充足,那么不需停留,继续前进
- 调用 unpark,就好比补充干粮
- 如果这时线程还在帐篷,就唤醒让他继续前进
- 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
- 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮
应用
Park在写的时候要拿到线程,然后进行操作,线程多的话可能需要将线程放在数组里面或者集合里,按照自己的需求唤醒对应的线程。
2.3 锁对象方法
- wait和notify、notifyAll
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
- BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
- BLOCKED 线程会在 Owner 线程释放锁时唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入
EntryList 重新竞争
正确使用方式:一般我们使用wait和notifyAll,因为notify是随机唤醒,一般不符合业务逻辑
package com.yuepengfei.java.WaitNotify;
public class WaitNotity {
static Object lock = new Object();
static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() ->{
//todo wait,notify必须在锁代码块中,不然会报错
synchronized(lock) {
while(flag) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 干活
System.out.println("干活了");
}
}).start();
//另一个线程
new Thread(()->{
synchronized(lock) {
flag = false;
lock.notifyAll();
}
}).start();
}
}
- await和signal、signalAll
使用案例
package com.yuepengfei.java;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class AwaitSignalDemo {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
lock.lock();
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
System.out.println("线程" + Thread.currentThread().getName());
});
t.setName("线程2");
t.start();
Thread.sleep(1000);
new Thread(() -> {
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}).start();
}
}
3. 关键字
3.1. volatile
3.1.1 原理
volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
- 对 volatile 变量的写指令后会加入写屏障
- 对 volatile 变量的读指令前会加入读屏障
3.1.2 保证可见性
写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
public void actor2(I_Result r) {
num = 2;
ready = true; // ready 是 volatile 赋值带写屏障
// 写屏障
}
而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
public void actor1(I_Result r) {
// 读屏障
// ready 是 volatile 读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
3.1.3 保证有序性
写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
注意:读写屏障之保证线程内的指令重排,不能控制多线程内的指令交错
3.1.4 单例模式中的应用
public final class Singleton {
private Singleton() { }
private static volatile Singleton INSTANCE = null;
public static Singleton getInstance() {
// 实例没创建,才会进入内部的 synchronized代码块
if (INSTANCE == null) {
synchronized (Singleton.class) { // t2
// 也许有其它线程已经创建实例,所以再判断一次
if (INSTANCE == null) { // t1
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
解释:
- 单层if存在的缺点和不足
- 不加volatile存在的问题
3.2 synchronized
3.2.1 锁升级
无锁 -----> 轻量级锁 -----> 偏向锁 -----> 重量级锁
3.2.2 对象头
这里的对象是指:锁对象
3.2.3 偏向锁
偏向锁产生的原因:轻量级锁参在锁重入的问题
只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有 。
- 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0
- 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数-XX:BiasedLockingStartupDelay=0 来禁用延迟
- 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、
age 都为 0,第一次用到 hashcode 时才会赋值
3.2.4 轻量级锁
轻量级锁产生的原因:很多时候线程之间并不存在锁竞争,因此不需要启动重量级锁
3.2.5 重量级锁
- monitor
每个 Java锁对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的
Mark Word 中就被设置指向 Monitor 对象的指针
4. AQS
AQS其实就是AbstractQueuedSynchronizer这个抽象类的简称。重写其内的部分方法就可以实现自己的并发工具,常用的并发工具很多都是基于其开发的,AQS就是并发编程的优秀模板。
4.1 AQS常见概念
4.1.1 关键方法和属性
- acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
其中tryAcquire(arg)方法需要重写。例如
@Override
protected boolean tryAcquire(int acquires) {
if (acquires == 1){
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}
return false;
}
- release
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
其中tryRelease(arg)方法需要重写。例如
@Override
protected boolean tryRelease(int acquires) {
if(acquires == 1) {
if(getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
return false;
}
- acquireShared(int arg)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
- releaseShared(int arg)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- acquireInterruptibly
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
- acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- enq
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
解读可参考:并发编程之美 P127
- isHeldExclusively
是否独自站
-
state
-
ConditionObject
Condition newCondition() { return new ConditionObject(); }
AQS中实现了ConditionObject,却没创建生成ConditionObject的方法,所以一般自定义同步器多添加上面一段代码。
注意: java并发编程之美P135的虚假唤醒的情况
4.1.2 独占锁和共享锁
独占锁:最多一个线程占有锁,例如:ReentrantLock
共享锁:可多个线程占有同一把锁,例如:Semaphore
4.1.3 公平锁和非公平锁
公平锁:如果有线程在排队,则线程直接进入队列排队
非公平锁:线程进来先尝试获取锁,获取不到才排队
4.1.4 可打断和不可打断
不可打断:即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了
4.2 ReentrantLock
重点研究AQS,ReentrantLock只是其一个实现,可重入独占锁。支持公平锁和非公平锁,默认非公平锁。
4.3 读写锁
巧妙使用AQS的状态高16位表示获取到读锁的个数,低16位表示写线程的可重入次数。实现了读写分离。
5. 原子类
5.1 原子整数
AtomicBoolean
AtomicInteger
AtomicLong
使用没啥好说的,有时间可以研究一下其原理。
5.2 原子引用
AtomicReference
AtomicMarkableReference
AtomicStampedReference
AtomicReference
原子意味着尝试更改相同AtomicReference的多个线程(例如,使用比较和交换操作)不会使AtomicReference最终达到不一致的状态。 AtomicReference甚至有一个先进的compareAndSet()方法,它可以将引用与预期值(引用)进行比较,如果它们相等,则在AtomicReference对象内设置一个新的引用。
在AtomicReference基础之上多了一个时间戳的概念,解决A–>B–>A的问题
AtomicMarkableReference
和AtomicStampedReference比较,时间戳编程了标记位。
5.3 原子数组
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
5.4 字段更新器
5.5 原子累加器
- LongAdder
package com.yuepengfei.java;
import lombok.SneakyThrows;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.LongAdder;
public class LongAdderDemo {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
LongAdder longAdder = new LongAdder();
CountDownLatch countDownLatch = new CountDownLatch(100);
CyclicBarrier cyclicBarrier = new CyclicBarrier(100, new Runnable() {
@Override
public void run() {
System.out.println("所有线程开已准备就绪");
}
});
Semaphore semaphore = new Semaphore(10);
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
cyclicBarrier.await();//本线程已到位
semaphore.acquire();//或许许可
for (int j = 0; j < 100; j++) {
longAdder.add(1L);
}
semaphore.release();//释放许可
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
System.out.println("计算结束");
System.out.println(longAdder.longValue());
}
}
- LongAccumulator
LongAdder其实是LongAccumulator的一个特例,调用LongAdder相当使用下面的方式调用LongAccumulator
LongAccumulator accumulator = new LongAccumulator(new LongBinaryOperator() {
@Override
public long applyAsLong(long left, long right) {
return left + right;
}
}, 0);
5.6 Unsafe
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得
public class UnsafeAccessor {
static Unsafe unsafe;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
}
static Unsafe getUnsafe() {
return unsafe;
}
}
Unsafe CAS 操作
@Data
class Student {
volatile int id;
volatile String name;
}
Unsafe unsafe = UnsafeAccessor.getUnsafe();
Field id = Student.class.getDeclaredField("id");
Field name = Student.class.getDeclaredField("name");
// 获得成员变量的偏移量
long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id);
long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name);
Student student = new Student();
// 使用 cas 方法替换成员变量的值
UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20); // 返回 true
UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, "张三"); // 返回 true
System.out.println(student)
6. 同步器
package com.yuepengfei.java;
import lombok.SneakyThrows;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.LongAdder;
public class LongAdderDemo {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
LongAdder longAdder = new LongAdder();
CountDownLatch countDownLatch = new CountDownLatch(100);
CyclicBarrier cyclicBarrier = new CyclicBarrier(100, new Runnable() {
@Override
public void run() {
System.out.println("所有线程开已准备就绪");
}
});
Semaphore semaphore = new Semaphore(10);
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
cyclicBarrier.await();//本线程已到位
semaphore.acquire();//或许许可
for (int j = 0; j < 100; j++) {
longAdder.add(1L);
}
semaphore.release();//释放许可
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
System.out.println("计算结束");
System.out.println(longAdder.longValue());
}
}
6.1 CountDownLatch
CountDownLatch countDownLatch = new CountDownLatch(100);
调用countDownLatch.await()
的线程进入等待,等待计数(初始值100)为0
,当一个线程调用countDownLatch.countDown()
,其中的计数减一。注:await可以设定超时时间。
例子:比如张三、李四和王五几个人约好去饭店一起去吃饭,这几个人都是比较绅士,要等到所有人都到齐以后才让服务员上菜。这种场景就可以用到CountDownLatch。
- 顾客类
package onemore.study;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class Customer implements Runnable {
private CountDownLatch latch;
private String name;
public Customer(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}
@Override
public void run() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
Random random = new Random();
System.out.println(sdf.format(new Date()) + " " + name + "出发去饭店");
Thread.sleep((long) (random.nextDouble() * 3000) + 1000);
System.out.println(sdf.format(new Date()) + " " + name + "到了饭店");
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 服务员类
package onemore.study;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class Waitress implements Runnable {
private CountDownLatch latch;
private String name;
public Waitress(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}
@Override
public void run() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
System.out.println(sdf.format(new Date()) + " " + name + "等待顾客");
latch.await();
System.out.println(sdf.format(new Date()) + " " + name + "开始上菜");
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 测试类
package onemore.study;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTester {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
List<Thread> threads = new ArrayList<>(3);
threads.add(new Thread(new Customer(latch, "张三")));
threads.add(new Thread(new Customer(latch, "李四")));
threads.add(new Thread(new Customer(latch, "王五")));
for (Thread thread : threads) {
thread.start();
}
Thread.sleep(100);
new Thread(new Waitress(latch, "♥小芳♥")).start();
for (Thread thread : threads) {
thread.join();
}
}
}
6.2 CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(100, new Runnable() {
@Override
public void run() {
System.out.println("所有线程开已准备就绪");
}
});
设定阈值为100,当线程调用cyclicBarrier.await()
进入等待,当等待的线程达到100则等待的线程开始执行。
6.3 Semaphore
Semaphore semaphore = new Semaphore(10);
简单说,作用就是控制线程数。semaphore.acquire()
获取许可,最多同时许可10个,semaphore.release()
释放许可,线程执行完及时释放。
Semaphore和线程池的区别:
使用
Semaphore
,实际工作线程由开发者自己创建;使用线程池,实际工作线程由线程池创建使用
Semaphore
,并发线程的控制必须手动通过acquire()
和release()
函数手动完成;使用线程池,并发线程的控制由线程池自动管理使用
Semaphore
不支持设置超时和实现异步访问;使用线程池则可以实现超时和异步访问,通过提交一个Callable
对象获得Future
,从而可以在需要时调用Future
的方法获得线程执行的结果,同样利用Future
也可以实现超时
7. 常见线程类
7.1 ThreadLocal
ThreadLocal提供了线程的局部变量,每个线程都可以通过set()
和get()
来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。简要言之:往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。
7.1.1价值意义
- 管理Connection
**最典型的是管理数据库的Connection:**当时在学JDBC的时候,为了方便操作写了一个简单数据库连接池,需要数据库连接池的理由也很简单,频繁创建和关闭Connection是一件非常耗费资源的操作,因此需要创建数据库连接池。
那么,数据库连接池的连接怎么管理呢??我们交由ThreadLocal来进行管理。为什么交给它来管理呢?ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务。
public class DBUtil {
//数据库连接池
private static BasicDataSource source;
//为不同的线程管理连接
private static ThreadLocal<Connection> local;
static {
try {
//加载配置文件
Properties properties = new Properties();
//获取读取流
InputStream stream = DBUtil.class.getClassLoader().getResourceAsStream("连接池/config.properties");
//从配置文件中读取数据
properties.load(stream);
//关闭流
stream.close();
//初始化连接池
source = new BasicDataSource();
//设置驱动
source.setDriverClassName(properties.getProperty("driver"));
//设置url
source.setUrl(properties.getProperty("url"));
//设置用户名
source.setUsername(properties.getProperty("user"));
//设置密码
source.setPassword(properties.getProperty("pwd"));
//设置初始连接数量
source.setInitialSize(Integer.parseInt(properties.getProperty("initsize")));
//设置最大的连接数量
source.setMaxActive(Integer.parseInt(properties.getProperty("maxactive")));
//设置最长的等待时间
source.setMaxWait(Integer.parseInt(properties.getProperty("maxwait")));
//设置最小空闲数
source.setMinIdle(Integer.parseInt(properties.getProperty("minidle")));
//初始化线程本地
local = new ThreadLocal<>();
} catch (IOException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
//获取Connection对象
Connection connection = source.getConnection();
//把Connection放进ThreadLocal里面
local.set(connection);
//返回Connection对象
return connection;
}
//关闭数据库连接
public static void closeConnection() {
//从线程中拿到Connection对象
Connection connection = local.get();
try {
if (connection != null) {
//恢复连接为自动提交
connection.setAutoCommit(true);
//这里不是真的把连接关了,只是将该连接归还给连接池
connection.close();
//既然连接已经归还给连接池了,ThreadLocal保存的Connction对象也已经没用了(是必须清理)
local.remove();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- 避免一些参数传递
在编写程序中也是一样的:日常中我们要去办理业务可能会有很多地方用到身份证,各类证件,每次我们都要掏出来很麻烦。
// 咨询时要用身份证,学生证,房产证等等....
public void consult(IdCard idCard,StudentCard studentCard,HourseCard hourseCard){
}
// 办理时还要用身份证,学生证,房产证等等....
public void manage(IdCard idCard,StudentCard studentCard,HourseCard hourseCard) {
}
而如果用了ThreadLocal的话,ThreadLocal就相当于一个机构,ThreadLocal机构做了记录你有那么多张证件。用到的时候就不用自己掏了,问机构拿就可以了。
在咨询时的时候就告诉机构:来,把我的身份证、房产证、学生证通通给他。在办理时又告诉机构:来,把我的身份证、房产证、学生证通通给他。
// 咨询时要用身份证,学生证,房产证等等....
public void consult(){
threadLocal.get();
}
// 办理时还要用身份证,学生证,房产证等等....
public void takePlane() {
threadLocal.get();
}
7.2 CurrentHashMap
- 原理
在JDK1.7中ConcurrentHashMap采用了数组+Segment+分段锁的方式实现。
1.Segment(分段锁)
ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
2.内部结构
ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。如下图是ConcurrentHashMap的内部结构图:
从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作。第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部。
3.该结构的优劣势
坏处
这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长
好处
写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上)。所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。