多线程—锁

乐观锁

概念:它认为一般情况下不会发生并发冲突,所以只有在进行数据更新的时候,才会检测并发冲突,如果没有冲突,则直接修改,如果有冲突,则返回失败。

CAS机制

Compare and swap,是乐观锁的一种机制
当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。CAS是一个乐观锁。
组成部分
V 内存值
A旧值
B新值
如果内存中的值等于旧值,则没有并发冲突,直接将新值带入到内存值即可
如果内存中的值不等于旧值,则进行自旋,将自己的内存值带入到旧值,然后再次进行比较
在这里插入图片描述

乐观锁的实现

使用Atomic.*类
示例

public class AtomicThread {
    public  static AtomicInteger count=new AtomicInteger(0);
    public static  final int maxsize=1000;

    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();
                    }
            }
        }
        });
        t1.start();
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                {
                    for (int i = 0; i <maxsize; i++) {
                        count.getAndDecrement();
                    }
            }
        }
        });
        t2.start();
        t1.join();
        t2.join();
        System.out.println("结果:" + count);
    }
}

在这里插入图片描述结果为0,该线程是安全的。
但是

乐观锁存在ABA问题

正常的转账情形:

public class RightABA {
    private static AtomicReference money = new AtomicReference(100);

    public static void main(String[] args) {
        //(-100)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean res = money.compareAndSet(100,0);
                System.out.println("第一次转账:" + res);
            }
        });
        t1.start();
        //(-100)
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean res = money.compareAndSet(100,0);
                System.out.println("第二次转账:" + res);
            }
        });
        t2.start();

    }

}

在这里插入图片描述
如果此时,有一个线程3,在线程1真正执行CAS时,把余额改了回来,接下来在执行线程1的CAS,就能成功转两次了

public class ABAtest2 {
    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 res = money.compareAndSet(100,0);
                System.out.println("第一次转账:" + res);
            }
        });
        t1.start();
        t1.join();
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean res = money.compareAndSet(0,100);
                System.out.println("加入100:" + res);
            }
        });
        t3.start();
t3.join();
        //(-100)
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean res = money.compareAndSet(100,0);
                System.out.println("第二次转账:" + res);
            }
        });
        t2.start();
t2.join();
    }
}

在这里插入图片描述

如何解决ABA问题

引入“修改时间”
在CAS修改的时候,同时记录下数据的“上次修改时间”
CAS比较的时候不光要比较 旧的值与内存值是否一致,还需要比较“上次修改的时间”是否一致
如果都一致,执行修改。否则不修改
通过修改时间,就可以确定当前这个值是一直没变,还是发生了改变

public class solveABA {
    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 res = money.compareAndSet(100,0,1,2);
                System.out.println("第一次转账:" + res);
            }
        });
        t1.start();
        t1.join();
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean res = money.compareAndSet(0,100,2,3);
                System.out.println("加入100:" + res);
            }
        });
        t3.start();
        t3.join();
        //(-100)
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean res = money.compareAndSet(100,0,1,3);
                System.out.println("第二次转账:" + res);
            }
        });
        t2.start();
        t2.join();
    }
}

在这里插入图片描述

可重入锁和不可重入锁

可重入锁:当一个线程获取到一个锁之后,可以重复的进入
Java里只要以Reentrant开头命名的锁都是可重入锁
代表:synchronized,Lock

public class Lock {
    //创建锁
    private static Object lock = new Object();
    public static void main(String[] args) {
        synchronized (lock) {
            System.out.println("第一次进入锁");
            synchronized (lock) {
                System.out.println("第二次进入锁");
            }
        }
    }
}

读写锁

多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁就产生了。
把锁分成了两部分,写锁,读锁。
特点:读锁是可以被多个线程同时拥有的,而写锁则只能被一个线程拥有。
读写锁的优势:锁的粒度更加小,性能也更高
读写锁把读操作和写操作分开了,普通的锁提供了加锁和解锁操作,而读写锁提供了三个操作,读加锁,写加锁,解锁,能够更好去降低锁冲突的概率。
读加锁和读加锁,不需要互斥
读加锁和写加锁,需要互斥;涉及到锁竞争,读写锁带来互斥,保证线程安全。
写加锁和写加锁,需要互斥;涉及到锁竞争,读写锁带来互斥,保证线程安全。
读写锁主要适用于少写多读的场景,锁冲突的概率就会少很多。
Java中 synchronized 不是读写锁,但是Java中 ReentrantLock类,提供了这样一个读写功能。

死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
死锁产生的四个必要条件:
互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用;
不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放;
请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有;
循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

死锁情形

第一种:一个线程一把锁
第二种:两个线程两把锁
环路等待: 线程1 先获取到锁A,尝试获取锁B;线程2先获取到锁B,尝试获取锁A。
线程2想要获取A,先得等线程1释放A;线程1想要获得B,先得等线程2释放B。


线程1
synchronized(locker1){
	synchronized(locker2){
	
	}	
}
线程2
synchronized(locker2){
	synchronized(locker1){
	
	}	
}

第三种:N个线程M个锁
产生死锁最核心的原因就是 循环等待
解决办法:
1.不要在加锁代码中尝试获取其他锁;
意味着代码同一时刻,只获取一把锁 ,就不会构成循环等待
2.按照一定的顺序来加锁;
假设有3把锁,锁1,锁2,锁3,如果确实是要在获取一把锁的过程中也要获取到其他锁,就约定一个固定的顺序,必须要先获取锁1,在获取锁2,再去获取锁3;

线程1
synchronized(locker1){
	synchronized(locker2){
		synchronized(locker3){

		}
	}	
}
线程2
synchronized(locker1){
	synchronized(locker2){
		synchronized(locker3){
	
		}
	}	
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值