公平锁和非公平锁是什么?有什么区别?
- 并发包中ReentrantLock的创建可以指定构造函数的布尔类型来得到公平锁和非公平锁,默认是非公平锁。
ReentrantLock lock = new ReentrantLock(true);
- 两者的区别
- 公平锁:就是很公平,在并发环境中,每个线程在获取锁时会查看此锁维护的等待队列,如果为空,或者当前线程时等待队列的第一个,就占有锁。否则就会加入到等待队列中,以后会按照FIFO规则在队列中取到自己。
- 非公平锁:上来就直接占有锁,如果尝试失败,就再采用类似公平锁的那种方式。非公平锁的优点在于吞吐量比公平锁大。
Synchronized也是一种非公平锁。
可重入锁(也叫递归锁)
指同一线程在外层方法获取锁的时候,在内层方法会自动获取锁。
ReentrantLock和Synchronized是典型的可重入锁
可重入锁最大的作用是避免死锁。
自旋锁(spinLock)
是指尝试获取锁的线程不会阻塞,而是采用循环的方式获取锁
- 好处:减少上下文切换的消耗
- 坏处:循环会消耗CPU
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
- 手写自旋锁
public class SpinLockDemo {
//原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t comeIn");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void myUnLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + "\t myUnlock ");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnLock();
}, "t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.myLock();
spinLockDemo.myUnLock();
}, "t2").start();
}
}
独占锁(写锁),共享锁(读锁),互斥锁
- 独占锁是指锁一次只能被一个线程所持有,对ReentrantLock和Synchronized而言,都是独占锁。
- 共享锁是指该锁可能被多个线程所持有。
对ReentrantWriteReadLock其读锁是共享锁,其写锁是独占锁,写写,读写,写读过程都是互斥的。也就是多线程下读的时候不写,写的时候不读,读的时候其他线程可以读。
- 读写锁案例,手写一个缓存
class myCache {//资源类
private volatile Map<String, Object> map = new HashMap<>();
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在写入:key:" + key);
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public void get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 开始读,key:" + key);
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读完成,result:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
/**
* @author liujian
* @descripts 读写锁
* @create 2019-06-22 17:26
* <p>
* 写操作:独占+原子,中间的过程必须是完整的统一体,不允许被打断,被分割
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
myCache myCache = new myCache();
for (int i = 0; i < 50; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.put(String.valueOf(tempInt), String.valueOf(tempInt));
}, String.valueOf(i)).start();
}
for (int i = 0; i < 50; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.get(String.valueOf(tempInt));
}, String.valueOf(i)).start();
}
}
}
Synchronized和Lock有什么区别?用新的Lock有什么好处?举例说明
1. 原始构成
Synchronized是关键字,属于JVM层面。
- monitorenter 底层通过monitor对象完成,其实wait/notify等方法也依赖monitor对象,只有在代码块和方法中才可以使用wait/notify等方法。
- monitorexit
Lock是具体的类(java.concurrent.locks.lock),是API层面的锁。
2. 使用方法
- Synchronized不需要用户手动去释放锁,当Synchronized代码执行完后,系统会自动让线程释放对锁的占用。
- ReentrantLock 需要用户手动去释放锁,如果不释放,会造成死锁的现象。
3. 等待是否可中断
synchronized不可中断,只有抛出异常和运行完成两种情况。
ReentrantLock 可中断
- 设置超时方法中断:tryLock(Long timeout ,TimeUnit unit)
- 在代码块中声明方法:lock.lockInterruptibly(); 当线程调用Thread.interrupted()时,等待锁的过程中会立即响应中断。
4. 加锁是否公平
Synchronized 是非公平锁
ReentrantLock 默认非公平锁,构建函数传入boolean值,true为公平锁,false为非公平锁。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
5. 锁绑定多个条件
Synchronized没有
ReentrantLock用来分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么唤醒一个线程要么唤醒全部线程。
demo演示
题目:多线程之间按顺序调用,实现A->B->C三个线程启动,要求如下:
AA打印五次,BB打印十次,CC打印15次
紧接着 AA打印五次,BB打印十次,CC打印15次
...
来10轮
public class SynAndReentrantLockDemo {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
for (int i = 0; i <10 ; i++) {
new Thread(() -> shareResource.print(5), "AA").start();
new Thread(() -> shareResource.print(10), "BB").start();
new Thread(() -> shareResource.print(15), "CC").start();
}
}
}
class ShareResource {
private int num = 1;
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print(int j) {
lock.lock();
try {
if (j==5){
while (num != 1) {
c1.await();
}
for (int i = 0; i < j; i++) {
System.out.println(Thread.currentThread().getName() + "\t ");
}
num = 2;
c2.signal();
}else if (j==10){
while (num != 2) {
c2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t ");
}
c3.signal();
num = 3;
}else if (j==15){
while (num != 3) {
c3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t ");
}
c1.signal();
num = 1;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
网友评论