锁的概念

  • 常见锁;悲观锁、乐观锁、偏向锁、分段锁
  • 特性:

    • 互斥性

      即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。

    • 不可见性

      必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。

Java中常用锁的实现方式

1.用并发包中的锁类

(未用到synchronized,而是利用了volatile的可见性),如下图
LOCK.png

  • ReentrantLock:可重入锁
  • AQS是抽象类,内置自旋锁实现的同步队列,封装入队和出队的操作,提供独占、共享、中断等特性的方法。在SQS中,定义了一个volatile int sate 变量作为共享资源,如果线程获取资源失败,则进入同步FIFO队列中等待;如果成功获取资源就执行临界代码。执行完释放资源时,会通知同步队列中的等待线程来获取资源后出队并执行。
  • jdk8新增StampedLock锁,改进了读写锁ReentrantWriteLock。

2.利用同步代码块

  • 该方法有两种方式对方法进行加锁操作:第一,在方法签名处加synchronized关键字;第二,使用synchronized对(对象或者类)进行同步。这里要遵循所的范围尽可能小的原则。

synchronized

  • JVM 底层是通过 monitor 监视锁来实现synchronized同步的, 监视锁monitor 是每个对象与生俱来的一个隐藏字段,JVM会根据synchronized的当前使用环境找到对应的monitor状态进行加锁、解锁的判断。如果成功加锁就成为monitor的唯一持有者。占有时monitor + 1
  • monitor

    • 0,lock
    • 重入
    • monitor 在被线程占有的时,其他线程请求会进入BLOCK,直到monitor为 0 (为零时解锁)
  • 反编译发现加synchronized关键字后。在代码块中会使用monitorenter及monitorexit两个字节码指令获取和释放monitor。而方法元信息中会使用ACC_SYNCHRONIZED标识方法是一个同步方法。
  • 偏向锁(针对与不存在线程竞争的情况):为了在资源没有被多线程竞争的情况下尽量减少锁带来的性能开销。在锁对象的对象头(一个实例对象包括:对象头、实例变量、填充数据)中有一个ThreadId字段。当锁被访问时,如果字段为空,那么JVM让线程持有偏向锁,并将ThreadId字段的值设置为线程的id。如果线程再次请求时会判断当前线程的ID是否与锁对象的ThreadId是否一致,一致不再重复获取锁。如果出现锁有竞争时,偏向锁会被撤销并升级为轻量锁,竞争激烈时,会升级为重量锁。

volatile

1.volatile的作用

让其他线程能够马上感知到某一线程或某个变量的修改

(1)保证可见性

对共享变量的修改,其他的线程马上能感知到

不能保证原子性 读、写、(i++)

(2)保证有序性

重排序(编译阶段、指令优化阶段),输入程序的代码顺序并不是实际执行的顺序,重排序后对单线程没有影响,对多线程有影响

Volatile

happens-before:是时钟顺序的先后,并不能保证线程交互的可见性

(3)volatile规则:

对于volatile修饰的变量:

  • volatile之前的代码不能调整到他的后面
  • volatile之后的代码不能调整到他的前面(as if seria)
  • 霸道(位置不变化)

(4)volatile的原理和实现机制(锁、轻量级)

HSDIS --反编译---汇编

Java --> .class---> JVM ---> ASM文件

2.volatile的使用场景

  1. 状态表示(开关模式)

    public class ShutDowsnDemmo extends Thread{
        private volatile boolean started=false;
    
        @Override
        public void run() {
            while(started){
                dowork();
            }
        }
        public void shutdown(){
            started=false;
        }
    }
    
  2. 双重检查锁定(Double-checked-Locking)

    public class Singleton {
        private volatile static Singleton instance;
        public static Singleton getInstance(){
            if(instance==null){
                synchronized (Singleton.class){
                    instance=new Singleton();
                }
            }
            return instance;
        }
    }
  3. 需要利用顺序性的场景

指令优化

CPU在处理信息时会对可以合并数据进行存取的操作进行合并优化以提高效率。

synchronized和volatile的区别

  1. 使用上的区别

    Volatile只能修饰变量,synchronized只能修饰方法和代码块

  2. 对原子性的保证

    synchronized可以保证原子性,Volatile不能保证原子性

  3. 对可见性的保证

    都可以保证可见性,但实现原理有区别

    前者对变量加lock,后者通过monitorenter进Lock,通过monitorexit正常或者异常出Lock

  4. 对有序性的保证

    Volatile能保证有序,synchronized可以保证有序性,但是代价(重量级)并发退化到串行

  5. 其他

    synchronized引起阻塞,Volatile不会引起阻塞,volatile在实际业务中会使线程的执行速度变慢。

信号量同步

1.定义

​ 信号量同步是指在不同的线程之间,通过船体同步信号量来协调线程执行的先后次序。

2.CountDownLatch

​ 该类是基于执行时间的同步类

3.Semaphore

​ 信号同步类。只有在调用Semaphore对象的acquire()成功后,才可以往下执行,完成后执行release()释放持有的信号量,下一个线程就可以马上获取这个空闲信号量进入执行。

总结

无论从性能还是安全上考虑,尽量使用并发包提供的信号同步类,避免使用对象的wait()notify()方式进行同步。


ShikoWei
0 声望1 粉丝

Java 练习生


« 上一篇
【笔记】JMM
下一篇 »
【笔记】Nginx