JUC的学习与使用(是对文章的补充)
sleep和wait的区别
(两者都需要捕获异常,这个异常不是重点)
1、每个对象都有一个锁来控制同步访问,Synchronized关键字可以和对象的锁交互,来实现同步方法或同步块。sleep()方法正在执行的线程主动让出CPU(然后CPU就可以去执行其他任务),在sleep指定时间后CPU再回到该线程继续往下执行(注意:sleep方法只让出了CPU,而并不会释放同步资源锁!!!);wait()方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。(注意:notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度);
2、sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用(否则执行的时候就会报错java.lang.IllegalMonitorStateException
)详情可以参考这个博客;
3、sleep()是线程线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态;
synchronized 和 lock
lock锁的几种机制下面举例详细说明这四种方法的使用:假如线程A和线程B使用同一个锁LOCK,此时线程A首先获取到锁LOCK.lock(),并且始终持有不释放。如果此时B要去获取锁,有四种方式:
①LOCK.lock(): 此方式会始终处于等待中,即使调用B.interrupt()也不能中断,除非线程A调用LOCK.unlock()释放锁。②LOCK.lockInterruptibly(): 此方式会等待,但当调用B.interrupt()会被中断等待,并抛出InterruptedException异常,否则会与lock()一样始终处于等待中,直到线程A释放锁。
③LOCK.tryLock(): 该处不会等待,获取不到锁并直接返回false,去执行下面的逻辑。
④LOCK.tryLock(10, TimeUnit.SECONDS):该处会在10秒时间内处于等待中,但当调用B.interrupt()会被中断等待,并抛出InterruptedException。10秒时间内如果线程A释放锁,会获取到锁并返回true,否则10秒过后会获取不到锁并返回false,去执行下面的逻辑。
两者对比
- Synchronized 内置的Java关键字, Lock 是一个Java类
- Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
- Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下
去; - Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以
自己设置); - Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!
生产者消费者问题
传统wait和notify带来的虚假唤醒问题
参考虚假唤醒,也就是说在while中,被唤醒的时候是从wait的地方开始,这样就会再做一次判断,而if就直接走下面的逻辑,不会二次判断。参考案例
集合类线程安全
线程安全类有两种方案,一种是使用Collections.synchronizedxxx()
方法,一种是使用比如CopyOnWriteList,这两种适合不同的场景,具体可以参考这两个
1.List集合
CopyOnWriteArrayList 实现原理
2.set集合
阻塞队列
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer() | put() | offer(,,) |
移除 | remove | poll() | tack() | poll(,,) |
检查队首元素 | element | peak() | - | - |
四组api中,其中第三组中,阻塞的时候 task,poll,remove是都可以取出数据然后再put的。
SynchronousQueue (狂的演示代码不能说明问题,因为每次都是线程等待)如果一直put的话,确实队列中只能存在一个值,只有被取出才可以再放。参考
线程池
- 1、2表示核心线程数
- 当候客区满了之后就是启动3、4、5的最大线程数
- 此时如果还是满的就是触发拒绝策略
- 当线程结束,3、4、5空闲时间到达设置的值后就会自动回收
JMM
小狂笔记中的store 和 write写反了。
单例模式
懒汉式中的dubble check 还要volitaile,防止在两次检测后进行new 的时候出现指令重排,导致对象还没有实例化完成。普通的单例模式可以通过反射被破解。
原子引用
使用包装类可能会出现问题,正常的业务逻辑应该是不同的java类。因为在进行compare的时候使用的是==
来判断的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。