java并发学习
并发相关知识
并发相关知识
并发(Concurrency),并行(Parallelism)
并发:多项任务,交替执行
并行:多项任务,同时执行
同步(Synchronous),异步(Asynchronous)
描述的是针对某个调用 获取返回结果的方式:是同步等待,还是异步通知
同步:调用某项方法时,等待方法返回结果
异步:调用后马上返回,结果计算完后,通知调用者
阻塞(blocking),非阻塞(non-blocking)
描述的是多线程之间的相互影响
阻塞:一个线程占用了临界资源,其他线程必须等待这个线程释放资源
非阻塞:访问被其他线程占用的临界资源时, 不会阻塞等待,而立即返回
临界区
表示公共资源,多个线程访问或修改同一个资源
多线程竞争锁导致会问题
- 死锁:所有线程都不能动
- 饥饿锁:某个线程一直无法获取所需的资源
- 活锁:线程秉承谦让的原则,主动释放给他人使用,这样可能会导致资源在两个线程中跳动,而没有一个线程正常执行
并发级别
阻塞
一个线程会阻塞在 获取资源的步骤中,直到其他线程释放该资源,synchronized的锁为阻塞级别
无饥饿
如果获取锁是公平的,各个线程排队获取锁,则该锁是无饥饿的
无障碍
- 最弱的非阻塞调度
- 两个线程访问同一个临界区,都不会被对方所阻塞,一旦检测到某一方把数据改动了,则所有线程操作全部回滚
- 阻塞的控制方式是 悲观策略,假定两个线程之间很可能发生冲突,而非阻塞的调度是乐观的策略,认为多个线程不会发生冲突,或者概率不大,一旦发生冲突,就应该回滚
无锁
- 要求有一个线程可以在有限步内完成操作
- 当所有线程都能尝试对临界区访问,但只有一个线程能 进入临界区,其他的线程会不断尝试
无等待
- 要求所有线程必须在有限步内完成
- 典型的无等待结构是 RCU(read-copy-update),读无等待,更新时,先取得副本更新,然后适时写回
并行的两个重要定律
Amdahl定律
- 定义了串行系统并行化的加速比的计算公式,和理论上限
加 速 比 = 优 化 前 系 统 耗 时 / 优 化 后 系 统 耗 时 F : 为 系 统 串 行 比 例 T 1 : 为 一 个 处 理 器 的 耗 时 T n : 为 n 个 处 理 器 优 化 后 的 耗 时 T n = T 1 ( F + 1 n ∗ ( 1 − F ) ) 加 速 比 = T 1 T n = 1 F + 1 n ∗ ( 1 − F ) 加速比 = 优化前系统耗时 / 优化后系统耗时\\F:为系统串行比例\\T_1:为一个处理器的耗时\\T_n:为n个处理器优化后的耗时\\T_n = T_1(F+\frac{1}{n}*(1-F))\\加速比 = \frac{T_1}{T_n} = \frac{1}{F+\frac{1}{n}*(1-F)} 加速比=优化前系统耗时/优化后系统耗时F:为系统串行比例T1:为一个处理器的耗时Tn:为n个处理器优化后的耗时Tn=T1(F+n1∗(1−F))加速比=TnT1=F+n1∗(1−F)1
- 由公式可分析出
- CPU处理器数量趋近于无穷,那么加速比与系统串行率成反比
- 如果系统串行率为50%,则系统最大加速比为2
Gustafson定律
a : 串 行 时 间 , b : 并 行 时 间 , n 处 理 器 个 数 实 际 执 行 时 间 = a + b 总 执 行 时 间 = a + n ∗ b 加 速 比 = a + n ∗ b a + b 串 行 比 例 = F = a a + b 加 速 比 = a + n ∗ b a + b = a a + b + n ∗ ( a + b − a ) a + b = F + n ∗ ( 1 − F ) = n − F ∗ ( n − 1 ) a:串行时间,b:并行时间,n处理器个数\\ 实际执行时间 = a+b\\ 总执行时间 = a+n*b\\ 加速比= \frac{a+n*b}{a+b}\\ 串行比例 = F = \frac{a}{a+b}\\ 加速比 = \frac{a+n*b}{a+b} = \frac{a}{a+b}+\frac{n*(a+b-a)}{a+b}=F+n*(1-F)=n-F*(n-1) a:串行时间,b:并行时间,n处理器个数实际执行时间=a+b总执行时间=a+n∗b加速比=a+ba+n∗b串行比例=F=a+ba加速比=a+ba+n∗b=a+ba+a+bn∗(a+b−a)=F+n∗(1−F)=n−F∗(n−1)
-
两个定律的不同点
- Amdahl定律侧重于 当 总任务一定时, 当串行比例一定时,加速比是有上线的
- Gustafson定律侧重于 不管F的值有多高,只要 n足够大,有足够的时间和 工作量,就能达到某个加速比
java多线程并发原则
原子性 Atomicity
函数调用过程中 不可被其他线程打断,要么成功,要么失败
可见性 visibility
对某一线程修改了某一个共享变量,其他线程能够立刻知道
有序性 ordering
- 在程序编译时可能 有指令重排:通过指令重排 减少CPU流水线指令的停顿
- 线程重排原则
- 程序顺序原则:一个线程内保证语义的串行性,不保证并行性
- volatile变量的写 先发生于读
- 锁规则:解锁必然发生在 加锁前
- 传递性: a 先于b,b先于c,a必然先于c
- 线程start方法优先于它的每一个动作
- 所有操作先于 线程的终结
- 中断先于 被中断线程的代码
- 对象的构造函数执行,结束先于finalize方法
java并行程序基础
线程状态变更图
线程状态详细图
java线程设计状态图
线程基本操作
新建线程:start
Thread.start()
线程停止:stop
Thread.stop()
:线程放弃一切工作,马上退出,这样会导致很多隐患- 在线程内部设置停止标识:有线程自己决定在哪地方退出
线程中断:interrupt
-
java已经实现中断标识,用于线程自行决定在哪里退出
- 判断是否中断:Thread.isInterrupted()
- 判断是否中断并清除中断标记:static Thread.interrupted()
- 发出中断:Thread.interrupt()
-
Thread.sleep() 捕捉到中断之后,会清除中断标记
-
code
package com.weisanju; public class InterruptedTest { public static class AThread implements Runnable{ @Override public void run() { while(true){ if(Thread.currentThread().isInterrupted()){ System.out.println("已被中断"); break; } System.out.println(1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new AThread()); thread.start(); Thread.sleep(1000); thread.interrupt(); } }
线程等待:wait,notify
- 当线程执行到该行代码时, 且该对象为持有锁的对象,则线程进入等待状态,等待其他线程调用该对象的notify
- 这样就实现了多线程的简单通信
- code
package com.weisanju;
public class WaitNotifyTest {
private static Object obj = new Object();
public static class Athread implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("A acquire lock");
System.out.println("A通知B");
obj.notify();
System.out.println("通知完毕");
}
}
}
public static class Bthread implements Runnable{
@Override
public void run() {
synchronized (obj){
System.out.println("B acquire lock");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B被唤醒");
}
}
}
public static void main(String[] args) {
new Thread(new Athread()).start();
new Thread(new Bthread()).start();
}
}
挂起与继续执行:suspend,resume
- suspend 挂起当前线程,但不释放锁,与资源, 直到调用resume才恢复执行
- 不推荐使用,推荐使用wait,notify
等待线程结束:join and yield
- join : 线程join 实际调用 Thread.wait() 方法,
- 当线程结束时,会通知所有等待在线程对象的其他线程
- yield 让出CPU,重新与其他线程竞争CPU调度
volatile关键字
- 修饰变量
- 告知各个线程,取变量值时,从主内存中取,不要从副本取
线程组
package com.weisanju;
public class ThreadGroupTest {
public static class AThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("xjq");
Thread t1 = new Thread(threadGroup,new AThread(),"t1");
Thread t2 = new Thread(threadGroup,new AThread(),"t2");
t1.start();
t2.start();
threadGroup.list();
System.out.println(threadGroup.activeCount());
System.out.println(threadGroup.activeGroupCount());
}
}
守护线程
-
线程分为用户线程 ,守护线程
-
当用户线程执行完毕之后, 守护线程会自行退出
-
守护线程一般完成系统性服务,例如垃圾回收,JIT线程
-
代码
package com.weisanju; public class DeamonTest { public static class Athread implements Runnable{ @Override public void run() { while(true){ System.out.println(1); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Athread()); t.setDaemon(true); t.start(); Thread.sleep(2000); } }
线程优先级
Thread.MAX_PRIORITY
= 10Thread.NORM_PRIORITY
= 5Thread.MIN_PRIORITY = 1
jdk并发包
可重入锁
-
重入锁可以完全替代synchronized 关键字, jdk1.5 之间重入锁性能远远好于 synchronized 从1.6开始,jdk在synchronized 做了大量优化,使得两者性能差距并不大
-
特性
- 可重入性质: 一个线程可以连续两次获得锁, 但相应的得释放两次锁
ReentryantLock lock1 = new ReentryantLock(); lock1.lock() lock1.lock() lock1.unlock() lock1.unlock()
- 可中断性质
- 线程在尝试获取锁时,可被打断,并被打断后,释放相应的锁,让其他线程获取锁
- 案例 : 线程a, 线程b ,a先得到锁1,然后请求锁2,b先得到锁2,然后请求锁1
- 代码
package com.weisanju; import java.util.concurrent.locks.ReentrantLock; public class DeadLock { private static ReentrantLock lock1= new ReentrantLock(); private static ReentrantLock lock2= new ReentrantLock(); public static class ThreadTest implements Runnable{ private char name; public ThreadTest(char name) { this.name = name; } @Override public void run() { if(name == 'A'){ try { lock1.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(1000); lock2.lockInterruptibly(); System.out.println("A 得到锁了"); } catch (InterruptedException e) { e.printStackTrace(); }finally { if(lock1.isHeldByCurrentThread()){ lock1.unlock(); } if(lock2.isHeldByCurrentThread()){ lock2.unlock(); } } }else{ try { lock2.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(1000); lock1.lockInterruptibly(); System.out.println("B 得到锁了"); } catch (InterruptedException e) { e.printStackTrace(); }finally { if(lock1.isHeldByCurrentThread()){ lock1.unlock(); } if(lock2.isHeldByCurrentThread()){ lock2.unlock(); } } } } } public static void main(String[] args) { Thread ta = new Thread(new ThreadTest('A')); Thread tb = new Thread(new ThreadTest('B')); ta.start(); tb.start(); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } tb.interrupt(); } }
- 超时性质
tryLock()
:尝试获取锁,获取不成功则马上返回tryLock(long mili)
:尝试获取锁,并等待指定时间段
- 公平锁
- 锁的申请遵循 先到先到,支持排队
public ReentrantLock(boolean fair)
- 实现公平锁,系统需要维护一个有序队列,实现成本较高,性能太低
- 根据系统的调度,一个线程会倾向于再次获取已经持有的锁,这种锁分配是高效的
Conditional条件等待
- 与 synchronized 配合 wait,notify使用类似 , condition 配合与 Reentryant锁使用实现线程间通信
- 代码
package com.weisanju;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionalTest {
private static int flag =0;
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition= lock.newCondition();
private static class AThread implements Runnable{
@Override
public void run() {
lock.lock();
System.out.println("正等待条件发生");
try {
condition.await();
System.out.println(flag);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new AThread()).start();
flag = 666;
Thread.sleep(200);
lock.lock();
System.out.println("已经获取锁");
Thread.sleep(1000);
condition.signal();
lock.unlock();
}
}
信号量
- API
- 构造函数:
public Semaphore(int premits)
- 逻辑方法
- acquire|acquireUninterruptible()|tryAcquire()
- release
- 构造函数:
- 例子:省略
读写锁
-
读写操作互斥表
读 写 读 不阻塞 阻塞 写 阻塞 阻塞 -
API
ReentrantReadWriteLock
lock.readLock(),lock.writeLock()
倒计时
- API
- 构造函数:
public CountDownLatch(int count)
- 逻辑操作
- 计时器减1:
CountDownLatch.countDown()
- 等待计时器归0:``CountDownLatch.await();`
- 获取计数器:
CountDownLatch.getCount()
- 计时器减1:
- 构造函数:
CyclicBarrier循环栅栏
-
每当有
parties
个 到达wait
点时, 则执行barrierAction -
APi
-
构造函数:
public CyclicBarrier(int parties, Runnable barrierAction)
-
await
:等待 -
一个线程在等待时被打断, 则其他线程抛出
BrokenBarrierException
,该线程抛出:InterruptedException
-
code
package com.weisanju; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierTest { private static CyclicBarrier barrier = new CyclicBarrier(5,new BarrierRun(false)); public static class Solider implements Runnable{ private int i; public Solider(int i) { this.i = i; } @Override public void run() { try { barrier.await(); Thread.sleep(1000); System.out.println("士兵"+i+"完成任务"); barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } public static class BarrierRun implements Runnable{ private boolean flag ; public BarrierRun(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { System.out.println("任务完成"); }else{ System.out.println("集合完毕"); flag = true; } } } public static void main(String[] args) { int n = 5; for (int i = 0; i < n; i++) { System.out.println("士兵报数:"+i); new Thread(new Solider(i)).start(); } } }
-
线程阻塞工具类
-
API
LockSupport.unpack(Object)
,LockSupport.pack(Thread)
- 类似于 值为1的信号量 操作
- unpack操作发生在pack操作之前,unpack使得许可可用,pack消耗许可
- 不需要获取锁
- 为每一个线程都拥有一个许可证
- 被打断之后正常返回,可以通过
Thread.isInterrputed
unpack(Object)
:object 为日志打印时的对象
-
code
package com.weisanju; import java.util.concurrent.locks.LockSupport; public class LockSupportTest { public static class AThread implements Runnable{ @Override public void run() { LockSupport.park(); if(Thread.currentThread().isInterrupted()){ System.out.println("被打断了"); return; } System.out.println("正常运行"); } } public static void main(String[] args) { Thread t1 = new Thread(new AThread()); Thread t2 = new Thread(new AThread()); t1.start(); t2.start(); t1.interrupt(); LockSupport.unpark(t2); } }
线程池
-
API
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
-
参数解释
corePoolSize
:线程活跃数量maximumPoolSize
:最大线程数量keepAliveTime
:超过corePoolSize部分,空闲的线程存活时间TimeUnit
:时间单位BlockingQueue
:任务队列,提交但未运行ThreadFactory
:创建线程的工厂handler
:任务太多来不及处理的处理策略
-
blockingQueue分三种
- 直接提交的队列
- 新任务提交给线程池时,如果线程数量<
maximumPoolSize
,则直接创建,否则拒绝 SynchronousQueue
- 新任务提交给线程池时,如果线程数量<
- 有界任务队列
ArrayBlockingQueue
- 若已有线程数量 小于 corePoolSize ,则创建新的线程,直接运行
- 若大于corePoolSize ,则加入等待队列
- 若等待队列已满,且当前线程数量小于
maximumPoolSize
则新建线程 - 若当前线程数量已等于
maximumPoolSize
,则执行拒绝策略
- 无界任务队列
LinkedBlockingQueue
- 若已有线程数量 小于 corePoolSize ,则创建新的线程,直接运行
- 若大于corePoolSize ,则加入等待队列
- 无界队列会一直增长 直到内存耗尽
- 优先任务队列:特殊的无界队列
PriorityBlockingQueue
:
- 直接提交的队列
-
内置四种拒绝策略
AbortPolicy
: 直接抛出异常CallerRunsPolicy
:直接在调用者线程中运行当前被丢弃的任务DiscardOldestPolicy
:丢弃最老的请求,也就是即将被执行的,并尝试再次提交当前任务DiscardPolicy
:丢弃该任务
-
ThreadFactory
:自定义线程创建- ThreadFactory是一个接口,只有
Thread newThread(Runnable r)
接口
- ThreadFactory是一个接口,只有
-
扩展线程池
ThreadPoolExecutor
可扩展线程池- code
package com.weisanju; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolTest { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(20)) { protected void beforeExecute(Thread t, Runnable r) { System.out.println("线程" + t.getName() + "开始运行"); } protected void afterExecute(Runnable r, Throwable t) { System.out.println( r.toString()+ "结束运行"); } protected void terminated() { System.out.println("线程池退出"); } }; executor.execute(()->{ System.out.println("helloWorld"); }); executor.shutdown(); } }
-
线程池的大小 引申自
<< Java Concurrency in practice>>
N c p u = C p u 数 量 U c p u = c p u 的 使 用 率 , 0 < < U c p u < < 1 w c = 等 待 时 间 计 算 时 间 N t h r e a d s = N c p u ∗ U c p u ∗ ( 1 + w c ) Ncpu = Cpu数量\\ Ucpu = cpu的使用率, 0 << Ucpu << 1\\ \frac{w}{c} = \frac{等待时间}{计算时间}\\ Nthreads = Ncpu * Ucpu * (1+\frac{w}{c}) Ncpu=Cpu数量Ucpu=cpu的使用率,0<<Ucpu<<1cw=计算时间等待时间Nthreads=Ncpu∗Ucpu∗(1+cw)