java多线程原理学习笔记(二)

一、使用synchronize加锁

当多个线程访问并修改同一个变量(共享变量)时,在不做同步或者其他额外的协调操作,往往这个变量的结果不符合预期。这就说明这个变量是线程不安全的。这就需要引入“锁”的概念。java中提供了synchronize关键字来实现加锁操作。
synchronize的基本语法:

  1. 修饰实例方法。作用域是当前实例对象。
  2. 静态方法。作用域是当前类对象。
  3. 修饰代码块。需要指定加锁对象。
public class SynchronizeDemo {
    Object lock;  
    public SynchronizeDemo(){
    }
    public SynchronizeDemo(Object lock){
        this.lock=lock;
    }
    public synchronized void demo(){}   //修饰实例方法,作用域是当前实例对象,同this。

    public void demo1(){
        synchronized (this){ //作用域是当前实例对象

        }
    }
    public synchronized static void demo2(){}  //静态方法。作用域是当前类对象,同Demo.class
    
     public void demo3(){
        synchronized (Demo.class){ //作用域是当前类对象

        }
    }
}

二、synchronize锁在内存中的具体实现

根据synchronize(lock),可以知道synchronize是根据lock对象的生命周期来实现控制锁的粒度的。而在java中,创建对象时,在对象头中会有一个区域专门存储该对象和锁有关的信息,称为Mark word。具体如下图所示:
java多线程原理学习笔记(二)_第1张图片

三、synchronize锁的升级

当使用synchronize加锁时,可以实现数据的安全性,但是会造成性能的下降。所有JDK1.6之后对synchronize做了一些优化。为了减少获得锁和释放锁带来的性能开销,加入了偏向锁、轻量级锁的概念。所以现在在synchronize中,锁有了四种状态:无锁、偏向锁、轻量级锁、重量级锁。锁的状态根据竞争的程度从低到高不断升级。

1、偏向锁

当一个线程访问加了锁的代码块时,会在对象的Mark word区域通过CAS操作存储当前线程ID,后续这个线程进入和退出这段代码块时,无需进行加锁和释放锁的操作。而是比较Mark word中存储的线程ID是否和该线程相同,相同表明偏向锁是偏向当前线程的,就不需要再执行获得锁的操作了。当另一个线程访问该同步代码块时,通过CAS操作写入线程ID失败时,说明当前锁存在竞争,需要先撤销偏向锁并升级为轻量级锁。
java多线程原理学习笔记(二)_第2张图片

2、轻量级锁

轻量级锁采用自旋的方式。自旋就是当其他线程来竞争锁时,通过原地循环执行CAS写入线程ID来等待锁的释放,而不是直接阻塞。而且在执行循环的时候也会消耗cpu资源的。所以轻量级锁适合同步代码块执行很快的场景,这样线程循环等待的时间会比较短。比起阻塞线程反而会提升锁的性能。但是自选的次数需要控制,防止自选时间过长,反而浪费资源。默认是10次,jdk1.6后加入自适应自旋锁,次数不是固定不变,根据前一次在同一个锁上自旋的时间及锁的拥有者的状态来决定。当超过自旋次数还没有获得锁,则轻量级锁会膨胀成重量级锁,并阻塞线程。
java多线程原理学习笔记(二)_第3张图片

3、重量级锁

当轻量级锁升级到重量级锁后,线程只能被挂起阻塞来等待唤醒。当一个java类加入了同步代码块后,会生成monitorenter和moniterexit,而每一个java对象都有一个对应的监视器monitor,当一个线程要执行被synchronize修饰的同步代码块时,该线程需要先获取synchronize修饰的对象的monitor。monitorenter去获得对象监视器,moniterexit去释放监视器,使其他被阻塞的线程可以获得monitor。monitor依赖操作系统的互斥锁来实现,线程被阻塞后加入同步队列。
java多线程原理学习笔记(二)_第4张图片

wait,notify,notifyAll方法在synchronize中的运用

被阻塞的线程什么时候被唤醒取决于获得锁的线程什么时候执行完同步代码块,并释放锁。我们可以使用Object类提供的信号机制,wait,notify,notifyAll这三个方法来控制线程的状态。
wait方法:持有对象锁的线程,释放锁以及cpu资源进入等待队列,直到被同一个锁对象执行notify/notifyAll唤醒。
notify方法:持有对象锁的线程A唤醒某个竞争该对象锁的线程B,当A执行结束后并且释放了锁,B获取了对象锁权限,其他竞争线程继续等待(即使B同步完成,释放锁。其他线程依旧等待,直到有新的线程被notify或者notifyAll被调用)
notifyAll方法:notifyAll会唤醒所有竞争同一个对象锁的线程。对象锁释放后,所有被唤醒的线程都可能获得锁。

java多线程原理学习笔记(二)_第5张图片

你可能感兴趣的:(java分布式与高并发,java,学习,开发语言)