锁入门(面试基础)

参考链接:

https://www.cnblogs.com/takumicx/p/9338983.html
乐观锁与悲观锁https://blog.csdn.net/qq_34337272/article/details/81072874
CAS:https://www.jianshu.com/p/ae25eb3cfb5d
https://www.cnblogs.com/qjjazry/p/6581568.html

1. 乐观锁(CAS)

https://blog.csdn.net/q5706503/article/details/84558343

  1. 概述:CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法可以对该操作重新计算。

  1. 缺点:
  • 循环时间长开销很大:
    我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

  • 只能保证一个共享变量的原子操作:
    当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

1.1 ABA问题

  1. 首先T1线程想把栈顶改为B
    在这里插入图片描述
  2. 此时T2线程在T1之前,取出A,B;然后推入D,C,A。(此时B游离)
    在这里插入图片描述
  3. 这时T1把栈顶改为B,但是B.Next()为空,这时候我们就丢失了C,D。
    在这里插入图片描述

2. 偏向锁、轻量锁、重量锁

1.锁总是被第一个占用(很少竞争)他的线程拥有,这个线程就是锁的偏向线程。
使用流程:在第一次获得锁的时候,记录下线程id,当下次使用,对比线程id,一致的话,就还是偏向锁,就不需要使用cas去更新对象头。如果不一致,则变成轻量级锁

  1. 轻量级锁参看上面的自旋锁,由于cas(对比与竞争)算法,每次都会循环竞争,每次进入退出同步块都需要CAS更新对象头

  2. 当上面的自旋次数太多,则会变成重量级锁,把一直循环的线程变为阻塞状态,等待唤醒。

3. synchronized

  1. 为什么要使用synchronized

在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。

  1. synchronized的三种应用方式

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

3.1 synchronized缺陷和对应的lock

  1. 首先说一下synchronized的缺陷
    当线程被synchronized修饰的话,除非1.当前线程执行完;2.线程被异常中端,否则不会释放当前资源的锁。

再举一个比较常见的例子:对于一个文件,我们两个线程同时去读,应该没有关系的(不会改变文件内容),但是加了synchronized只能一个读的时候,另外一个堵塞。
另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。

Java1.6为Synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低

4.Lock

https://blog.csdn.net/takemetofly/article/details/48086069
https://blog.csdn.net/qq_34337272/article/details/79714196
https://blog.csdn.net/qq_34337272/article/details/79714196

  1. 注意lock时一个类,不是java的关键字
  2. 注意加锁后,要主动释放锁,不然会死锁

4.1 lock()方法,普通的获得锁

//新建锁对象(还不是获得锁),注意Lock时接口,不能直接用他来赋值,然后不要在try中新建lock。
Lock lock = ... 
try{
//给当前代码块加锁。
lock.lock();
}catch(){
}finally{
//释放锁
lock.unlock();
}

4.2 trylock(),如果能获得锁,就获取,不然直接释放锁

Lock lock = ... 
//尝试给当前代码块加锁。
if(lock.trylock()){
	try{
	
	}catch(){
	
	}finally{
	//释放锁
	lock.unlock();
}else{
//无法获得锁就做其他事
}

4.3 lockInterruptibly(),当获取锁的时候,获取失败了,可以被interrupt方法中断。

1, 注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为本身在前面的文章中讲过单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
2, 因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。
3. 而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

方法名称描述
void lock()获得锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁。
void lockInterruptibly()获取锁,如果可用并立即返回。如果锁不可用,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,和lock()方法不同的是在锁的获取中可以中断当前线程(相应中断)。
boolean tryLock()只有在调用时才可以获得锁。如果可用,则获取锁定,并立即返回值为true;如果锁不可用,则此方法将立即返回值为false 。
boolean tryLock(long time, TimeUnit unit)超时获取锁,当前线程在一下三种情况下会返回: 1. 当前线程在超时时间内获得了锁;2.当前线程在超时时间内被中断;3.超时时间结束,返回false.
void unlock()释放锁。
Condition newCondition()获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。

5. lock锁的实现类

5.1. ReentrantLock类(可重入锁)

构造方法:

ReentrantLock() 创建一个 ReentrantLock的实例。
ReentrantLock(boolean fair) 创建一个特定锁类型(公平锁/非公平锁)的ReentrantLock的实例

  1. 普通的锁

5.2 ReentrantReadWriteLock(读写锁)

(只要有写就会互斥)

5.3 lock于Condition的合作

方法名称描述
void await()相当于Object类的wait方法
boolean await(long time, TimeUnit unit)相当于Object类的wait(long timeout)方法
signal()notify()
signalAll()notifyAll()
  1. 创建一个condition调用
public class UseSingleConditionWaitNotify {

    public static void main(String[] args) throws InterruptedException {

        MyService service = new MyService();

        ThreadA a = new ThreadA(service);
        a.start();

        Thread.sleep(3000);

        service.signal();

    }

    static public class MyService {

        private Lock lock = new ReentrantLock();
        public Condition condition = lock.newCondition();

        public void await() {
            lock.lock();
            try {
                System.out.println(" await时间为" + System.currentTimeMillis());
                condition.await();
                System.out.println("这是condition.await()方法之后的语句,condition.signal()方法之后我才被执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void signal() throws InterruptedException {
            lock.lock();
            try {               
                System.out.println("signal时间为" + System.currentTimeMillis());
                condition.signal();
                Thread.sleep(3000);
                System.out.println("这是condition.signal()方法之后的语句");
            } finally {
                lock.unlock();
            }
        }
    }

    static public class ThreadA extends Thread {

        private MyService service;

        public ThreadA(MyService service) {
            super();
            this.service = service;
        }

        @Override
        public void run() {
            service.await();
        }
    }
}
  1. 新建多个condition来实现顺序等待/通知机制
public class UseMoreConditionWaitNotify {
    public static void main(String[] args) throws InterruptedException {

        MyserviceMoreCondition service = new MyserviceMoreCondition();

        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();

        Thread.sleep(3000);

        service.signalAll_A();

    }
    static public class ThreadA extends Thread {

        private MyserviceMoreCondition service;

        public ThreadA(MyserviceMoreCondition service) {
            super();
            this.service = service;
        }

        @Override
        public void run() {
            service.awaitA();
        }
    }
    static public class ThreadB extends Thread {

        private MyserviceMoreCondition service;

        public ThreadB(MyserviceMoreCondition service) {
            super();
            this.service = service;
        }

        @Override
        public void run() {
            service.awaitB();
        }
    }

}

死锁的四个必要条件

互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

循环等待条件: 若干进程间形成首尾相接循环等待资源的关系

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

死锁的避免和预防

1. 死锁避免(类似于银行家算法):

系统每次进行动态检查,查看是否为安全序列,是的话就可以,不是则不给予分配

2. 死锁预防(针对于死锁产生的四个条件)

  1. 破坏“不可剥夺”:进程在申请新的资源,得不到满足的时候,进程会暂时释放当前占用资源(也就是进程所占有的资源可以被占有)
    缺点:可能会造成上一步的任务失效
  2. 破坏“占有与等待":两种解决方案
  • 静态解决方案:进程每次只能一次性获取自己进程所需要的全部资源,不然不允许占有资源
    缺点:会很浪费系统资源,导致部分进程可能仅在程序初期或者末期有机会投入运行
  • 动态:进程在占有新的资源时,必须释放当前资源
  1. 破坏”循环等待“条件:给系统资源设置一个编号,规定每个进程按编号顺序请求资源,同类资源一次性申请完。
    缺点:限制了新设备的加入,而且有一些进程可能不会按照规定顺序执行,造成资源浪费。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值