文章目录
01、乐观锁和悲观锁
悲观锁:
- 当线程去操作数据的时候,总认为别的线程会去修改数据,所以它每次拿数据的时候总会上锁,别的线程去拿数据的时候就会阻塞,比如
synchronized
乐观锁:
- 每次去
拿数据
的时候都认为别人不会修改,更新数据
的时候会判断是别人是否回去更新数据,通过版本来判断,如果数据被修改了就拒绝更新,比如CAS是乐观锁,但严格来说并不是锁,通过原子性来保证数据的同步,比如说数据库的乐观锁,通过版本控制来实现,CAS不会保证线程同步,乐观的认为在数据更新期间没有其他线程影响 - 注意:拿数据的时候不判断,更新数据的时候才会判断。
小结
- 悲观锁适合
写操作多
的场景 - 乐观锁适合
读操作多
的场景 - 乐观锁的吞吐量会比悲观锁大
02、公平锁和非公平锁
公平锁
- 指多个线程按照
申请锁的顺序
来获取锁 - 公平锁: 非常公平, 不能够插队,必须先来后到!
非公平锁:
- 获取锁的方式是
随机获取
的,保证不了每个线程都能拿到锁,也就是存在有线程饿死,一直拿不到锁,比如synchronized、ReentrantLock - 非公平锁:非常不公平,可以插队 (默认都是非公平锁)
小结
- 非公平锁性能高于公平锁,更能重复利用CPU的时间
- 参考
可重入锁
(ReentrantLock)的两个构造:一个可以传参,一个不可以传参,默认是非公平锁
public ReentrantLock() {
//非公平锁:默认的
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
//true为公平锁,false为非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
03、 可重入锁
概述
什么是可重入
可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁
可重入锁有
- synchronized
- ReentrantLock
synchronized关键字和ReentrantLock的区别:
synchronized不需要手动释放锁,而ReentrantLock需要手动释放锁
synchronized关键字
演示可重入锁
public class SynchronizedReentrantLock {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (this){
System.out.println("第1次获取锁,这个锁是:"+this);
int index=1;
while (true){
synchronized (this){
System.out.println("第"+(++index)+"次获取锁,这个锁是:"+this);
}
//重复获取十次锁
if(index==10){
break;
}
}
}
}
}).start();
}
}
- 运行结果:可以重复的获取同一把锁,不出现死锁
第1次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
第2次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
第3次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
第4次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
第5次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
第6次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
第7次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
第8次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
第9次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
第10次获取锁,这个锁是:com.aismall.somelocks.reentrantlock.SynchronizedReentrantLock$1@2f5aff2b
不需要手动释放锁
对于线程Thread_A:调用sendMsg方法的时候拿到一把锁,然后会自动拿到里面的call()方法的锁
等两个方法都执行完就释放锁,Thread_B拿到锁继续执行,不用手动释放锁
public class synchronizedLock {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
phone.sendMSG();
},"Thread_A").start();
new Thread(()->{
phone.sendMSG();
},"Thread_B").start();
}
}
class Phone{
public synchronized void sendMSG(){//这里有锁
System.out.println(Thread.currentThread().getName()+" :sendMSG.....");
call();//这里也有锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+" :call.....");
}
}
- 运行结果
Thread_A :sendMSG.....
Thread_A :call.....
Thread_B :sendMSG.....
Thread_B :call.....
ReentrantLock
演示可重入锁
//加了多少次,解了多少次
public class ReentrantLockDemo {
public static void main(String[] args) {
//创建一个可重入锁
ReentrantLock lock=new ReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
//加锁
lock.lock();
System.out.println("第1次获取锁,这个锁是:" + lock);
int index = 1;
while (true) {
try {
//加锁
lock.lock();
System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);
if (index == 10) {
break;
}
} finally {
//解锁
System.out.println("第" + (index) + "次解锁,这个锁是:" + lock);
lock.unlock();
}
}
} finally {
//解锁
lock.unlock();
System.out.println("第1" + "次解锁,这个锁是:" + lock);
}
}
}).start();
}
}
- 运行结果:
加了多少次,解了多少次
第1次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第2次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第2次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第3次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第3次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第4次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第4次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第5次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第5次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第6次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第6次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第7次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第7次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第8次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第8次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第9次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第9次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第10次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第10次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Locked by thread Thread-0]
第1次解锁,这个锁是:java.util.concurrent.locks.ReentrantLock@749c37a6[Unlocked]
ReentrantLock需要手动释放锁
注意:拿锁和解锁的顺序相反,最后哪的最先解锁
Thread_A先拿到锁A,有拿到锁B,然后拿到锁C,如果这个三个锁有一个不解锁,Thread_B都不能执行,因为Thread_A拿锁和解锁的数量不一样,造成死锁。
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone02 phone02=new Phone02();
new Thread(()->{
phone02.sendMSG();
},"Thread_A").start();
new Thread(()->{
phone02.sendMSG();
},"Thread_B").start();
}
}
class Phone02{
Lock lock= new ReentrantLock();
public void sendMSG(){
// 细节问题:lock.lock(); lock.unlock()必须配对,否则就会死在里面
lock.lock(); //锁A
lock.lock();//锁B
try {
System.out.println(Thread.currentThread().getName() + " sendMSG");
call(); // 调用:锁C
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁B
lock.unlock();//解锁A
}
}
public void call(){
lock.lock();//锁C
try {
System.out.println(Thread.currentThread().getName() + " call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁C
}
}
}
- 运行结果
Thread_A sendMSG
Thread_A call
Thread_B sendMSG
Thread_B call
04、自旋锁
自旋锁里面使用了CAS,java自带的元子类包中的很多类都是用CAS来实现自旋锁,例如java.util.concurrent.atomic. AtomicReference
类中的很多方法都利用CAS实现了自旋锁
- 例如这个方法:
getAndUpdate
public final V getAndUpdate(UnaryOperator<V> updateFunction) {
V prev, next;
do {
// 获取当前值,如果使用AtomicReference的空参构造,类型为传入的泛型,值为null,
//使用AtomicReference的有参构造,类型为传入的泛型,值为形参值。
prev = get();
// 根据我们重写的apply方法获取到一个新的值
next = updateFunction.apply(prev);
//调用compareAndSet方法,如果我们获取的prev值不等于构造的时候传入的值
//也就是在执行完构造方法之后,我们使用set方法更改了这个值,判断就会失败,
//就会进入循环体,不对值进行更新,返回原始值,直到这个值为构造时传入的值才会替换
//这就是自旋,一直等待,直到结果为true。
} while (!compareAndSet(prev, next));
return prev;
}
- 分析
- 这个方法的参数是一个UnaryOperator<V>类型的参数,如果不说一下这个参数什么意思,估计这段代码是看不懂的。
- UnaryOperator<V>是一个函数式接口,这个接口继承自Function<T, R>接口,UnaryOperator意思是一元运算符
- 因为Function<T, R>接口是传入T类型的参数,返回R类型的值,如果传入的参数类型和返回的值类型相同,
- 就可以简化使用UnaryOperator<V>这个一元运算符接口。
- Function<T, R>接口源码如下:我们只关注 R apply(T t);这个抽象方法
@FunctionalInterface
public interface Function<T, R> {
/**
* Function接口中的抽象方法:传入一个T类型的参数,结果返回一个R类型的值
*/
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
- UnaryOperator<V>接口源码如下:没什么好看的,我们可以看到Function<T, T>,也就是我们传入什么
- 类型的值,就返回什么类型的参数,我们使用这个接口要从写父类的抽象方法。
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
- 举例:
UnaryOperator<Integer> uop = x -> x + 1;
System.out.println(uop.apply(10)); // 11
我们也可以利用java自带的原子类来手动写一个自旋锁:
public class MySpinLock {
//使用 AtomicReference类,使用的是空参构造,所以初始化值为Thread类型的null值
AtomicReference<Thread> atomicReference = new AtomicReference<>();
// 加锁
public void myLock(){
Thread thread = Thread.currentThread();
// 自旋锁:
/**
* 解析:因为创建atomicReference实例使用的是空参构造
* 当创建对象的时候,默认为Thread类型的null
* 第一个线程进来,利用空参构造创建一个对象,得到默认值null
* 此时判断成立,将其替换为thread对象的地址,返回true,所以第一个
* 进来的线程不会进入while循环
* 第二个线程进来以后,由于默认值被替换为thread的地址不是null,所以进入while循环
* 并且一直等待,直到第一个线程调用解锁方法,把这个thread的值替换为null,第二个线程才能执行
* */
while (!atomicReference.compareAndSet(null,thread)){
System.out.println(Thread.currentThread().getName() + "==> 拿不到锁");
}
}
// 加锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==> 解锁");
atomicReference.compareAndSet(thread,null);
}
public static void main(String[] args) {
//创建一个锁
MySpinLock lock=new MySpinLock();
Thread thread01=new Thread(new Runnable() {
@Override
public void run() {
//加锁
lock.myLock();
System.out.println("thread01==> 拿到锁");
try {
//让线程睡1毫秒,看看线程thread02能不能拿到锁
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//解锁
lock.myUnLock();
}
},"thread01");
Thread thread02=new Thread(new Runnable() {
@Override
public void run() {
//加锁
lock.myLock();
System.out.println("thread01==> 拿到锁");
//解锁
lock.myUnLock();
}
},"thread02");
thread01.start();
thread02.start();
}
}
- 运行结果
thread01==> 拿到锁
thread02==> 拿不到锁
thread02==> 拿不到锁
thread02==> 拿不到锁
thread01==> 解锁
thread02==> 拿不到锁
thread01==> 拿到锁
thread02==> 解锁
05、死锁
死锁就是:
- A线程拿着A锁,想要B锁,等待获取B锁
- B线程拿着B锁,想要A锁,等待获取A锁
- 两个线程相互等待,造成死锁
举个例子吧:
public class DeadLock {
public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
//注意这个两个实例拿锁的顺序。。。。。
MyDeadLockThread A=new MyDeadLockThread(lockA,lockB);
MyDeadLockThread B=new MyDeadLockThread(lockB,lockA);
new Thread(A).start();
new Thread(B).start();
}
}
class MyDeadLockThread implements Runnable{
private String lockA;
private String lockB;
public MyDeadLockThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName() + ":拿到"+lockA+"=>想得到"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName() + ":拿到"+lockB+"=>想得到"+lockA);
}
}
}
}
- 运行结果
Thread-0:拿到lockA=>想得到lockB
Thread-1:拿到lockB=>想得到lockA
如何排查死锁
先使用命令:jps -l
查看到当前java进程的进程号
再使用命令:jstack 进程号
查看当前线程的堆栈信息
如何解开死锁
方法:避免死锁产生的条件就可以了
得到这个答案的时候让我们想起了当初学红黑树的时,有人问怎么构建一个红黑树,只要满足红黑树的条件就是红黑树,为什么那,因为专家就是这样定义红黑树的,,,,
有些答案深入追究是没有意义的!!!!!
06、独占锁和共享锁
独享锁
- 是指锁一次只能被一个线程持有。
- 也叫
X锁
/排它锁/写锁/独享锁:该锁每一次只能被一个线程所持有,加锁后任何线程试图再次加锁的线程会被阻塞,直到当前线程解锁。例子:如果 线程A 对 data1 加上排他锁后,则其他线程不能再对 data1 加任何类型的锁,获得独享锁的线程即能读数据又能修改数据!
共享锁
-
是指锁一次可以被多个线程持有。
-
也叫
S锁
/读锁,能查看数据,但无法修改和删除数据的一种锁,加锁后其它用户可以并发读取、查询数据,但不能修改,增加,删除数据,该锁可被多个线程所持有,用于资源数据共享! -
ReentrantLock和synchronized都是独享锁,ReadWriteLock的读锁是共享锁,写锁是独享锁。
07、互斥锁和读写锁
-
与独享锁和共享锁的概念差不多,是独享锁和共享锁的具体实现。
-
ReentrantLock和synchronized都是互斥锁,ReadWriteLock是读写锁