zoukankan      html  css  js  c++  java
  • JVM锁机制之synchronized

    概述:

    synchronized是java用于处理多线程同步的一个关键字,用于标记一个方法/代码块,使之成为同步方法/同步块。
    用synchronized可以避免多线程处理时的竞态条件问题。

    相关概念:
    在java中,所有对象都有一个锁(也叫对象监视器/内置锁),并且JVM会记录对象的加锁次数。
    内置锁的可重入性:
    当一个线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将该锁的计数值置为1。如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数值会相应递减。当计数值为0时,表示锁已被释放。
    重入性简单的说就是同一个线程可以连续持有一个内置锁。

    当一个线程持有某对象的锁之后,其他线程只有等锁被释放之后才能持有该锁,并执行相关代码块。线程在同步代码执行完成或者出异常后将自动释放锁。


    用法:
    synchronized可以用来标记4种类型的代码:
    1. 非静态方法
    2. 静态方法
    3. 非静态方法内的同步块
    4. 静态方法内的同步块


    下面分别说明这四中类型的写法:
    一. 非静态同步方法:
    public synchronized void method( int value ) {
            
    // doSomething;
    }

    synchronized 关键字用在非静态方法上,表示同步锁是在对象级别,对象锁说明2个线程调用同一个实例对象的同步方法才会产生锁竞争的情况,2个线程分别调用同一个类的2个不同实例的同步方法是互不影响的。
    注意:多个线程调用同一个实例对象的同步方法,这里的同步方法不一定是同一个方法,即使是2个不同的方法,只要2个方法都用synchronized 标记,那么在多线程情况下就会产生互斥。因为锁实际是在实例对象上。

    下面我们动手来试试synchronized 用在非静态方法上的效果:
    public class TestSynchronized {
            public static void main(String[] args) {
                  NumberSpeaker numberSpeaker1 = new NumberSpeaker("numberSpeaker1");
                  NumberSpeaker numberSpeaker2 = new NumberSpeaker("numberSpeaker2");
                  
                  Thread thread1 = new NumberSpeakerThread(numberSpeaker1 );
                  Thread thread2 = new NumberSpeakerThread(numberSpeaker2 );
    
                   thread1.start();
                   thread2.start();
           }
    }
    class NumberSpeaker{
            public String speakerName ;
           
            public NumberSpeaker(String speakerName ) {
                   this.speakerName = speakerName ;
           }
           
            public synchronized void speak(int number) {
                   for(int i = 0; i < number ; i ++) {
                          try {
                    Thread.sleep(500);
                          } catch (InterruptedException e ) {
                              e.printStackTrace();
                          }
                         System. out.println(this .speakerName + " speaking: " + i );
                  }
           }
    }
    class NumberSpeakerThread extends Thread {
            private NumberSpeaker numberSpeaker = null;
           
            public NumberSpeakerThread(NumberSpeaker numberSpeaker ) {
                   this.numberSpeaker = numberSpeaker ;
           }
           
            @Override
            public void run() {
                   this.numberSpeaker.speak(10);
           }
    }

    我们可以用这段代码做细微调整来模拟3种情形:
    1. 2个线程调用同一个对象的非同步方法,代码修改如下:
    Thread thread1 = new NumberSpeakerThread(numberSpeaker1 );
    Thread thread2 = new NumberSpeakerThread(numberSpeaker1 );     // 改为numberSpeaker1 

    public void speak( int number)      // 把synchronized关键字去掉

    执行代码,结果是2个线程并发执行:
    numberSpeaker1 speaking: 0
    numberSpeaker1 speaking: 0
    numberSpeaker1 speaking: 1
    numberSpeaker1 speaking: 1
    numberSpeaker1 speaking: 2
    numberSpeaker1 speaking: 2
    numberSpeaker1 speaking: 3
    numberSpeaker1 speaking: 3
    ......

    2. 2个线程调用同一个对象的同步方法,代码修改如下:
    Thread thread1 = new NumberSpeakerThread(numberSpeaker1 );
    Thread thread2 = new NumberSpeakerThread(numberSpeaker1 );     // 改为numberSpeaker1 

    public synchronized void speak( int number)      // 把synchronized关键字加上

    执行代码,结果是2个线程按顺序执行:
    numberSpeaker1 speaking: 0
    numberSpeaker1 speaking: 1
    numberSpeaker1 speaking: 2
    numberSpeaker1 speaking: 3
    numberSpeaker1 speaking: 4
    numberSpeaker1 speaking: 5
    numberSpeaker1 speaking: 6
    numberSpeaker1 speaking: 7
    numberSpeaker1 speaking: 8
    numberSpeaker1 speaking: 9
    numberSpeaker1 speaking: 0
    numberSpeaker1 speaking: 1
    numberSpeaker1 speaking: 2
    numberSpeaker1 speaking: 3
    numberSpeaker1 speaking: 4
    numberSpeaker1 speaking: 5
    ......

    3. 2个线程调用同2个对象的同步方法,代码修改如下:
    Thread thread1 = new NumberSpeakerThread(numberSpeaker1 );
    Thread thread2 = new NumberSpeakerThread(numberSpeaker2 );     // 改为numberSpeaker2 

    public synchronized void speak( int number)      // 把synchronized关键字加上

    执行代码,结果是2个线程并发执行:
    numberSpeaker2 speaking: 0
    numberSpeaker1 speaking: 0
    numberSpeaker2 speaking: 1
    numberSpeaker1 speaking: 1
    numberSpeaker2 speaking: 2
    numberSpeaker1 speaking: 2
    numberSpeaker2 speaking: 3
    numberSpeaker1 speaking: 3
    numberSpeaker1 speaking: 4
    numberSpeaker2 speaking: 4
    numberSpeaker2 speaking: 5
    numberSpeaker1 speaking: 5
    ......

    这3个例子证明了synchronized 用在非静态方法上,同步锁是作用域对象级别的,并且一个对象的锁只能被一个线程所持有。



    二. 静态同步方法:
    public static synchronized void method(int value ) {
         // doSomething;
    }

    synchronized 关键字用在静态方法上,表示同步锁是在静态方法所在Class对象上,Class锁说明2个线程调只要调用某个类(不管是Class.add还是new Class().add)都会产生锁竞争的情况。



    三. 非静态方法内的同步块:
    有时候我们不需要同步整个方法,只需要对多线程会影响到的代码块做同步,我们应该尽量做到最细粒度的同步控制来保证程序的效率。java允许我们用synchronized 关键字对某一段需要同步的代码做同步。写法如下:
    public void method(int value) {
              // doSomething;
            synchronized (object) {
                   // doSomething;
           }
    }
    synchronized(实例对象),这里的实例对象可以是this,也可以是其他实例化的object,这时锁定的就是this或者object。
    如果synchronized包含了方法中的所有代码,并且被锁对象是this,那么效果和synchronized标记在方法是一样的。


    下面我们把非静态方法中的示例代码稍作修改,看看synchronized 锁指定对象(非this)是什么效果
    public class TestSynchronized {
    
            public static void main(String[] args) {
                  String str1 = "numberSpeaker1";
                  String str2 = "numberSpeaker2" ;
    
                  NumberSpeaker numberSpeaker1 = new NumberSpeaker(str1);
                  NumberSpeaker numberSpeaker2 = new NumberSpeaker(str2);
    
                  Thread thread1 = new NumberSpeakerThread(numberSpeaker1 );
                  Thread thread2 = new NumberSpeakerThread(numberSpeaker2 );
    
                   thread1.start();
                   thread2.start();
           }
    
    }
    
    class NumberSpeaker {
            public String speakerName ;
    
            public NumberSpeaker(String speakerName ) {
                   this.speakerName = speakerName ;
           }
    
            public void speak(int number) {
                   synchronized (speakerName ) {
                          for (int i = 0; i < number ; i ++) {
                                try {
                                      Thread. sleep(500);
                               } catch (InterruptedException e ) {
                                       e.printStackTrace();
                               }
                               System. out.println(speakerName + " speaking: " + i );
    
                         }
                  }
           }
    }
    
    class NumberSpeakerThread extends Thread {
            private NumberSpeaker numberSpeaker = null;
    
            public NumberSpeakerThread(NumberSpeaker numberSpeaker ) {
                   this.numberSpeaker = numberSpeaker ;
           }
    
            @Override
            public void run() {
                   this.numberSpeaker .speak(10);
           }
    }

    我们可以用这段代码做细微调整来模拟2种情形:
    1. 2个NumberSpeaker实例的speakerName用同一个String对象,代码修改如下:
    NumberSpeaker numberSpeaker1 = new NumberSpeaker(str1 );
    NumberSpeaker numberSpeaker2 = new NumberSpeaker(str1);

    执行代码,结果是2个线程按顺序执行:
    numberSpeaker1 speaking: 0
    numberSpeaker1 speaking: 1
    numberSpeaker1 speaking: 2
    numberSpeaker1 speaking: 3
    numberSpeaker1 speaking: 4
    numberSpeaker1 speaking: 5
    numberSpeaker1 speaking: 6
    numberSpeaker1 speaking: 7
    numberSpeaker1 speaking: 8
    numberSpeaker1 speaking: 9
    numberSpeaker1 speaking: 0
    numberSpeaker1 speaking: 1
    numberSpeaker1 speaking: 2
    numberSpeaker1 speaking: 3
    numberSpeaker1 speaking: 4
    numberSpeaker1 speaking: 5
    ......

    2. 2个NumberSpeaker实例的speakerName用同不同String对象,代码修改如下:
    NumberSpeaker numberSpeaker1 = new NumberSpeaker(str1 );
    NumberSpeaker numberSpeaker2 new NumberSpeaker(str2);

    执行代码,结果是2个线程并发执行:
    numberSpeaker2 speaking: 0
    numberSpeaker1 speaking: 0
    numberSpeaker1 speaking: 1
    numberSpeaker2 speaking: 1
    numberSpeaker2 speaking: 2
    numberSpeaker1 speaking: 2
    numberSpeaker1 speaking: 3
    numberSpeaker2 speaking: 3
    numberSpeaker2 speaking: 4
    numberSpeaker1 speaking: 4
    numberSpeaker2 speaking: 5
    numberSpeaker1 speaking: 5
    ......

    上面的例子证明,synchronized(object)的写法中,锁定的对象是括号中的object,并且在同一时间内只有一个线程可以获取这个锁。



    四. 静态方法内的同步块:
    public static void  method(int value) {
              // doSomething;
            synchronized (Class) {
                   // doSomething;
           }
    }

    synchronized用在静态方法内部,括号内应该填写CLASS,即锁定的是CLASS级别(和用synchronized标记静态方法一样)。

    我们再来看看下面的例子:
    public class MyClass {
            public static synchronized void log1(String msg1, String msg2) {
                   log.writeln(msg1);
                   log.writeln(msg2);
           }

            public static void log2(String msg1, String msg2) {
                   synchronized (MyClass.class) {
                          log.writeln(msg1);
                          log.writeln(msg2);
                  }
           }
    }

    这段代码中,log1和log2都用了synchronized ,log1是静态同步方法,所以log1的synchronized 是作用在MyClass.class上的,虽然log2的synchronized 同步的是代码块,但log2中锁的也是MyClass.class。所以在同一时间内只能有一个线程调用log1方法或者log2中的同步块。

    总结:
    synchronized 时,首先要分清被锁的对象,是类的实例对象还是类本身。那么就可以很清楚的分辨什么情况会发生多线程琐竞争的情况。synchronized 可以把任何一个非null对象作为"锁",当synchronized作用在方法上时,锁住的便是对象实例(this);当作用在静态方法时锁住的便是对象对应的Class实例,因为Class数据存在于永久带,因此静态方法锁相当于该类的一个全局锁;当synchronized作用于某一个对象实例时,锁住的便是对应的代码块。

    实现原理:
    当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线程:
    • Contention List:所有请求锁的线程将被首先放置到该竞争队列。
    • Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List。
    • Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set。
    • OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck。
    • Owner:获得锁的线程称为Owner。
    • !Owner:释放锁的线程。
    下图反映了个状态转换关系:
    新请求锁的线程将首先被加入到ConetentionList中,当某个拥有锁的线程(Owner状态)调用unlock之后,如果发现EntryList为空则从ContentionList中移动线程到EntryList,并会指定EntryList中的某个线程(一般为Head)为Ready(OnDeck)线程。Owner线程并不是把锁传递给OnDeck线程,只是把竞争锁的权利交给OnDeck,OnDeck线程需要重新竞争锁。


    注意事项:
    synchronized的同步机制在java多线程处理上并不是最优的选择,所以在java 5开始引入并发库(java.util.concurrent)

  • 相关阅读:
    MyCat 概念与配置
    使用zookeeper管理远程MyCat配置文件、MyCat监控、MyCat数据迁移(扩容)
    数据库分库分表中间件MyCat的安装和mycat配置详解
    搭建dubbo+zookeeper+dubboadmin分布式服务框架(windows平台下)
    数据库分库分表的类型和特点
    数据库为什么要分库分表
    正确安装MySQL5.7 解压缩版(手动配置)方法
    SQL 查询相同记录下日期最大的一条数据
    HashMap在Jdk1.7和1.8中的实现
    Spring Bean的生命周期
  • 原文地址:https://www.cnblogs.com/ViviChan/p/4981716.html
Copyright © 2011-2022 走看看