java multithreading - lock learning

  (Recently compiled knowledge under java multi-threaded, by the way write down)

Use a synchronized and principles

use:

java is synchronized own keyword, the estimate is that we come into contact with the lock mechanism of the earliest use of java multi-threaded, synchronized using java object as a lock, thread synchronization code execution to block attempts to acquire a lock, a thread to acquire the lock is not released During this time, other threads attempting to acquire the lock, then wait in order to achieve safe multithreaded code execution.

   1 common synchronization method: Lock the current object instance, if the new multiple instances, they do not affect each other, the synchronization method of the same object instance need to compete with a lock

       Public class TestA {e.g.

    public synchronized void method1(){

    System.out.println("method1");

    }

    public synchronized void method2(){

    System.out.println("method2");

    }

  }

Static Synchronization Method 2: Class objects using class (java.lang.Class) independently of each other as the lock object instances of objects, class objects and classes Class.

       Public class TestB {e.g.

    public static synchronized void method1(){

    System.out.println("method1");

    }

    public static synchronized void method2(){

    System.out.println("method2");

    }

  }

Method 3 sync block: synchronized using brackets behind the object as a lock

       Public class TestC {e.g.

    private Object lock

    public void method1(){

    synchronized(lock){

      System.out.println("method1");

    }

    }

    public void method2(){

    synchronized(lock){

      System.out.println("method2");

    }

    }

  }

         Learn to use after further consideration of the class or object instance is just a piece of data in memory, it is how to provide a lock function of it?

 synchronized principle:

      (Java syntax specification reference oracle of   https://docs.oracle.com/javase/specs/jls/se12/html/jls-17.html#jls-17.1   and

             jvm description  https://docs.oracle.com/javase/specs/jvms/se12/html/jvms-2.html#jvms-2.11.10 )

                  

              

    Roughly Translated: 

       (1). Monitor each java class has an associated thread can lock and unlock the monitor, once a thread lock this monitor, other threads have to wait to unlock he unlock, monitor object is reentrant, the same reentrant thread count by one, wait method is also an object of the wait monitor.

       (2) The method obtains common synchronization before execution of the current object (this) of the monitor lock, monitor lock acquire static method Class example, obtains monitor lock object brackets before performing block synchronization method.

       (3) the specific process is performed for jvm synchronization method, constantpool this method will play a compiled flag (ACC_SYNCHRONIZED), will be identified when executed, perform the method of the former to perform the method enters a Monitor, and the method is normally performed after the execution or exception exits the monitor, the synchronization method of the block will be added monitorenter and monitorexit instructions compile time before and after the method of block

  Continue to understand the need to understand the implementation principle monitor object header data structure java and java object model, which is part of the complex, you can refer to the HotSpot tutorials and source code. ( Https://github.com/openjdk-mirror/jdk7u-hotspot )

     HotSpot is a c ++ implementation, designed a OOP-Klass Model Based on. OOP (Ordinary Object Pointer) refers to a common object pointer, and for the particular type described Klass object instance.

     

     再来一个源码(oop.hpp)截图: 

      

 _mark是图里的markword metadata里的_klass 是图里指向方法区instanceKlass 的元数据指针,_mark 字段则包含了需要java类的状态信息,对象的锁状态也在其中。

    以32位操作系统为例子:

    

monitor机制:

  

 

ObjectMonitor(objectMonitor.cpp)的源码里的尝试获取锁解析。

简单说明: 尝试获取monitor 方法,THREAD 是请求获取monitor的线程,  _owner 是记录的获取到当前monitor的线程。

       (1)两者不相等时再判断是否线程可以拥有_own的锁(等线程部分详解),不拥有再基于CAS尝试获取,都失败返回false。

       (2)两者相等计数加一,返回true

这只是monitor其中一个方法,其他的enter,exit等方法可以参考源码,monitor的方法和java对象的头部信息完成了synchronized的锁机制。

java synchronized 锁说明:

(1)偏向锁: 锁标志01 和无锁标志一样,java对象头里还记录了线程ID,线程获取锁时判断线程ID为空或者等于自己,则成功。其他线程同时获取则失败,锁变成轻量级锁。

(2)轻量级锁: 锁标志00 ,获取不到锁的线程会自旋(循环获取),再次获取失败,锁膨胀为重量级锁

(3)重量级锁:锁标志10, 获取不到锁的线程block。

到这里synchronized 关键字 锁的使用和原理基本结束,但有一个最最基本的问题没有解释,线程怎么通过CAS就能安全地获取到锁了,CAS可以说是锁的最基本的原子操作,等看完java并发包的锁再一起讲

 

 

        

二 java并发包里的锁

 使用:

 先看一下lock接口的方法:

void lock();                                                    获取锁,获取不到则线程block
void lockInterruptibly() throws InterruptedException; 获取锁,获取不到,如果线程的中断标识为true,则抛出异常,否则线程block,
boolean tryLock(); 尝试获取锁,成功返回true,不成功返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;尝试获取锁,成功返回true,不成功如果中断标识为true,抛出异常,否者返回false
void unlock(); 释放锁
Condition newCondition(); 返回一个Condition 对象
并发包提供的对象有:ReentrantLock-可重入锁,ReentrantReadWriteLock-可重入读写锁,StampedLock-不可以冲入,适用于内部
ReentrantLock reentrantLock = new ReentrantLock();
if(reentrantlock.tryLock()){ // even you tryLock is true when you lock can still block by the lock ,that is multiply -thread programming
try{
reentrantLock.lock(); //only one thread can lock the reentrantLock,the others will be blocked
......
}
finally{
reentrantLock.unlock(); //in case of code crush ,unlock the reentrantlock in the finally statement block
}
}else
{
....
}

  ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); 

   Lock writeLock = reentrantReadWriteLock.writeLock();

  Lock readLock = reentrantReadWriteLock.readLock();

   writeLock是独占锁,一个线程占用了,其他线程都需要排队

   readLock是共享锁,readLock可以多个线程公用,但是跟writeLock 互斥

 

 

原理:

打开java并发包的源码,ReentrantLock的源码,ReentrantLock的Lock等方法基本就是Sync 属性的方法.ReentrantLock 有 lock lockInterruptibly tryLock 带时间的trylock unlock等方法。Sync有公平锁和非公平锁,默认非公平锁


以调用ReentrantLock lock方法为例子 内部调用FairSync或者UnFairSync的lock方法,内部调用AbstractQueuedSynchronizer的acquire的方法,父类方法调用抽象方法tryAcquire(抽象方法在子类实现,FairSync实现时考虑了排队)
方法成功后 再acquireQueued,正真排队获取锁。其他方式的lock和trylock等方法可以参考代码。

   

    hasQueuedPredecessors 方法时判断当前线程是不是在排队的对头部。不在头部返回true。

再来看看AbstractQueuedSynchronizer 的队列和获取锁的方法,判断如果是在head后的节点,再次tryacquire,成功则返回中断标志位, 如果需要中断则调用unsafe类中断线程。

  等待队列模型: 

 

释放锁:非共享锁释放队列头。

 

 

CAS

这里用到了Unsafe类提供的基本的CAS操作和线程的中断方法,UnSafe类还提供了线程安全的基本类。这里先看看CAS操作的原理

unsafe的CAS方法一个例子:

 

打开hotspot的Unsafe类(unsafe.cpp)

 

步骤基本意思是oopDesc (oopDesc::atomic_compare_exchange_oop)方法判断孤弱入参对象指定位置的值等于期望的值,交换为给定的新值,并设置barrier,内存屏障是多线程同时运行时取到某个值时 因为缓存锁必须到主存里取最新的值,volatile关键字就是类似方式实现

继续看原子交换方法(opp.inline.hpp):其他是一些判断主要是 Atomic::cmpxchg 和  Atomic::cmpxchg_ptr 方法

atomic的方法跟计算机的操作系统有关系,选一个atomic_linux_x86.inline.hpp文件:

由内嵌的汇编代码的汇编指令cmpxchgl 完成值的比较和交换,原子性得以保证。

 

volatile

volatile关键字的作用是让每个线程的缓存里的数据跟主存里保持一致。
问题引入: 多个线程多个cpu执行时,每个cpu都有自己的缓存(电脑的L1,L2,寄存器都是缓存),修改和读取数据都是先从缓存修改读取,再同步到内存,这样就可能出现其他线程读取不到最新数据的问题
解决方法: 设置成volatile的属性字段,多个缓存会读取时都回读取到最新的数据。
实现原理:
volatile的属性字段读写操作前面和后面加一个 内存屏障,强制读和写到主存


Cpu数据原子操作实现:
基于总线或者缓存锁 #lock,总线锁 锁住所有的cpu,性能较差。 缓存锁锁住缓存并基于缓存一致性,其他cpu写的缓存数据无效,实现数据原子操作。


 

 

 








 

Guess you like

Origin www.cnblogs.com/thinkqin/p/11099384.html