常见锁bing
Posted 鸟随二月
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见锁bing相关的知识,希望对你有一定的参考价值。
目录标题
锁策略
乐观锁
它认为一般情况下不会出现问题,所以他在使用的时候不会加锁,只有在数据修改的时候,才会判断有没有锁竞争,如果没有就会直接修改数据,如果有则会返回失败信息给用户自行处理。
乐观锁的经典事项:CAS (Compare And Swap)对比并且替换。
CAS 实现:(v【内存中值】,A【预期的旧值】,B_(新值))
V==A 对比? true ->V->B; false->不能修改
CAS原理
CAS在 Java中是通过UnSafe 实现,Unsafe本地类和本地方法,它是C/C++实现的原生方法,通过调用操作系统Atomic::cmpxchg(原子指令)来实现的。
CAS在 Java中的应用: AtomicInteger/Atomic*
在多线程中实现i++、i–保证线程安全的方法:
1.加锁
2.ThreadLocal
3.AtomicInteger
private static AtomicInteger count =
new AtomicInteger(0);
private static final int MAXSIZE = 100000;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < MAXSIZE; i++) {
count.getAndIncrement(); // i++
// count.incrementAndGet(); // ++i
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < MAXSIZE; i++) {
count.getAndDecrement(); // i--
}
}
});
t2.start();
t1.join();
t2.join();
System.out.println("最终结果:" + count);
}
乐观锁(CSA)问题解决及方案
问题
ABA问题
代码模拟:
private static AtomicReference money =
new AtomicReference(100);
public static void main(String[] args) throws InterruptedException {
// 转账 -100
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100, 0);
System.out.println("线程1执行转账:" + result);
}
});
t1.start();
t1.join();
// 账户增加了 100
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(0, 100);
System.out.println("线程3转入100元:" + result);
}
});
t3.start();
t3.join();
// 转账 -100
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100, 0);
System.out.println("线程2执行转账:" + result);
}
});
t2.start();
}
解决方案
使用版本号,每次修改的时候判断预期的旧值和版本号,每次成功修改之后更改版本号,这样即使预期的值和Ⅳ值相等,但因为版本号不同,所以也不能进行修改,从而解决了ABA的问题。
解决方案:AtomicStampedReference
代码解决:
private static AtomicStampedReference money =
new AtomicStampedReference(100, 1);
public static void main(String[] args) throws InterruptedException {
// 转账 -100
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100, 0,
1, 2);
System.out.println("线程1执行转账:" + result);
}
});
t1.start();
t1.join();
// 账户增加了 100
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(0, 100,
2, 3);
System.out.println("线程3转入100元:" + result);
}
});
t3.start();
t3.join();
// 转账 -100
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
boolean result = money.compareAndSet(100, 0,
1, 2);
System.out.println("线程2执行转账:" + result);
}
});
t2.start();
}
AtomicStampedReference注意事项
解决方案
悲观锁
悲观锁:悲观锁任务只要执行多线程就会出现问题,所以在进入方法之后会直接加锁。
悲观锁的实现: synchronized
synchronized实现
在 Java层面:是将锁标识放在对象头(每个对象都有对象头)中;
在JVM 层面是监视器锁;
在操作系统是互斥锁mutex。
synchronized jdk1.7优化?
无锁->偏向锁->轻量级锁(自旋)->重量级锁。
公平锁和非公平锁
公平锁:获取锁的顺序按照线程访问的先后顺序获取。
公平锁非公平锁:不会按照线程的先后访问顺序按需获取。
Java实现:
new ReentrantLock(true)
独占锁和共享锁
独占锁:指的是这一把锁只能被一个线程拥有(例如:synchronized)。
共享锁:指的是一把锁可以被多个线程同时拥有。(例如:ReadWriterLock 读写锁,读锁就是共享。优势:将锁的粒度更加的细化,从而提高锁的性能。)
读写锁例子:
public static void main(String[] args) {
// 创建一个读写锁
ReentrantReadWriteLock readWriteLock =
new ReentrantReadWriteLock();
// 得到读锁
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
// 写锁
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, 10, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));
// 任务一:读锁演示
executor.execute(new Runnable() {
@Override
public void run() {
// 加锁
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() +
" 进入了读锁,时间:" + new Date());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
readLock.unlock();
}
}
});
// 任务二:读锁演示
executor.execute(new Runnable() {
@Override
public void run() {
// 加锁
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() +
" 进入了读锁,时间:" + new Date());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
readLock.unlock();
}
}
});
// 任务三:写锁
executor.execute(new Runnable() {
@Override
public void run() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() +
" 执行了写锁,时间:" + new Date());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
});
// 任务四:写锁
executor.execute(new Runnable() {
@Override
public void run() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() +
" 执行了写锁,时间:" + new Date());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
});
}
读锁是共享锁,写锁是独占锁,,读锁和写锁是非共享的。
可重入锁
一个线程在拥有了一把锁之后,可以重复的进入,就叫做可重入锁。
可重入锁的经典代表: synchronized
典型使用场景:
synchronized、ReentrantLock
自旋锁
相当于死循环,一直循环尝试获取锁。
使用场景:synchronized
偏向锁
在线程初次访问的时候,将线程的ID放到对象头偏向锁ID的字段中,每次线程访问时判断一下线程的id是否等于对象头中的偏向锁id,如果相等则表明这个线程拥有此锁就可以正常执行代码;否则表明线程不拥有此锁,只能通过自旋的方式尝试获取锁。
下一篇
以上是关于常见锁bing的主要内容,如果未能解决你的问题,请参考以下文章
JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch应用(等待多个线程准备完毕( 可以覆盖上次的打印内)等待多个远程调用结束)(代码片段