zoukankan      html  css  js  c++  java
  • 锁的初体验

    synchronized

    synchronized 关键字声明的方法同一时间只能被一个线程访问。
    synchronized 锁的是对象而不是代码,锁方法锁的是this,锁static方法锁的是class。
    锁定方法和非锁定方法是可以同步执行的。

    public synchronized void doSomething(){
    .......
    }
    

    synchronized原理,锁升级
    偏向锁:如果一个线程获得了锁,那么锁就进入偏向模式,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。
    自旋锁:偏向锁有多个线程(相对较多)竞争的时候就会升级为自旋锁。自旋形象的比喻就是在锁的边上等,不在锁的队列中。
    重量级锁:在自旋多次后还是拿不到锁救护i升级为重量级锁。重量级锁需要到操作系统申请资源,效率比较低。

    锁是通过monitorenter和monitorexit来实现的,这两个字节码代表的是啥意思:

    可以在下面参考的网页中了解monitorenter和monitorexit的作用,每个对象都有一个monitor监视器,调用monitorenter就是尝试获取这个对象,成功获取到了就将值+1,离开就将值减1。如果是线程重入,在将值+1,说明monitor对象是支持可重入的。

    系统锁:加锁代码执行时间长、线程数多
    自旋锁:加锁代码执行时间段、线程数少

    注意事项
    控制加锁代码的范围,避免资源浪费。
    synchronized(object)不能用在string常量、Integer、Long等类型上

    volatile

    一个 volatile 对象引用可能是 null。

    volatile作用
    线程的可见性

    • MESI:CPU高速缓存一致性协议
    • volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中(堆内存)重新读取该成员变量的值。当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

    禁止指令重排序(CPU)

    • CPU为了提高效率,会将指令并发执行。volatile关键字会在指令之间加上读屏障和写屏障,保证指令线性执行。
    • 懒汉式单利模式用到了volition关键字
    public class MyRunnable implements Runnable
    {
        private volatile boolean active;
        public void run()
        {
            active = true;
            while (active) // 第一行
            {
                // 代码
            }
        }
        public void stop()
        {
            active = false; // 第二行
        }
    }
    

    通常情况下,在一个线程调用 run() 方法(在 Runnable 开启的线程),在另一个线程调用 stop() 方法。
    如果 第一行 中缓冲区的 active 值被使用,那么在 第二行 的 active 值为 false 时循环不会停止。

    但是以上代码中我们使用了 volatile 修饰 active,所以该循环会停止。

    CAS(CompareAndSet)

    无锁优化,自旋锁、乐观锁

    Atomic类
    java提供了一些原子类,内部自带锁,这些锁的实现不是synchronized重量级的,是通过CAS实现的。
    比如有:AtomicBoolean、AtomicInteger、AtomicLong
    这些类的API操作可以实现同步。

    通过原子类的代码跟踪,就会发现原子类的实现是依赖与unsafe类。Atomic类内部调用unsafe类的方法CompareAndSet(对比和设定),这是一种乐观锁的实现方式

    cas(Value,Expection,NewValue){
        if(V==E) V=NewValue;
    }
    //Value是需要修改的值,Expection是期望修改值Value的现在值,NewValue是要设定的新值。
    //CAS乐观锁就是在修改值的时候,对比要修改的值和期望值是不是一致,是才能修改,不是不能修改,会进行下一次尝试
    

    CAS是CPU原语级别实现的,中间不能被打断

    ABA问题
    在CAS算法中,需要取出内存中某时刻的数据(由用户完成),在下一时刻比较并交换(CPU保证原子操作),这个时间差会导致数据的变化。 假设有以下顺序事件: > 1、线程1从内存位置V中取出A > 2、线程2从内存位置V中取出A > 3、线程2进行了写操作,将B写入内存位置V > 4、线程2将A再次写入内存位置V > 5、线程1进行CAS操作,发现V中仍然是A,交换成功
    尽管线程1的CAS操作成功,但线程1并不知道内存位置V的数据发生过改变

    解决ABA问题
    要解决ABA问题,可以增加一个版本号,当内存位置V的值每次被修改后,版本号都加1

    AtomicStampedReference内部维护了对象值和版本号,在创建AtomicStampedReference对象时,需要传入初始值和初始版本号,
    当AtomicStampedReference设置对象值时,对象值以及状态戳都必须满足期望值,写入才会成功

    unsafe类
    Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。
    Unsafe可以直接操作内存,直接生成类实例,直接操作类或者实例变量

    LongAdder、AtomicLong、synchronized比较

    为什么sync比atomic快?
    atomic不加锁,cas实现的原子操作

    为什么longadder比atomic快?
    LongAdder内部做了一个分段锁,高并发时将对单一变量的CAS操作分散为对数组cells中多个元素的CAS操作,取值时进行求和;
    并发较低时仅对base变量进行CAS操作,与AtomicLong类原理相同。

    JUC锁

    ReentrantLock 可重入锁

    synchronized本身也是一种可重入锁。否则子类调用父类不能实现。
    可重入:比如同一个类的两个方法都上锁this,方法一在执行期间调用了方法二,因为两把锁的对象是同一个,所以可以执行方法二。

    ReentrantLock 可以替代 synchronized
    可以在使用synchronized(synchronized(this))的地方替换成ReentrantLock (lock.lock())。
    不同点在于ReentrantLock 需要手动解锁(lock.unlock),手动解锁一定写在finally里面避免死锁。

    lockInterruptibly() 会对Interrupt()做出响应,是一个可以被打断的锁。lock.lock()是不能被打断的。

    int getHoldCount() 查询当前线程保持此锁定的个数,也就是调用lock()方法的次数

    int getQueueLength() 返回正在等待此锁定线程的估计数

    int getWaitQueueLength(Condition condition) 返回等待与此锁定相关的给定条件condition的线程计数

    boolean hasQueuedThread(Thread thread) 查询制定线程是否在等待此锁定

    boolean hasQueuedThread() 查询是否有线程在等待此锁定

    boolean hasWaiters(Contidition condition) 查询是否有线程等待与此锁定有关的condition条件。

    Boolean isFair() 判断是不是公平锁

    boolean isHeldByCurrentThread 查询当前线程是否爆出此锁定

    Boolean isLocked 查询此锁定是否由任意线程保持

    void lockInterruptibly() 如果当前线程未被中断,则获取此锁定;如果被中断则抛异常


    Boolean tryLock() 尝试锁定,仅在调用时锁定未被另一线程保持的情况下,或得锁定。不管锁定与否,方法都会继续执行,可以自己决定要不要wait

    Boolean tryLock(long time,TimeUnit unit) 如果锁定在给定的等待时间内没有被另一个线程保持,且当前线程未被中断,则尝试获取锁

    公平锁 ReentrantLock 的构造方法可以传一个boolean值,传true即为公平锁。公平锁会优先之前在排队的线程先执行。
    RenntrantLock默认是非公平锁。

    ReentrantLock 可以定义 ** Condition ** 条件, Condition 有await()和sinalAll()可以控制线程的阻塞和唤醒。

    CountDownLatch

    latch是门栓的意思,计数到了,门就开了。
    当一个线程执行到latch.await()方法的时候会进行等待,并在在构造 new CountDownLatch(num)传的值num上减1,一直等这个num值减到0的时候程序才会继续向下执行。

    countDown要比join灵活,join需要等线程执行结束之后才会继续执行。

    CyslicBarrier

    循环栅栏。barry.await()会等待在构造new CyslicBarrier(num,new Runnable(){})设置的num满了才会继续执行。
    有一种等人齐发车的意思。

    Phaser

    阶段。结合了CountDownLatch 和 CyslicBarrier。
    Phaser按照不同的阶段执行线程,本身维护着一个成员变量。

    phaser.arriveAndAwaitAdvance()方法意思是到达等待继续向下一个阶段走。
    phaser.arriveAndDeregister()方法可以在某一阶段解除某个线程的注册
    phaser.register()在某一阶段注册添加线程

    ReadWriteLock

    读写锁。读锁是共享锁,写锁是排他锁。
    ReentranReadWriteLock是ReadWriteLock锁的一种实现,在里面又分为readLock和WriteLock。
    读锁可以一起读,写锁需要锁等待。

    Semaphore

    信号灯。可以传一个参数,permits是允许的数量,可以做限流。
    s.acquire()方法叫阻塞方法,线程执行前先用该方法判断下是不是能获取到允许继续执行,不能获取到就得等待。
    s.release()方法是线程结束后释放允许的权限,将权限还回去给下一个线程使用。

    Semaphore默认是非公平锁。它的第二个参数可以传递一个boolean值确定是不是公平锁。

    Exchanger

    交换器。可以在两个线程间交换数据。
    s是线程的某信息,使用e.exchange(s)可以获取到另一个线程交换的信数据。

    LockSupport

    在synchronized/wait/notify/notifuAll方法使用的时候,锁的对象必须一直,一个synchronized代码块中只能有一个线程调用wait/notify。
    wait()会释放所,notify()唤醒线程的时候是不会释放锁的。

    • java.util.concurrent包中Lock引了LockSupport,可以不需要synchronized只用其park()和unpark()方法就可以实现阻塞和唤醒。
    • unpark()方法可以先于park()方法执行,就不会被阻塞。
    • 如果一个线程处于等待状态,连续调用两次park()方法,就会使线程永远无法被唤醒

    park()和unpark()方法的实现原理
    有Unsafe类提供,底层API由C和C++实现。通过某一变量作为标识,在0~1之间切换,以此来实现阻塞和非阻塞。

    公平锁和非公平锁
    新来的一个线程要获得锁的时候,先检查等待队列有没有等待线程,有就排队,这是公平锁。
    如果新来的线程先试图抢占资源获得锁,这就是非公平锁。

  • 相关阅读:
    PAT顶级 1002. Business (35)
    Codeforces Round #417 (Div. 2) B. Sagheer, the Hausmeister
    Codeforces Round #417 (Div. 2) C. Sagheer and Nubian Market
    主席树模板poj 2104
    Java 集合相关
    扁平疣病治疗
    一些网址
    在ubuntu14.04设置静态ip
    在Ubuntu14.04下安装vsftp服务器
    移植SDL运行库到Tiny6410 上
  • 原文地址:https://www.cnblogs.com/farmersun/p/12617100.html
Copyright © 2011-2022 走看看