• Java多线程6:Synchronized锁代码块(this和任意对象)


    一、Synchronized(this)锁代码块

       用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待。这种情况下就可以使用synchronized同步该方法中会引起线程安全的那部分代码,其余不会引起线程安全的就不需要同步,这部分代码就可以多线程并发执行,减少时间提高效率。

      举例:多线程执行同一个方法时,同步方法和同步代码块花费时间的比较

      1、synchronized修饰方法(同步方法)

      synchronized修饰longTimeTask方法,其中花费时间比较长的且与线程安全无关的是37-39行代码,会引起线程安全问题的是42-46。

     1 public class ThreadSynch {
     2 
     3     private int num;
     4 
     5     public synchronized void longTimeTask(String userName){
     6         //定义各线程的进入时间
     7         long thread0StartTime = 0L;
     8         long thread1StartTime = 0L;
     9         long thread2StartTime = 0L;
    10         long thread3StartTime = 0L;
    11         long thread4StartTime = 0L;
    12         //定义各线程执行该方法所需的时间
    13         long thread0LastTime;
    14         long thread1LastTime;
    15         long thread2LastTime;
    16         long thread3LastTime;
    17         long thread4LastTime;
    18         //显示各线程进入的时间
    19         if(Thread.currentThread().getName().contains("-0")){
    20             thread0StartTime = System.currentTimeMillis();
    21             System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread0StartTime);
    22         }else if(Thread.currentThread().getName().contains("-1")){
    23             thread1StartTime = System.currentTimeMillis();
    24             System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread1StartTime);
    25         }else if(Thread.currentThread().getName().contains("-2")){
    26             thread2StartTime = System.currentTimeMillis();
    27             System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread2StartTime);
    28         }else if(Thread.currentThread().getName().contains("-3")){
    29             thread3StartTime = System.currentTimeMillis();
    30             System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread3StartTime);
    31         }else if(Thread.currentThread().getName().contains("-4")){
    32             thread4StartTime = System.currentTimeMillis();
    33             System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread4StartTime);
    34         }
    35 
    36         //花费时间较长,与线程安全无关的代码
    37         for(int i = 200000000; i > 0; i--) {
    38             String nameID = Thread.currentThread().getName() + Thread.currentThread().getId();
    39         }
    40 
    41         //与线程安全相关的代码块
    42         if("zs".equals(userName)){
    43             num = 100;
    44         }else if("ls".equals(userName)){
    45             num = 200;
    46         }
    47 
    48         //显示各线程执行该方法的时间
    49         if(Thread.currentThread().getName().contains("0")){
    50             thread0LastTime = System.currentTimeMillis() - thread0StartTime;
    51             System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread0LastTime + "ms");
    52         }else if(Thread.currentThread().getName().contains("1")){
    53             thread1LastTime = System.currentTimeMillis() - thread1StartTime;
    54             System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread1LastTime + "ms");
    55         }else if(Thread.currentThread().getName().contains("2")){
    56             thread2LastTime = System.currentTimeMillis() - thread2StartTime;
    57             System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread2LastTime + "ms");
    58         }else if(Thread.currentThread().getName().contains("3")){
    59             thread3LastTime = System.currentTimeMillis() - thread3StartTime;
    60             System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread3LastTime + "ms");
    61         }else if(Thread.currentThread().getName().contains("4")){
    62             thread4LastTime = System.currentTimeMillis() - thread4StartTime;
    63             System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread4LastTime + "ms");
    64         }
    65 
    66     }
    67 }

      继承Thread的Thread01类,其run方法调用上述对象的longTimeTask方法

    public class Thread01 extends Thread{
        private ThreadSynch threadSynch;
    
        public Thread01(ThreadSynch threadSynch) {
            this.threadSynch = threadSynch;
        }
    
        @Override
        public void run() {
            threadSynch.longTimeTask("ls");
        }
    }

      测试,构建同一对象的多个线程

    public class Test {
        public static void main(String[] args) {
            ThreadSynch threadSynch = new ThreadSynch();
            //五个线程使用同一个对象构建
            Thread thread01 = new Thread01(threadSynch);
            Thread thread02 = new Thread01(threadSynch);
            Thread thread03 = new Thread01(threadSynch);
            Thread thread04 = new Thread01(threadSynch);
            Thread thread05 = new Thread01(threadSynch);
            //五个线程同时调用该对象中的方法
            thread01.start();
            thread02.start();
            thread03.start();
            thread04.start();
            thread05.start();
        }
    }

      结果:

    Thread-0进入时间为====1553150692703
    Thread-0执行时间为===8437ms
    Thread-3进入时间为====1553150701140
    Thread-3执行时间为===7014ms
    Thread-1进入时间为====1553150708154
    Thread-1执行时间为===7002ms
    Thread-4进入时间为====1553150715157
    Thread-4执行时间为===7121ms
    Thread-2进入时间为====1553150722278
    Thread-2执行时间为===7147ms

      说明:因为synchronized修饰的是整个方法,所以线程Thread-0访问longTimeTask方法的时候,其余四个线程都处于阻塞状态,待其执行结束释放锁的时候,线程Thread-3开始执行,其余三个线程还是处于阻塞状态,所以,这五个线程执行完毕所需的时间是各自执行时间的相加,8.4 + 7.0 + 7.0 + 7.1 + 7.1 = 36.6s。

      2、synchronized修饰代码块(同步代码块)

      synchronized由同步方法改为同步方法中引起线程安全问题的代码块,其余都不变

    public class ThreadSynch {
    
        private int num;
    
        public void longTimeTask(String userName){
            long thread0StartTime = 0L;
            long thread1StartTime = 0L;
            long thread2StartTime = 0L;
            long thread3StartTime = 0L;
            long thread4StartTime = 0L;
            long thread0LastTime;
            long thread1LastTime;
            long thread2LastTime;
            long thread3LastTime;
            long thread4LastTime;
            if(Thread.currentThread().getName().contains("-0")){
                thread0StartTime = System.currentTimeMillis();
                System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread0StartTime);
            }else if(Thread.currentThread().getName().contains("-1")){
                thread1StartTime = System.currentTimeMillis();
                System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread1StartTime);
            }else if(Thread.currentThread().getName().contains("-2")){
                thread2StartTime = System.currentTimeMillis();
                System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread2StartTime);
            }else if(Thread.currentThread().getName().contains("-3")){
                thread3StartTime = System.currentTimeMillis();
                System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread3StartTime);
            }else if(Thread.currentThread().getName().contains("-4")){
                thread4StartTime = System.currentTimeMillis();
                System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread4StartTime);
            }
    
            //花费时间较长,与线程安全无关的代码
            for(int i = 200000000; i > 0; i--) {
                String nameID = Thread.currentThread().getName() + Thread.currentThread().getId();
            }
    
            //与线程安全相关的代码块用synchronized修饰
            synchronized(this){
                if("zs".equals(userName)){
                    num = 100;
                }else if("ls".equals(userName)){
                    num = 200;
                }
            }
    
            if(Thread.currentThread().getName().contains("0")){
                thread0LastTime = System.currentTimeMillis() - thread0StartTime;
                System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread0LastTime + "ms");
            }else if(Thread.currentThread().getName().contains("1")){
                thread1LastTime = System.currentTimeMillis() - thread1StartTime;
                System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread1LastTime + "ms");
            }else if(Thread.currentThread().getName().contains("2")){
                thread2LastTime = System.currentTimeMillis() - thread2StartTime;
                System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread2LastTime + "ms");
            }else if(Thread.currentThread().getName().contains("3")){
                thread3LastTime = System.currentTimeMillis() - thread3StartTime;
                System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread3LastTime + "ms");
            }else if(Thread.currentThread().getName().contains("4")){
                thread4LastTime = System.currentTimeMillis() - thread4StartTime;
                System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread4LastTime + "ms");
            }
    
        }
    }

      同样的五个线程访问,看一下结果:

    Thread-0进入时间为====1553151204348
    Thread-3进入时间为====1553151204348
    Thread-1进入时间为====1553151204348
    Thread-2进入时间为====1553151204348
    Thread-4进入时间为====1553151204380
    Thread-3执行时间为===19330ms
    Thread-2执行时间为===19383ms
    Thread-1执行时间为===19854ms
    Thread-4执行时间为===20498ms
    Thread-0执行时间为===20782ms

      说明:因为synchronized修饰的是方法中会引起线程安全问题的代码块,所以仅仅是这一部分代码无法并发执行。可以看到Thread-0,Thread-1,Thread-2,Thread-3,Thread-4几乎同时进入longTimeTask方法,并发执行for循环中花费时间较长的代码,由结果看,Thread-3最先执行完这部分代码,开始执行synchronized修饰的代码块,其余四个线程随后进入阻塞状态。因为同步代码块中执行时间较短,Thread-3执行完后,Thread-2开始执行,最后是Thread-0执行,至此,五个线程执行完毕,所花费的时间就是Thread-0花费的时间,即20.8s。

      可以看到,在longTimeTask方法中,synchronized由修饰方法改为修饰代码块,多线程执行所花费的时间由36.6s变成20.8s,执行时间明显减少,效率提升。

     二、任意对象作为对象监视器

      2.1 上述同步代码块使用的是synchronized(this)格式,其实Java还支持对“任意对象”作为对象监视器来实现同步的功能。这种任意对象大多是该方法所属类中的实例变量或该方法的参数,不然抛开这个类去使用别的对象作为对象监视器,意义不大。使用的格式是synchronized(非this的任意对象)。

      举例:以ThreadSynch类中的变量student作为对象监视器去同步代码块

    public class ThreadSynch {
    
        private Student student = new Student();
        private String schoolName;
    
        public void setNameAndPassWord(String name,String age){
            synchronized(student){
                System.out.println(Thread.currentThread().getName() + "===" + "进入同步代码块");
                try {
                    Thread.sleep(3000);
                    this.student.setName(name);
                    this.student.setAge(age);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "===" + "离开同步代码块");
            }
        }
    }

      Thread01的run方法调用setNameAndPassWord方法

    public class Thread01 extends Thread{
        private ThreadSynch threadSynch;
    
        public Thread01(ThreadSynch threadSynch) {
            this.threadSynch = threadSynch;
        }
    
        @Override
        public void run() {
            threadSynch.setNameAndPassWord("ls","11");
        }
    }

      测试:

    public class Test {
        public static void main(String[] args) {
            ThreadSynch threadSynch = new ThreadSynch();
            //三个线程使用同一个对象构建
            Thread thread01 = new Thread01(threadSynch);
            Thread thread02 = new Thread01(threadSynch);
            Thread thread03 = new Thread01(threadSynch);
            //三个线程同时调用该对象中的方法
            thread01.start();
            thread02.start();
            thread03.start();
        }
    }

      结果:

    Thread-1===进入同步代码块
    Thread-1===离开同步代码块
    Thread-2===进入同步代码块
    Thread-2===离开同步代码块
    Thread-0===进入同步代码块
    Thread-0===离开同步代码块

      说明:Thread-0,Thread-1,Thread-2执行到同步代码块synchronized(student)时,都会去获取与student对象关联的monitor,判断该monitor是否被别的线程所有,因为三个线程中的student都是同一个对象,所以一个线程执行的时候,与student关联的那个monitor会被当前线程所有,别的线程都会处于阻塞状态。

      稍微改一下ThreadSynch类中setNameAndPassWord的方法,添加7-9行的代码

     1 public class ThreadSynch {
     2 
     3     private Student student = new Student();
     4     private String schoolName;
     5 
     6     public void setNameAndPassWord(String name,String age){
     7         if(Thread.currentThread().getName().contains("1")){
     8             student = new Student();
     9         }
    10         synchronized(student){
    11             System.out.println(Thread.currentThread().getName() + "===" + "进入同步代码块");
    12             try {
    13                 Thread.sleep(3000);
    14                 this.student.setName(name);
    15                 this.student.setAge(age);
    16             } catch (InterruptedException e) {
    17                 e.printStackTrace();
    18             }
    19             System.out.println(Thread.currentThread().getName() + "===" + "离开同步代码块");
    20         }
    21     }
    22 }

      其余都不变,看一下结果:

    Thread-0===进入同步代码块
    Thread-1===进入同步代码块
    Thread-0===离开同步代码块
    Thread-1===离开同步代码块
    Thread-2===进入同步代码块
    Thread-2===离开同步代码块

      说明:可以看到,Thread-0和Thread-1同时进入同步代码块。分析一下原因,Thread-0执行到synchronized(student)时,会去获取与该student对象关联的monitor的所有权,该monitor没有被别的线程占有,Thread-0进入同步代码块中。Thread-1执行setNameAndPassWord方法的时候,新添加的7-9行的代码将student变量指向了一个新的student对象,此时的student对象和Thread-0时的student对象已经不是同一个了,对应的monitor也不是Thread-0时的那个monitor,所以Thread-1在Thread-0还未离开同步代码块的时候,也可以进入到同步代码块中执行。但Thread-2执行同步代码块时的student还是Thread-1时的那个student,所以Thread-2只能等到Thread-1执行结束,才能进入同步代码块中。

      所以,多个线程访问同步代码块时,只要synchronized(this对象/非this对象)中的对象是同一个对象,那么同一时间只能有一个线程可以执行同步代码块中的内容。这里注意一下当任意对象是string类型时,使用不当可能会有一些麻烦。具体就是以下两个例子:

    public class Test {
        public static void main(String[] args) {
            String str1 = "111";
            String str2 = "111";
            System.out.println(str1 == str2);
    
            String str3 = new String("222");
            String str4 = new String("222");
            System.out.println(str3 == str4);
    
        }
    }

      结果:

    true
    false

      多线程并发执行时,当synchronized(str1)由str1变成str2时,其余线程是否还会处于阻塞状态(会)。

      多线程并发执行时,当synchronized(str3)由str3变成str4时,其余线程是否还会处于阻塞状态(不会)。

      具体的string常量与new String对象的区别,参见这篇文章从为什么String=String谈到StringBuilder和StringBuffer

    参考资料:

    Java多线程5:synchronized锁方法块

  • 相关阅读:
    布隆过滤器
    django-redis 和 vue 实现的购物车
    富文本编辑器
    django 和 七牛云 交互
    django 提供的加密算法
    DRF mixins 的常用方法
    Python笔记5(作业)
    Python笔记5(random&sys&os模块)
    Python笔记5(collections模块)
    Python笔记5(时间模块)
  • 原文地址:https://www.cnblogs.com/zfyang2429/p/10567069.html
Copyright © 2020-2023  润新知