声明: 1. 本文为我的个人复习总结, 并非那种从零基础开始普及知识 内容详细全面, 言辞官方的文章
2. 由于是个人总结, 所以用最精简的话语来写文章
3. 若有错误不当之处, 请指出
一. 前置基础:
-
IO 操作不占用 cpu, 只是我们一般拷贝文件使用的是【阻塞 IO】, 这时相当于线程虽然不用 cpu, 但需要一
直等待 IO 结束, 没能充分利用线程。
所以才有后面的【非阻塞 IO】和【异步 IO】优化
-
synchronized同步 是互斥的
-
锁的本质是 大家对一个共享资源进行互斥访问
-
栈是线程私有, 堆是多线程共享的;
但不意为着线程栈里的局部变量就被别的线程访问到, 需要先进行逃逸分析;
比如将局部变量传递给方法内部的开辟的线程, 那样此变量就逃逸出此方法了, 便会有线程安全问题
-
进程 是对运行时程序的封装,
操作系统
进行资源调度
与资源分配
的基本单位线程 是
cpu
进行调度
的基本单位管程 是监视器, 即synchronized锁
举例: 一个正在运行的Java程序是一个JVM进程, 其中既有用户线程, 又有GC垃圾回收线程(守护线程); 可见一个进程里可以有多个线程
-
无论是主线程还是自定义线程,只有有存活JVM就不会结束;
而当仅有守护线程时,JVM便会结束
-
锁粒度不能太大, 但又不能太小; 既要考虑并发能力, 又要考虑安全性
查看进程线程的方法
栈帧:
每个栈由多个栈帧(Frame)组成, 对应着每次方法调用时所占用的内存
每个线程只能有一个活动栈帧, 对应着当前正在执行的那个方法
栈帧内包括: 局部变量, 返回地址, 操作数栈, args参数, 锁记录
多线程是为了充分利用cpu资源, 不会因为单个的阻塞而阻塞整个业务流程。
多线程串行 相比于 单线程 的优势:
多线程串行, 即便有个线程阻塞了, 也不会影响别的线程;
而且当未对临界区进行修改发生竞态条件时, 是不需要加锁的, 这时优势就又更明显了
同步/异步, 是从 多个线程间的 业务逻辑关系或回调通知机制 的角度来看的
串行/并行, 是从 多个线程间的 实际执行过程 的角度来看的
异步业务: 商品详情功能业务 & 评论功能业务
同步业务: 登录功能业务 & 购物车功能业务
一般而言, 由于业务是异步的关系, 导致了并行执行; 由于业务是同步的关系, 导致了串行执行
同步:
- 两个事物保持某种相对关系
- syncronized互斥
- 消息通知机制上 没有回调
- 业务同步, 有先后执行顺序
join( ): 插队, 即等此线程结束
join(3000L) 最多让插队3s, 超时main就不等t1了,t1走t1的, main继续往下走
// t1花费1s, t2花费2s, 总花费并不是3s, 而是2s; t1和t2可以看作并行运行
public static main(String[] args){
Thread t1 = new Thread(( ) -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(( ) -> {
sleep(2);
r2 = 20;
});
t1.start( );
t2.start( );
t1.join( ); //是main线程调用,故插队到main前
t2.join( );
}
yield( ): 礼让一下
LockSupport.park( ) LockSupport.unpark( );
可以先调用unpark, 那么下一次park就会无效; 注意先调用的unpark只能累加一次, 不能叠加
操作系统层面的线程状态:
JVM层面的线程状态:
Java层面的 RUNNABLE 状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【阻塞状态】(BIO导致的线程阻塞, 在Java里无法区分, 仍然认为是可运行)
Java层面的阻塞状态有: BLOCKED(没抢到锁), WAITING(wait), TIMED_WAITING(带时间的wait)
线程通信的几种方式:
- 共享内存(全局变量)
- synchronized锁
- wait( )/notify( )
- join( )
- 并发工具: CountDownLatch, CyclicBarrier
- 管道
- 信号量
二. synchronized底层:
synchronized底层是Monitor, 翻译为监视器, 重量级锁或管程
Monitor底层结构:
- Owner 抢到锁的线程
- EntryList 没抢到锁而阻塞的线程
- WaitSet wait的线程, 相当于一间休息室; 所以wait必须放在synchronized中执行
synchronized的字节码:
第4行的astore是存放锁的引用, 因为后面需要找到锁进行释放
monitorenter后有两个monitorexit, 第一个monitorexit是正常执行时的释放锁, 第二个monitorexit是发生异常时进行释放锁
锁的相关信息(线程id, 是否为偏向锁, 锁重入的次数)放在对象头的MarkWord里存储
JDK6中引入了偏向锁&轻量级锁, 有了锁升级的概念
1. 偏向锁:
轻量级锁在没有竞争时(就自己一个线程), 每次重入仍然需要执行CAS操作
而偏向锁: 只有第一次使用CAS时将线程ID设置到对象的Mark Word头, 之后发现这个线程ID是自己的 就表示没有竞争, 不用重新CAS
偏向锁可以重偏向到其他线程
撤销偏向锁:
即升级为轻量级锁, 需要STW
如果撤销偏向到达某个阈值, 那么整个类的所有对象都会变为不可偏向的
2. 轻量级锁:
多个线程错开访问, 没有竞争时 可以使用轻量级锁来优化
3. 重量级锁:
多线程间有竞争
synchronized锁的状态只能升级不能降级
其他锁的概念:
自旋锁: 抢锁失败后先别着急阻塞, 而是多循环尝试几次; 因为线程切换 阻塞状态和可运行状态 是需要代价的
是使用CAS来实现的
锁膨胀: 即锁升级, 偏向锁->轻量级锁->重量级锁
锁粗化: 连续好几个synchronized同一个锁对象, 且中间未夹杂其他代码, 则被粗化成一个synchronized代码块
锁消除: 逃逸分析后发现局部变量没有逃逸出此方法, 便会对此局部变量加的锁消除掉
死锁: 两个线程互相把此对方的锁, 且都不愿意先释放自己的锁, 造成一种互相等待的僵局
活锁: 两个线程执行相反的操作, 比如A线程对初始值为10的cnt进行++操作, B线程对cnt进行–操作;
两个线程一直在运行, cnt的值却很久都达到不了0, 结束不了运行
公平锁: 所有线程雨露均沾, 防止有些线程太强了干完了所有活, 导致其他线程饥饿着没活干;
但这个会降低并发, 所以不建议使用;
new ReentrantLock(true); 是公平锁, 默认为false
可重入锁: 在锁代码块内部重复获得相同的锁, 又称递归锁
三. 多线程安全问题:
多线程安全问题: 多个线程对共享的一块内存进行写操作, 即临界区发生了竞态条件
一段代码块内如果存在对共享资源的多线程读写操作, 称这段代码块为临界区
多个线程在临界区内执行, 由于代码的执行序列不同而导致结果无法预测, 称之为发生了竞态条件
虚假唤醒: if 里线程被wait打断后再醒来时可能数据已经变了, 而却不重复判断
1. 原子性:
-
i++的原子性问题: 最初cnt=0, A线程++得到1,还没的及写入却被阻塞了,B又对0–成-1写入, 这时A继续写为1; 导致了0 ++ --=1
-
多个操作间的原子性问题: 线程切换导致if判断不准确。比如10000元去买东西, if(10000>8000), if(10000>4000), if(10000>2000), 因为线程被打断来不及扣减, 最终10000-8000-4000-2000<0 负债了
解决方案:
-
阻塞式的解决方案:synchronized, AQS
-
非阻塞式的解决方案:原子类(即CAS+volatile)
2. 可见性:
JIT编译将热点代码缓存到工作内存里, 那样工作内存
里存储的就不是主内存
里的最新数据
解决方案:
- volatile, 保证可见性和有序性
- 有synchronized即可禁止JIT编译将此部分代码缓存到工作内存, 无论锁谁都行
- 全局禁用JIT(不推荐)
3. 有序性:
cpu调整指令顺序
比如一会加载a, 一会加载b, 循环1000次; cpu发现单线程下不影响结果的前提下, 肯定优先先加载1000次a, 再去加载1000次b
int a = 0;
boolean flag = false;
public void writer( ){
a = 1;
flag = true;
}
public void reader( ){
if(flag){
int i = a + a;
}
}
解决方案:
加锁, synchronized或AQS
综上:
-
volatile能解决 可见性 & 有序性 问题
-
乐观锁CAS 能解决 原子性 & 可见性 & 有序性 问题
-
悲观锁synchronized或AQS 能解决原子性 & 可见性 & 有序性问题
-
用private和final修饰方法,可以避免一些问题, 因为如果不然的话, 在子类中, 创建了新线程让新的线程共享list局部变量, 就坑爹了
-
as-if-serial规则保证 单线程下的指令重排, 不会影响执行结果
happens-before规则保证 多线程下的指令重排, 不会影响执行结果
happens-before规则:
规定了哪些写操作对其它线程的读操作可见, 它是可见性与有序性的一套规则总结
一个操作happens-before另一个操作,表示第一个的操作结果对第二个操作可见
volatile底层原理:
是更轻量级的锁
可见性: 被volatile修饰的变量执行写操作时: 立即将工作内存中对此变量的数据刷写到主内存中, 并使得其他线程工作内存中对此变量的缓存失效
有序性: volatile修饰的共享变量在被读写时会 加入内存屏障,阻止其他读写操作越过屏障
volatile 读写加入的屏障只能防止同一线程内的指令重排
JMM内存模型对volatile的排序规则为:
-
当第一个操作是
volatile
读时,无论第二个操作是什么都不能进行重排序 -
当第二个操作是
volatile
写时,无论第一个操作是什么都不能进行重排序 -
当第一个操作是
volatile
写,第二个操作为volatile
读时,不能进行重排序
JMM(Java内存模型)
JMM 定义了一套保障数据的可见性、有序性、原子性的规则;
架构:
- 工作内存 线程私有
- 主内存 线程公有的
内存屏障:
Load是读, Store是写
-
LoadLoad
Load1, LoadLoad, Load2; 即先Load1, 再Load2
-
StoreStore
Store1, StoreStore, Store2; 即先Store1, 再Store2
-
LoadStore
Load1, LoadLoad, Store2; 即先Load1, 再Store2
-
StoreLoad
Store1, StoreStore, Load2; 即先Store1, 再Load2
转账案例:
public class ExerciseTransfer {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(( ) -> {
for (int i = 0; i < 1000; i++) {
a.transfer(b, randomAmount( ));
}
}, "t1");
Thread t2 = new Thread(( ) -> {
for (int i = 0; i < 1000; i++) {
b.transfer(a, randomAmount( ));
}
}, "t2");
t1.start( );
t2.start( );
t1.join( );
t2.join( );
log.debug("total:{}", (a.getMoney( ) + b.getMoney( )));
}
static Random random = new Random( );
public static int randomAmount( ) {
return random.nextInt(100) + 1;
}
}
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney( ) {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void transfer(Account target, int amount) {
// 注意, 不能锁this, 因为上文一个线程 a.transfer(b, randomAmount( ));, 另一个线程 b.transfer(a, randomAmount( ));
// 当this是a时, 线程二的b根本不理会this(a)这把锁
synchronized (Account.class) {
if (this.money > amount) {
this.setMoney(this.getMoney( ) - amount);
target.setMoney(target.getMoney( ) + amount);
}
}
}
}
四. CAS与原子类:
CAS:
CAS(compare and set/swap)是乐观锁, 本质并不是锁, 而是while循环进行版本号判断
悲观锁 VS 乐观锁:
悲观锁靠阻塞实现, 乐观锁使用循环代替阻塞
悲观锁: 适用于锁竞争激烈的场景(读少写多)
优点: 线程抢不到锁 阻塞时会让出cpu
缺点: 线程切换状态, 有一定的开销
乐观锁: 适用于锁竞争不激烈(读多写少), 且cpu核数较多的场景
优点: 线程不用切换状态
缺点: 线程就一直执行while占用cpu
- CAS 是基于乐观锁的思想: 不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗
- Synchronized 是基于悲观锁的思想: 防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会
原子类:
底层使用CAS+volatile实现无锁并发
JUC包中的4种原子类:
-
基本类型
更新基本类型
AtomicInteger
:整形原子类AtomicLong
:长整型原子类AtomicBoolean
:布尔型原子类
-
数组类型
更新数组里的某个元素
AtomicIntegerArray
:整形数组原子类AtomicLongArray
:长整形数组原子类AtomicReferenceArray
:引用类型数组原子类
-
引用类型
- AtomicReference
- AtomicStampedReference:
- AtomicMarkableReference:
-
更新类的字段
需要配合volatile使用, volatile修饰该字段
- AtomicIntegerFieldUpdater: 整型字段的更新器
- AtomicLongFieldUpdater: 长整型字段的更新器。
- AtomicReferenceFieldUpdater:引用类型的更新器
ABA问题:
值A变成了B后又变回了A, 这两个A认不认为它是同一个版本呢?
AtomicReference和AtomicMarkableReference认为是(即存在ABA问题), AtomicStampedReference认为不是
解决: 使用版本号
LongAdder:
LongAdder的累加效率要比AtomicLong高:
LongAdder 分为 base 和 cells 两部分:
- 没有并发竞争的时候或者是 cells 数组正在初始化的时候,会使用 CAS 来累加到base
- 有并发竞争时,会初始化 cells 数组,数组有多少个 cell,就允许有多少线程并行修改; 最后将数组中每个 cell 累加,再加上 base 就是最终的值
相当于降低了锁的粒度, 只锁cells累加单元, 而不是锁住所有
UnSafe:
这个类很底层, 不建议我们直接使用
像CAS和NIO的直接内存, 都是通过底层调用UnSafe来实现的
五. 常见线程安全类:
String
Integer
StringBuffer
Random
Vector
Hashtable
java.util.concurrent 包下的类
这里的安全指的是 多个线程操作同一个实例时不会带来线程安全问题
String和Integer都是不可变类, 所谓的修改不过是new一个新的对象返回
对于Hashtable, 单个API是原子性的,但是组合起来不能保证其原子性,
如下图: 原本是想着table为null才put值, 结果因为API组合起来不具备原子性, 从而put了两次
六. ReentrantLock:
特点:
-
支持多个Condition条件变量, 即具有多个WaitSet休息室, 可以只叫醒某一个休息室的线程
await signal signalall
-
可以使用tryLock
-
得手动释放锁
-
可重入
-
可以设置超时时间
-
可以设置为公平锁
-
可中断
ReentrantReadWriteLock:
读锁是共享锁, 写锁是排他锁, 写锁可以降级为读锁
七. Callable, Future:
Callable:
// 有返回值; 且抛出了异常,子类不必非得try catch了
class MyThread implements Callable<Integer> {
@Override
public Integer call( ) throws Exception {
return 200;
}
}
Future:
-
在主线程中需要执行比较耗时的操作时但又不想阻塞主线程时,可以把这些作业交给Future在后台完成,
当主线程将来需要时,就可以通过Future的get方法获得结果
-
将Future视为保存结果的对象, 它暂时不保存结果, 但将来会保存 (即Callable返回的时候), 为了获得结果,得需要Callable
-
类的关系: class FutureTask<V> implements RunnableFuture<V>
interface RunnableFuture<V> extends Runnable, Future<V>
相关API:
CompletableFuture:
是Future的实现类, 用作编排多个线程间的关系, 有异步回调功能
八. Fork/Join:
Fork:把一个复杂任务进行拆分, 递归拆分
Join:把拆分任务的结果进行合并
案例: 计算 1+2+3…+1000, 拆分成子任务, 每个子任务做100个数字的累加
class TaskExample extends RecursiveTask<Long> {
private int start;
private int end;
private long sum;
public TaskExample(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute( ) {
System.out.println("任务" + start + "=========" + end + "累加开始");
//大于 100 个数相加切分,小于直接加
if (end - start <= 100) {
for (int i = start; i <= end; i++) {
//累加
sum += i;
}
} else {
//切分为 2 块
int middle = start + 100;
//递归调用,切分为 2 个小任务
TaskExample taskExample1 = new TaskExample(start, middle);
TaskExample taskExample2 = new TaskExample(middle + 1, end);
//执行:异步
taskExample1.fork( );
taskExample2.fork( );
//同步阻塞获取执行结果
sum = taskExample1.join( ) + taskExample2.join( );
}
//加完返回
return sum;
}
}
public class ForkJoinPoolDemo {
public static void main(String[] args) {
//定义任务
TaskExample taskExample = new TaskExample(1, 1000);
//定义执行对象
ForkJoinPool forkJoinPool = new ForkJoinPool( );
//加入任务执行
ForkJoinTask<Long> result = forkJoinPool.submit(taskExample);
//输出结果
try {
System.out.println(result.get( ));
} catch (Exception e) {
e.printStackTrace( );
} finally {
forkJoinPool.shutdown( );
}
}
}
九. 线程池:
为什么使用线程池:
-
复用Thread对象, 防止频繁地创建和销毁, 从而降低资源消耗 并提高响应速度
-
提高线程的可管理性, 控制其上限
七大参数:
- corePoolSize 核心线程数目
- maximumPoolSize 最大线程数目
- keepAliveTime 救急线程的生存时间
- unit 时间单位 救急线程的生存时间单位
- workQueue 等待队列
- threadFactory 线程工厂, 可以定制线程对象的创建如 设置线程名字、是否是守护线程等
- handler 拒绝策略
- 抛异常 AbortPolicy
- 返回给调用者去执行 CallerRunsPolicy
- 丢弃此任务 DiscardPolicy
- 丢弃最早排队的任务 DiscardOldestPolicy
救急线程优先处理新来的任务, 而不是呆在等待队列里的任务
创建线程池的几种方法:
-
使用Executors工厂方法创建
这里的无限大是说为int最大值的意思
-
自己通过
new ThreadPoolExecutor
方法传参创建
线程的销毁:
救急线程在空闲一定时间后自动销毁
核心线程可调用shutdown等方法销毁
-
shutdown
-
不接收新任务
-
已提交任务会执行完
-
不会阻塞调用此方法的线程
-
线程池状态变为 SHUTDOWN
-
-
shutdownNow
-
不接收新任务
-
已提交但还在队列里没开始执行的任务会被取消
-
用interrupt打断正在执行的任务
-
线程池状态变为 STOP
-
十. AQS常用的锁:
AQS: AbstractQueuedSynchronizer 抽象队列同步器
ReentrantLock, FutureTask, CountDownLatch, CyclicBarrier, Semaphore 都是基于AQS实现的
AQS可以独占, 也可以共享
AQS内部有一些CAS代码
AQS的原理:
AQS维护了一个共享资源, 然后使用双向队列来保证线程排队获取资源
用 volatile state 字段 来表示同步状态, 通过CAS进行setState
队列里只有头节点才是锁的持有者,尾指针指向队列的最后一个等待线程节点
AQS的资源共享方式有哪些?
通过修改state字段来实现
- Exclusive:独占, 只有一个线程可以执行, 例如ReentrantLock
- Share:共享, 多个线程可同时执行, 如ReentrantReadWriteLock
AQS使用了模板方法设计模式
自定义AQS:
-
继承AbstractQueuedSynchronizer
-
重写方法:
-
isHeldExclusively( ):判断该线程是否正在独占资源
-
tryAcquire(int):独占方式, 尝试获取资源
-
tryRelease(int):独占方式, 尝试释放资源
-
tryAcquireShared(int):共享方式, 尝试获取资源; 负数表示失败, 0表示无可用资源, 正数有可用资源
-
tryReleaseShared(int):共享方式, 尝试释放资源
-
CountDownLatch(递减计数器):
当计数器的值变为0时,await方法阻塞的线程会被唤醒继续执行
案例: 6个同学陆续离开教室后值班同学才可以关门
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception {
//定义一个数值为 6 的计数器
CountDownLatch countDownLatch = new CountDownLatch(6);
//创建 6 个同学(线程)
for (int i = 1; i <= 6; i++) {
new Thread(( ) -> {
try {
if (Thread.currentThread( ).getName( ).equals("同学 6")) {
Thread.sleep(2000);
}
System.out.println(Thread.currentThread( ).getName( ) + "离开了");
//计数器减一,不会阻塞
countDownLatch.countDown( );
} catch (Exception e) {
e.printStackTrace( );
}
}, "同学" + i).start( );
}
countDownLatch.await( ); // 当计数器为0时,才被唤醒
System.out.println("全部离开了,现在的计数器为" + countDownLatch.getCount( ));
}
}
CyclicBarrier(循环栅栏):
每执行cyclicBarrier.await( )一次, 障碍数就会加一, 如果达到了目标障碍数, 才会执行
构造器: public CyclicBarrier(int parties, Runnable runable)
案例: 经历81难才能取得真经
public class CyclicBarrierDemo {
private final static int NUMBER = 81;
public static void main(String[] args) throws Exception{
//定义循环栅栏, 定义的run方法(lambda表达式), await( )81次才会被执行
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, ( ) -> System.out.println("集齐" + NUMBER + "颗龙珠,现在召唤神龙!!!!!!!!!"));
//定义 7 个线程分别去收集龙珠
for (int i = 1; i <= NUMBER; i++) {
try {
System.out.println("经历第" + i+"难");
Thread.sleep(500L)
cyclicBarrier.await( );
} catch (Exception e) {
e.printStackTrace( );
}
}
}
}
Semaphore(信号灯):
定义最大许可证, 每个信号量最多只同时有一个许可证。
使用 acquire 方法获得许可证,release 方法释放许可证
案例: 6辆汽车(线程)抢3个停车位
public class SemaphoreDemo {
public static void main(String[] args) throws Exception {
//定义 3 个停车位
Semaphore semaphore = new Semaphore(3);
//模拟 6 辆汽车停车
for (int i = 1; i <= 6; i++) {
Thread.sleep(100);
//停车
new Thread(( ) -> {
try {
System.out.println(Thread.currentThread( ).getName( ) + "找车位 ing");
semaphore.acquire( );
System.out.println(Thread.currentThread( ).getName( ) + "汽车停车成功!");
Thread.sleep(10000L); //停车中
} catch (Exception e) {
e.printStackTrace( );
} finally {
System.out.println(Thread.currentThread( ).getName( ) + "溜了溜了");
semaphore.release( );
}
}, "汽车" + i).start( );
}
}
}
邮戳锁StampedLock:
是对读写锁的优化, 乐观读, 类似于CAS, 戳即版本号
-
乐观读:
long stamp = lock.tryOptimisticRead( );
// 验戳
if(!lock.validate(stamp)){
// 锁升级
}
-
读锁:
long stamp = lock.readLock( );
lock.unlockRead(stamp);
-
写锁:
long stamp = lock.writeLock( );
lock.unlockWrite(stamp);
十一. 大对比:
wait VS sleep:
wait VS park:
打断 sleep, wait, join, park 的线程:
synchronized VS volatile:
synchronized VS Lock:
Runable VS Thread:
Runable run( ) VS Callable call( ):
execute VS submit:
死锁 VS 活锁:
十二. 代码案例:
线程八锁问题:
-
import java.util.concurrent.TimeUnit; //先byPlane, 后byCar 其实同时锁住了两个方法, 但没有时间暂停, 看不出来 public class Sync_1 { public static void main(String[] args) throws InterruptedException { Vehicle_A vehicle1 = new Vehicle_A( ); new Thread(( ) -> vehicle1.byPlane( )).start( ); TimeUnit.MILLISECONDS.sleep(500L); //只是为了排除线程先后启动的误差, 使认为线程1比线程2先启动 new Thread(( ) -> vehicle1.byCar( )).start( ); } } class Vehicle_A { public synchronized void byPlane( ) { System.out.println("------byPlane------"); } public synchronized void byCar( ) { System.out.println("------byCar------"); } }
-
import java.util.concurrent.TimeUnit; //3s后 先byPlane, 后byCar 说明同时锁住了两个方法 public class Sync_2 { public static void main(String[] args) throws InterruptedException { Vehicle_B vehicle1 = new Vehicle_B( ); new Thread(( ) -> vehicle1.byPlane( )).start( ); TimeUnit.MILLISECONDS.sleep(500L); //只是为了排除线程先后启动的误差 new Thread(( ) -> vehicle1.byCar( )).start( ); } } class Vehicle_B { public synchronized void byPlane( ) { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException ignored) { } System.out.println("------byPlane------"); } public synchronized void byCar( ) { System.out.println("------byCar------"); } }
-
import java.util.concurrent.TimeUnit; //先hello, 3s后再byPlane 说明没有同时锁住两个方法 public class Sync_3 { public static void main(String[] args) throws InterruptedException { Vehicle_C vehicle1 = new Vehicle_C( ); new Thread(( ) -> vehicle1.byPlane( )).start( ); TimeUnit.MILLISECONDS.sleep(500L); //只是为了排除线程先后启动的误差 new Thread(( ) -> vehicle1.hello( )).start( ); } } class Vehicle_C { public synchronized void byPlane( ) { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException ignored) { } System.out.println("------byPlane------"); } public synchronized void byCar( ) { System.out.println("------byCar------"); } public void hello( ) { System.out.println("------hello------"); } }
-
import java.util.concurrent.TimeUnit; //先byCar, 3s后再byPlane 说明没有同时锁住两个方法 public class Sync_4 { public static void main(String[] args) throws InterruptedException { Vehicle_D vehicle1 = new Vehicle_D( ); Vehicle_D vehicle2 = new Vehicle_D( ); new Thread(( ) -> vehicle1.byPlane( )).start( ); TimeUnit.MILLISECONDS.sleep(500L); //只是为了排除线程先后启动的误差 new Thread(( ) -> vehicle2.byCar( )).start( ); } } class Vehicle_D { public synchronized void byPlane( ) { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException ignored) { } System.out.println("------byPlane------"); } public synchronized void byCar( ) { System.out.println("------byCar------"); } }
-
import java.util.concurrent.TimeUnit; //3s后 先byPlane, 后byCar 说明同时锁住了两个方法 public class Sync_5 { public static void main(String[] args) throws InterruptedException { Vehicle_E vehicle1 = new Vehicle_E( ); new Thread(( ) -> vehicle1.byPlane( )).start( ); TimeUnit.MILLISECONDS.sleep(500L); //只是为了排除线程先后启动的误差 new Thread(( ) -> vehicle1.byCar( )).start( ); } } class Vehicle_E { public synchronized static void byPlane( ) { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException ignored) { } System.out.println("------byPlane------"); } public synchronized static void byCar( ) { System.out.println("------byCar------"); } }
-
import java.util.concurrent.TimeUnit; //3s后 先byPlane, 后byCar 说明同时锁住了两个方法 public class Sync_6 { public static void main(String[] args) throws InterruptedException { Vehicle_F vehicle1 = new Vehicle_F( ); Vehicle_F vehicle2 = new Vehicle_F( ); new Thread(( ) -> vehicle1.byPlane( )).start( ); TimeUnit.MILLISECONDS.sleep(500L); //只是为了排除线程先后启动的误差 new Thread(( ) -> vehicle2.byCar( )).start( ); } } class Vehicle_F { public synchronized static void byPlane( ) { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException ignored) { } System.out.println("------byPlane------"); } public synchronized static void byCar( ) { System.out.println("------byCar------"); } }
-
import java.util.concurrent.TimeUnit; //先byCar, 3s后再byPlane 说明没有同时锁住两个方法 public class Sync_7 { public static void main(String[] args) throws InterruptedException { Vehicle_G vehicle1 = new Vehicle_G( ); new Thread(( ) -> vehicle1.byPlane( )).start( ); TimeUnit.MILLISECONDS.sleep(500L); //只是为了排除线程先后启动的误差 new Thread(( ) -> vehicle1.byCar( )).start( ); } } class Vehicle_G { public synchronized static void byPlane( ) { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException ignored) { } System.out.println("------byPlane------"); } public static void byCar( ) { System.out.println("------byCar------"); } }
-
import java.util.concurrent.TimeUnit;//先byCar, 3s后再byPlane 说明没有同时锁住两个方法public class Sync_8 { public static void main(String[] args) throws InterruptedException { Vehicle_H vehicle1 = new Vehicle_H( ); Vehicle_H vehicle2 = new Vehicle_H( ); new Thread(( ) -> vehicle1.byPlane( )).start( ); TimeUnit.MILLISECONDS.sleep(500L); //只是为了排除线程先后启动的误差 new Thread(( ) -> vehicle2.byCar( )).start( ); }}class Vehicle_H { public synchronized static void byPlane( ) { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException ignored) { } System.out.println("------byPlane------"); } public synchronized void byCar( ) { System.out.println("------byCar------"); }}
总结:
-
锁非静态方法为锁this,锁静态方法为锁对应的类.class
-
锁住静态时不影响非静态方法, 但影响其他的静态
-
锁住非静态时不影响静态, 但影响其他的非静态
多个线程交替打印问题:
-
三个线程T1、T2、T3交替打印A、B、C, 打印10次, 如ABCABCABCABC…
-
三个线程T1、T2、T3交替打印A(5次)、B(10次)、C(15次), 打印10次, 如AAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCCAAAAABBBBBBBBBBCCCCCCCCCCCCCCC…
-
三个线程交替打印1-100, T1打印1, T2打印2, T3打印3, T1打印4…
-
两个线程交替打印字母和数字, T1打印A1, T2打印B2, T1打印C3…一直到Z26, 如A1B2C3…
synchronized & wait & notify版解法:
-
public class Demo01 { private static Object lock = new Object( ); private static int num; public static void printABC(String show, int targetNum) throws InterruptedException { for (int i = 0; i < 10; i++) { synchronized (lock) { while (num % 3 != targetNum) { lock.wait( ); } num++; System.out.print(show); lock.notifyAll( ); } } } } class Test01 { public static void main(String[] args) { new Thread(( ) -> { try { Demo01.printABC("A", 0); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo01.printABC("B", 1); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo01.printABC("C", 2); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); } }
-
public class Demo02 { private static Object lock = new Object( ); private static int num; public static void printABC(String show, int targetNum) throws InterruptedException { for (int i = 0; i < 10; i++) { synchronized (lock) { while (num % 3 != targetNum) { lock.wait( ); } num++; for (int j = 0; j < 5 * (targetNum + 1); j++) { System.out.print(show); } lock.notifyAll( ); } } } } class Test02 { public static void main(String[] args) { new Thread(( ) -> { try { Demo02.printABC("A", 0); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo02.printABC("B", 1); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo02.printABC("C", 2); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); } }
-
public class Demo03 { private static Object lock = new Object( ); private static int num; public static void print_1_100(String threadName, int targetNum) throws InterruptedException { // num [0,99] num++后再打印num while (true) { synchronized (lock) { while (num % 3 != targetNum) { lock.wait( ); } num++; if (num > 100) { // 不叫醒其他线程的话,会导致其他线程都在wait 程序不会结束 lock.notifyAll( ); break; } System.out.println(threadName + ":" + num); lock.notifyAll( ); } } } } class Test03 { public static void main(String[] args) { new Thread(( ) -> { try { Demo03.print_1_100("T1", 0); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo03.print_1_100("T2", 1); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo03.print_1_100("T3", 2); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); } }
-
public class Demo04 { public static Object lock = new Object( ); public static int num; public static void print(int order) throws InterruptedException { while (true) { synchronized (lock) { while (num % 2 != order) { lock.wait( ); } num++; if (num > 26) { lock.notifyAll( ); break; } System.out.print((char) (num - 1 + 'A')); System.out.print(num); lock.notifyAll( ); } } } } class Test04 { public static void main(String[] args) { new Thread(( ) -> { try { Demo04.print(0); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo04.print(1); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); } }
Lock & Condition版解法:
-
public class Demo01 { private static int num; public static void printABC(String show, int targetNum, Lock lock, Condition cur, Condition next) throws InterruptedException { for (int i = 0; i < 10; i++) { lock.lock( ); try { while (num % 3 != targetNum) { cur.await( ); } num++; System.out.print(show); next.signal( ); } finally { lock.unlock( ); } } } } class Test01 { private static ReentrantLock lock = new ReentrantLock( ); private static Condition condition1 = lock.newCondition( ); private static Condition condition2 = lock.newCondition( ); private static Condition condition3 = lock.newCondition( ); public static void main(String[] args) { new Thread(( ) -> { try { Demo01.printABC("A", 0, lock, condition1, condition2); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo01.printABC("B", 1, lock, condition2, condition3); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo01.printABC("C", 2, lock, condition3, condition1); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); } }
-
public class Demo02 { private static int num; public static void printABC(String show, int targetNum, Lock lock, Condition cur, Condition next) throws InterruptedException { for (int i = 0; i < 10; i++) { lock.lock( ); try { while (num % 3 != targetNum) { cur.await( ); } num++; for (int j = 0; j < 5 * (targetNum + 1); j++) { System.out.print(show); } next.signal( ); } finally { lock.unlock( ); } } } } class Test02 { private static ReentrantLock lock = new ReentrantLock( ); private static Condition condition1 = lock.newCondition( ); private static Condition condition2 = lock.newCondition( ); private static Condition condition3 = lock.newCondition( ); public static void main(String[] args) { new Thread(( ) -> { try { Demo02.printABC("A", 0, lock, condition1, condition2); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo02.printABC("B", 1, lock, condition2, condition3); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo02.printABC("C", 2, lock, condition3, condition1); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); } }
-
public class Demo03 { private static int num; public static void print_1_100(String threadName, int targetNum, Lock lock, Condition cur, Condition next) throws InterruptedException { // num [0,99] num++后再打印num while (true) { lock.lock( ); try { while (num % 3 != targetNum) { cur.await( ); } num++; if (num > 100) { // 不叫醒其他线程的话,会导致其他线程都在wait 程序不会结束 next.signal( ); break; } System.out.println(threadName + ":" + num); next.signal( ); } finally { lock.unlock( ); } } } } class Test03 { private static ReentrantLock lock = new ReentrantLock( ); private static Condition condition1 = lock.newCondition( ); private static Condition condition2 = lock.newCondition( ); private static Condition condition3 = lock.newCondition( ); public static void main(String[] args) { new Thread(( ) -> { try { Demo03.print_1_100("A", 0, lock, condition1, condition2); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo03.print_1_100("B", 1, lock, condition2, condition3); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo03.print_1_100("C", 2, lock, condition3, condition1); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); } }
-
public class Demo04 { public static int num; public static void print(int order, Lock lock, Condition cur, Condition next) throws InterruptedException { while (true) { lock.lock( ); try { while (num % 2 != order) { cur.await( ); } num++; if (num > 26) { next.signal( ); break; } System.out.print((char) (num - 1 + 'A')); System.out.print(num); next.signal( ); } finally { lock.unlock( ); } } } } class Test04 { private static ReentrantLock lock = new ReentrantLock( ); private static Condition condition1 = lock.newCondition( ); private static Condition condition2 = lock.newCondition( ); public static void main(String[] args) { new Thread(( ) -> { try { Demo04.print(0, lock, condition1, condition2); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); new Thread(( ) -> { try { Demo04.print(1, lock, condition2, condition1); } catch (InterruptedException e) { e.printStackTrace( ); } }).start( ); } }