展开
拓展 关闭
订阅号推广码
GitHub
视频
公告栏 关闭

锁(一):简介

  • 锁的分类
1、自旋锁: 线程状态及上下文切换消耗系统资源,当访问共享资源的时间短,频繁上下文切换不值得。jvm实现,使线程在没获得锁的时候,不被挂起,转而执行空循环,循环几次之后,如果还没能获得锁,则被挂起
2、阻塞锁:阻塞锁改变了线程的运行状态,让线程进入阻塞状态进行等待,当获得相应的信号(唤醒或者时间)时,才可以进入线程的准备就绪状态,转为就绪状态的所有线程,通过竞争,进入运行状态
3、重入锁:支持线程再次进入的锁,就跟我们有房间钥匙,可以多次进入房间类似
4、读写锁: 两把锁,读锁跟写锁,写写互斥、读写互斥、读读共享
5、互斥锁: 上厕所,进门之后就把门关了,不让其他人进来
6、悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
7、乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
8、公平锁:大家都老老实实排队,对大家而言都很公平
9、非公平锁:一部分人排着队,但是新来的可能插队
10、偏向锁:偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁
11、独占锁:独占锁模式下,每次只能有一个线程能持有锁
12、共享锁:允许多个线程同时获取锁,并发访问共享资源
  • 代码案例
# 在线程不安全案例中使用锁
public class UnSafeThread {

    private static int num = 0;

    // 用来线程计数器-1的,也就是新增线程运行完之后,都调用此方法将计数器变成0
    private static CountDownLatch countDownLatch = new CountDownLatch(10);

    // new1个锁
    private static Lock lock = new ReentrantLock();

    // 每次调用对num进行++操作
    public static void inCreate() {
        lock.lock();    // 执行操作前,先上锁
        num++;
        lock.unlock();  // 执行操作后,再放开或
    }

    public static void test() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    inCreate();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //每个线程执行完成之后,调用countdownLatch
                countDownLatch.countDown();
            }).start();
        }

        while (true) {
            if (countDownLatch.getCount() == 0) {
                System.out.println(num);
                break;
            }
        }
    }

}

# 控制台执行结果:执行正确
1000
  • lock与synchronized的区别
1、lock 获取锁与释放锁的过程,都需要程序员手动的控制 Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就 是CAS操作 
2、synchronized托管给jvm执行,原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁
  • 查看lock实现,ctrl + H

  • 查看lock实现,方式2

  • 选中全部

  • 查看方法

lock()  获取锁
unlock()  释放锁
lockInterruptibly()  某个线程获取锁的过程中,允许使用interrupt方法中断该线程,这是该线程就不用获取锁了
tryLock()  尝试获取锁,能成功则成功
tryLock(long time)  在指定时间内,尝试获取锁
  • 自定义锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {

    // 锁的状态
    private boolean isHoldLock = false;

    // 线程
    private Thread holdLockThread = null;

    // 重入次数
    private int reentryCount = 0;

    /**
     * 同一时刻,能且仅能有一个线程获取到锁,
     * 其他线程,只能等待该线程释放锁之后才能获取到锁
     */
    @Override
    public synchronized void lock() {
        // 判断,这个锁是否已经被持有,且这个线程不是持有锁的线程
        if (isHoldLock && Thread.currentThread() != holdLockThread) {
            try {
                wait();     // 这个锁已经被其他线程获取,则等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 否则持有锁的线程就是当前线程
        holdLockThread = Thread.currentThread();
        isHoldLock = true;
        reentryCount++;
    }

    /**
     * 释放锁
     */
    @Override
    public synchronized void unlock() {
        //判断当前线程是否是持有锁的线程,是,重入次数减去1,不是就不做处理
        if (Thread.currentThread() == holdLockThread) {
            reentryCount--;
            if (reentryCount == 0) {
                notify();   // 随机唤醒1个等待的线程
                isHoldLock = false;     // 修改锁的状态
            }
        }
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

}

# 测试1
public class UnSafeThread {

    private static int num = 0;

    private static CountDownLatch countDownLatch = new CountDownLatch(10);

    // 使用自定义的锁
    private static Lock lock = new MyLock();

    // 每次调用对num进行++操作
    public static void inCreate() {
        lock.lock();
        num++;
        lock.unlock();
    }

    public static void test() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    inCreate();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //每个线程执行完成之后,调用countdownLatch
                countDownLatch.countDown();
            }).start();
        }

        while (true) {
            if (countDownLatch.getCount() == 0) {
                System.out.println(num);
                break;
            }
        }
    }
}

# 测试2
public class ReentryDemo {

    public Lock lock = new MyLock();

    public void methodA() {
        lock.lock();
        System.out.println("进入方法A");
        methodB();
        lock.unlock();
    }

    public void methodB() {
        lock.lock();
        System.out.println("进入方法B");
        lock.unlock();
    }

    public static void main(String[] args) {
        ReentryDemo reentryDemo = new ReentryDemo();
        reentryDemo.methodA();
    }

}
  • AbstractQueuedSynchronizer浅析
1、AbstractQueuedSynchronizer 为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。 此类的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。 子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。 假定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用getState()、setState(int) 和 compareAndSetState(int, int) 方法来操作以原子方式更新的 int 值。 应该将子类定义为
非公共内部帮助器类,可用它们来实现其封闭类的同步属性。类 AbstractQueuedSynchronizer 没有实现任何同步接口。而是定义了诸如 acquireInterruptibly(int) 之类的一些方法,在适当的时候可以通过具体的锁和相关同步器来调用它们,以实现其公共方法
2、此类支持默认的独占 模式和共享 模式之一,或者二者都支持。处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功。此类并不“了解”这些不同,除了机械地意识到当在共享模式下成功获取某一锁时,下一个等待线程(如果存在)也必须确定自己是否可以成功获取该锁。处于不同模式下的等待线程可以共享相同的 FIFO 队列。通常,实现子类只支持其中一种模式,但两种模式都可以在(例如)ReadWriteLock 中发挥作用。只支持独占模式或者只支持共享模式的子类不必定义支持未使用模式的方法
3、此类通过支持独占模式的子类定义了一个嵌套的 AbstractQueuedSynchronizer.ConditionObject 类,可以将这个类用作 Condition 实现。isHeldExclusively() 方法将报告同步对于当前线程是否是独占的;使用当前 getState() 值调用release(int) 方法则可以完全释放此对象;如果给定保存的状态值,那么 acquire(int) 方法可以将此对象最终恢复为它以前获取的状态。没有别的 AbstractQueuedSynchronizer 方法创建这样的条件,因此,如果无法满足此约束,则不要使用它。AbstractQueuedSynchronizer.ConditionObject 的行为当然取决于其同步器实现的语义
4、此类为内部队列提供了检查、检测和监视方法,还为 condition 对象提供了类似方法。可以根据需要使用用于其同步机制的 AbstractQueuedSynchronizer 将这些方法导出到类中
5、此类的序列化只存储维护状态的基础原子整数,因此已序列化的对象拥有空的线程队列。需要可序列化的典型子类将定义一个 readObject 方法,该方法在反序列化时将此对象恢复到某个已知初始状态

tryAcquire(int)  尝试获取
tryRelease(int)  尝试释放
tryAcquireShared(int)   尝试获取共享
tryReleaseShared(int)   尝试释放共享
isHeldExclusively() 
    Acquire: 
        while (!tryAcquire(arg)) {   // 在while循环中尝试获取,获取不到时则进入等待队列
            enqueue thread if it is not already queued; 
            possibly block current thread; 
        } 
    Release:
        if ((arg)) 
            unblock the first queued thread;
posted @ 2022-05-13 18:46  DogLeftover  阅读(72)  评论(0编辑  收藏  举报