并发锁及其原理

==== =锁= ====

1、Synchronized原理是什么?
  Synchronized 是由JVM实现的一种实现互斥同步的锁 ,被 Synchronized修饰过的程序
  块,在编译前后被编译器生成了monitorenter和monitorexit两个字节码指令。
  虚拟机执行到monitorenter指令时, 尝试获取对象的锁:如果这个对象没有锁定, 或者当前线程
  已经拥有了这对象的锁,把锁的计数器 +1;当执行monitorexit指令时将锁计数器-1;当计数器为
  0时,锁就被释放了 。如果获取对象失败了,那当前线程就要阻塞等待,直到对象锁被另外一个
  线程释放为止 。
2、锁升级

在JDK早期是重量级的;后来改进—>锁升级的概念;只能升级不能降级,sync的四种状态
在这里插入图片描述

markWord记录这个线程的ID(偏向锁);
如果线程争用:升级---->自旋锁(占用cpu)适用于时间段、数量少的线程;
自旋锁触发10次以后升级为重量级锁---->os(不占用cpu);
执行时间短,线程数少用自旋锁;
执行时间长,线程数多用系统锁。

3、volatile关键字很重要的两个特性:
 1)保证变量在线程间可见,volatile变量在各个线程中是一致的
 3)禁止指令的重排序优化;
4、synchronized和volatile区别
 可见性:JMM提供了volatile变量定义、final、synchronized块来保证可见性。
 原子性:个人理解是如果执行,就执行完,synchronized块来保证。 
 有序性:volatile和synchronized保证线程之间操作的有序性。 
 指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,不保证各个语句的执行顺
                  序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致
                  的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。
JMM处理过程:通过禁止特定类型的编译器重排序和处理器重排序来为程序员提供一致的内存可见
                          性保证。

 例如A线程具体什么时候刷新共享数据到主内存是不确定的,假设我们使用了同步方式 
 (synchronized,volatile和final),那么刷新的时间是确定的。

 每个线程都有一个自己的本地内存空间。线程执行时,先把变量从主内存读取到线程自己的本地内
 存空间,然后再对该变量进行操作对该变量操作完后,在某个时间再把变量刷新回主内存,线程A
 释放锁后会同步到主内存,线程B获取锁后会同步主内存数据。

 假设本地内存A和B有主内存中共享变量x的副本,初始时这三个内存中的x值都为0。线程A在执行
 时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,
 线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。然
 后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。

 1)volatile主要应用在多个线程对实例变量更改的场合,刷新主内存共享变量的值从而使得各个线
      程可以获得最新的值,线程读取变量的值需要从主存中读取;synchronized则是锁定当前变
      量,只有当前线程可以访问该变量,其他线程被阻塞住。另外,synchronized还会创建一个内
      存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中(即释放锁前),从而保
      证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作
 2)volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
 3)volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞,比如多个线程争抢
 4)synchronized锁对象时,会出现阻塞。
 5)volatile仅能实现变量的修改可见性,不能保证原子性;synchronized则可以保证变量的修改
      可见性和原子性,因为线程获得锁才能进入临界区,保证临界区中的所有语句全部得到执行。
 6)volatile标记的变量不会被编译器优化,可以禁止进行指令重排;synchronized标记的变量可以
      被编译器优化。
5、volatile双重检查实现懒汉单例模式
class Singleton{    
	private volatile static Singleton instance = null;
	private Singleton() {}         
	public static Singleton getInstance() {   
	     if(instance==null) {            
	     synchronized (Singleton.class) { 
	     	if(instance==null) {
	     		instance = new Singleton();            
	     	}   
	     	return instance;   
	     }
	}
}

恶汉单例模式
类加载到内存后就实例化

public class VillainSingleton {
    private static final VillainSingleton INSTANCE = new VillainSingleton();
//    private static final VillainSingleton;
//    static {
//        INSTANCE = new VillainSingleton();
//    }
    public static VillainSingleton getInstance() {
        return INSTANCE;
    }
    private VillainSingleton() {}
}
6、多线程对volatile进行变量的自增操作该操作是否原子性
  举例:对变量inc进行自增操作,10个线程分别自增1000次操作
  假如某个时刻变量inc的值为10,

线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;
  然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行
读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行
无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入
工作内存,最后写入主存。然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线
程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入
工作内存,最后写入主存。那么两个线程分别进行了一次自增操作后,inc只增加了1
所以volatile不是原子性的
保证变量的自增操作的是原子性的,可以用synchronize、ReentrantLock、AtomicInteger

  public  int inc = 0;
  // synchronize
  public synchronized void increase() {
       inc++;
  }
  // ReentrantLock
  Lock lock = new ReentrantLock();
  public  void increase() {
      lock.lock();
      try {
          inc++;
      } finally{
          lock.unlock();
      }
  }
  // AtomicInteger
  public  AtomicInteger inc = new AtomicInteger();
  public  void increase() {
      inc.getAndIncrement();
  }
7、在多线程中++i和i++是线程不安全的
 解决方式:①、synchronized加锁修饰 ②、AtomicXXX ③、LongAdder
 AtomicXXX比synchronized效率高的原因是AtomicXXX是无锁的CAS操作;
 LongAdder比AtomicXXX效率高的原因是LongAdder是分段锁(分段锁也是无锁的CAS操作)
8、可重入锁
 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然
 会得到该对象的锁.
synchronized void m1() {m2();}
synchronized void m2() {}
  继承中子类调用父类的同步方法
public class Parent {
    synchronized void m() {}
    public static void main(String[] args) {
        new Child().m();
    }
}
class Child extends Parent {
    @Override
    synchronized void m() {super.m();}
}

ReentrantLock可重入锁,可以替代synchronized,子类和父类都是synchronized(this)同一把锁。
ReentrantLock是如何实现可重入性的?
ReentrantLock内部自定义了同步器Sync,加锁的时候通过CAS算法,将线程对象放到一个双向链
表中,每次获取锁的时候,看下当前维护的那个线程ID和当前请求的线程ID是否一样判断重入。
ReentrantLock、synchronized区别:
1)ReentrantLock可以使用trylock进行尝试锁定,不管锁定与否,方法都将继续进行;
synchronized如果搞不定就肯定阻塞了,ReentrantLock可以自己决定到底要不要wait
2)ReentrantLock还可以用lock.lockinterruptibly(),对interrupt(0方法做出回应,可以被打断的加锁
3)ReentrantLock还可以指定为公平锁,所谓公平锁就是后来的线程在队列中等待,让已有线程先
执行完了在执行后来的。ReentrantLock默认是非公平锁。ReentrantLock(true)公平锁。
4)ReentrantLock替代sync实现可重入但是sync是自动解锁,但是ReentrantLock必须手动解锁,
try…finally{lock.unlock()}
跟Synchronized相比,可重入锁ReentrantLock其实现原理有什么不同?
Synchronized通过在对象头中设置标记,是一种JVM原生的锁实现方式,ReentrantLock以及所有
的基于Lock接口的实现类,都是通过用一个volitile修饰的int型变量,并保证每个线程都能拥有对该
int的可见性和原子修改,其本质是基于所谓的AQS框架。

9、什么是AQS
 AQS队列内部是一个 FIFO 的双向链表,每个数据结构都有两个指针,分别指向直接的后继节点和
 直接前驱节点。所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。每个Node由线
 程封装,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去。

 Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用
 的高性能开发库都是基于Unsafe类开发的,比如Netty、Hadoop、Kafka等;Unsafe可认为是
 Java中留下的后门,提供了一些低层次操作,如直接内存访问、线程调度等

 AQS的两种功能
 1)独占锁,每次只能有一个线程持有锁,ReentrantLock 就是以独占方式实现的互斥锁;
 2)共享锁,允许多个线程同时获取锁,并发访问共享资源,比如 ReentrantReadWriteLock;
10、各种JUC同步锁:
   CountDownLatch、CyclicBarrier、MarriagePhaser、ReadWriteLock、
   Semaphore、Exchanger、LockSupport
11、什么是CAS?
   CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同
   时,将内存值V修改为B,否则什么都不做。
   java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。
12、CAS中ABA问题。
   如线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,并且线程2进行了一些操作将
   内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内
   存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程就是有问题的。
   比如链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
   AtomicStampedReference/AtomicMarkableReference来处理会发生ABA问题的
13、ThreadLocal的理解

​ ThreadLocal线程本地变量/线程本地存储。ThreadLocal为变量在每个线程中都创建了一个副
本,那么每个线程可以访问自己内部的副本变量。
1)ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
  2)threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,每个线程中可有多个
threadLocal变量;
  3)在进行get之前,必须先set,否则会报空指针异常;如果没有先set的话,即在map中查找不
到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语
句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。
   如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软软的铲屎官

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值