• synchronized同步引发的思考


      最近公司某同事非常爱学,下班回家后也会抱书学习,看到多线程写例子的时候遇到了非常奇怪的问题,故而将例子发给我看让给解答,下面给出例子。

      1.第一例及运行结果

        下面是示例代码

     1 package com.coderweb.sys.util;
     2 
     3 public class TxtThread implements Runnable {
     4 
     5     Integer num = 10000;
     6     String str = new String();
     7 
     8     @Override
     9     public void run() {
    10         synchronized (num) {
    11             while (true) {
    12                 if (num > 0) {
    13                     try {
    14                         // Thread.sleep(10);
    15                     } catch (Exception e) {
    16                         e.getMessage();
    17                     }
    18                     System.out.println(Thread.currentThread().getName()
    19                             + " this is " + num--);
    20 
    21                     // str+="1";
    22                 } else {
    23                     break;
    24                 }
    25 
    26             }
    27         }
    28     }
    29 
    30     public static void main(String[] args) {
    31         TxtThread tt = new TxtThread();
    32         new Thread(tt).start();
    33         new Thread(tt).start();
    34         new Thread(tt).start();
    35         new Thread(tt).start();
    36     }
    37 }
    View Code

         下面是运行截图的一部分

    Thread-0 this is 10000
    Thread-2 this is 9999
    Thread-0 this is 9998
    Thread-2 this is 9997
    Thread-3 this is 9995
    Thread-0 this is 9996
    Thread-3 this is 9993
    Thread-3 this is 9991
    Thread-3 this is 9990
    Thread-3 this is 9989
    Thread-3 this is 9988
    Thread-3 this is 9987
    Thread-3 this is 9986
    Thread-3 this is 9985

       问题一:Integer不是对象吗?对象引用不变的呀?修改值后应该不变的呀?为啥起了四个线程后,四个线程都对一个对象里的值进行修改了呢?为啥synchronized语句块没有起到任何作用呢?

       带着问题一,修改例子

       2.第二例及运行结果(唯一不同的地方是,synchronized(中的内容))

     1 package com.coderweb.sys.util;
     2 
     3 public class TxtThread implements Runnable {
     4 
     5     Integer num = 10000;
     6     String str = new String();
     7 
     8     @Override
     9     public void run() {
    10         synchronized (str) {
    11             while (true) {
    12                 if (num > 0) {
    13                     try {
    14                         // Thread.sleep(10);
    15                     } catch (Exception e) {
    16                         e.getMessage();
    17                     }
    18                     System.out.println(Thread.currentThread().getName()
    19                             + " this is " + num--);
    20 
    21                     // str+="1";
    22                 } else {
    23                     break;
    24                 }
    25 
    26             }
    27         }
    28     }
    29 
    30     public static void main(String[] args) {
    31         TxtThread tt = new TxtThread();
    32         new Thread(tt).start();
    33         new Thread(tt).start();
    34         new Thread(tt).start();
    35         new Thread(tt).start();
    36     }
    37 }
    View Code

        运行结果部分

    Thread-0 this is 10000
    Thread-0 this is 9999
    Thread-0 this is 9998
    Thread-0 this is 9997
    Thread-0 this is 9996
    Thread-0 this is 9995

    .........

    Thread-0 this is 5
    Thread-0 this is 4
    Thread-0 this is 3
    Thread-0 this is 2
    Thread-0 this is 1

    通过问题2,可以得出总结,对于String字符串在初始化后,其引用地址没有发生变化,后面也没有进行修改,因此多个线程同时访问的时候起到了互斥的作用,当四个线程启动后,哪个线程先进入代码块进行了加锁,谁将一直持有该锁直到该线程结束,其余线程发现有线程持有该string的锁,将处于等待状态,因此结论便是,这四个线程,哪个线程先进入同步快,将一直打印该线程的数据。

               由问题1跟2的不同运行结果发现,区别之处在于第一例子中的synchronized是num,并在后面进行了减法操作,而第二个例子中的synchronized是str,并且该str没有发生变化,难道是因为num改变之后引用地址发生变化了?下面给出思考问题的验证例子3跟4

    3.第三例及运行结果

    package com.coderweb.sys.util;
    
    public class TxtThread implements Runnable {
    
        Integer num = 10000;
        String str = new String();
        Integer testI = 0;
    
        @Override
        public void run() {
            synchronized (testI) {
                while (true) {
                    if (num > 0) {
                        try {
                            // Thread.sleep(10);
                        } catch (Exception e) {
                            e.getMessage();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + " this is " + num--);
    
                        // str+="1";
                    } else {
                        break;
                    }
    
                }
            }
        }
    
        public static void main(String[] args) {
            TxtThread tt = new TxtThread();
            new Thread(tt).start();
            new Thread(tt).start();
            new Thread(tt).start();
            new Thread(tt).start();
        }
    }
    View Code

        运行结果

    Thread-0 this is 10000
    Thread-0 this is 9999
    Thread-0 this is 9998
    Thread-0 this is 9997
    Thread-0 this is 9996
    Thread-0 this is 9995
    Thread-0 this is 9994
    Thread-0 this is 9993

    。。。。。。

    Thread-0 this is 7
    Thread-0 this is 6
    Thread-0 this is 5
    Thread-0 this is 4
    Thread-0 this is 3
    Thread-0 this is 2
    Thread-0 this is 1

      该例子的不同之处在于,新加了一个成员变量testI,并且没有对该值进行操作,发现结果居然成功,只有一个线程持有锁,这也就验证了Integer类型的确是引用类型,在

    创建完成后的引用地址没有发生变化。那么猜想string如果内容变了会怎样呢?例子4进行验证

      4.第四例及运行结果

     1 package com.coderweb.sys.util;
     2 
     3 public class TxtThread implements Runnable {
     4 
     5     Integer num = 10000;
     6     String str = new String();
     7 
     8     @Override
     9     public void run() {
    10         synchronized (str) {
    11             while (true) {
    12                 if (num > 0) {
    13                     try {
    14                         // Thread.sleep(10);
    15                     } catch (Exception e) {
    16                         e.getMessage();
    17                     }
    18                     System.out.println(Thread.currentThread().getName()
    19                             + " this is " + num--);
    20 
    21                      str+="1";
    22                 } else {
    23                     break;
    24                 }
    25 
    26             }
    27         }
    28     }
    29 
    30     public static void main(String[] args) {
    31         TxtThread tt = new TxtThread();
    32         new Thread(tt).start();
    33         new Thread(tt).start();
    34         new Thread(tt).start();
    35         new Thread(tt).start();
    36     }
    37 }
    View Code

      运行部分结果

         .............................

    Thread-3 this is 9774
    Thread-2 this is 9779
    Thread-3 this is 9773
    Thread-0 this is 9777
    Thread-3 this is 9771
    Thread-3 this is 9769
    Thread-3 this is 9768
    Thread-2 this is 9772
    Thread-3 this is 9767
    Thread-0 this is 9770
    Thread-3 this is 9765
    Thread-2 this is 9766
    Thread-3 this is 9763
    Thread-0 this is 9764
    Thread-3 this is 9761

    ..............................

    该例子的不同之处在于,在循环最后不停的对str进行修改,所以导致了多个线程同时访问,并没有起到加锁的作用。

     

    但是我们的都知道,string类型变量是不可变的,也就是所说的immutable,就是说在对象创建之后,该string的引用类型变量是不变的,如果对该变量进行修改操作之后,会重新建立对象,并将新对象的地址赋给该引用,也就是说例子中的不停的修改str对象就相当于不停的创建新对象并赋给该引用。这个例子还好理解,毕竟我们对string还稍微有点了解,但是为什么Integer也会有这样的效果呢,难道我们对Integer进行了修改之后起引用地址也发生了变化?下面查看了jdk关于Integer封装类的源码

     

    5.JDK中关于Integer的部分源码

    public final class Integer extends Number implements Comparable<Integer> {
        
    
         /**
         * The value of the <code>Integer</code>.
         *
         * @serial
         */
        private final int value;
    
    /**
         * Compares this object to the specified object.  The result is
         * <code>true</code> if and only if the argument is not
         * <code>null</code> and is an <code>Integer</code> object that
         * contains the same <code>int</code> value as this object.
         *
         * @param   obj   the object to compare with.
         * @return  <code>true</code> if the objects are the same;
         *          <code>false</code> otherwise.
         */
        public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
        }
    
    
     /**
         * Returns a hash code for this <code>Integer</code>.
         *
         * @return  a hash code value for this object, equal to the 
         *          primitive <code>int</code> value represented by this 
         *          <code>Integer</code> object. 
         */
        public int hashCode() {
        return value;
        }

      观察上面的源码我们便能明白道理了,在Integer封装类中,利用了一个final的int类型,也就是说一旦对象创建,该值便不能改变了,但是为啥我们还能对其进行修改呢,所以必定是我们修改了之后,会创建新的地址,并赋给新的引用,我们先通过下面例子验证一把是否引用地址发生了变化

      

     1 public static void main(String[] args) {
     2 //        TxtThread tt = new TxtThread();
     3 //        new Thread(tt).start();
     4 //        new Thread(tt).start();
     5 //        new Thread(tt).start();
     6 //        new Thread(tt).start();
     7         
     8         Integer number = 5;
     9         Integer number2 = number;
    10         number2--;
    11         System.out.println("number---"+number);
    12         System.out.println("number2---"+number2);
    13         System.out.println("number ==number2? "+(number==number2));
    14     }
    View Code

      这个例子中,我们定义了第一个对象,这个时候第一个对象地址没有发生变化,这时我们创建了新对象,并指向第一个对象,这时候两个对象的引用地址是一样的,紧接着我们对第二个对象进行了修改,当然其值是发生了变化,其实我们可以想一下,如果地址没有发生变化的话,5是怎么等于4的呢?所以地址必然不一样,最后的false也就验证了这一点。当然咱通过Integer的源代码发现,其equals方法也是通过判断其中的值类判断两个Integer是否相等的。

        综上所有事例得出结论:Integer这类对于基本数据类型的封装类,当其值发生改变时,其引用地址也发生了变化。

      

  • 相关阅读:
    CSS和Js样式属性的对照关系
    CSS选择器
    主成分分析(PCA)核心思想
    线性变换的本质
    java 滤镜实现
    Spring Boot工程发布到Docker
    解决maven的报错
    spring boot初探
    WPF的Page介绍及Page Window Frame 之间的链接使用示例,嵌套问题
    浅谈WPF页间导航
  • 原文地址:https://www.cnblogs.com/lixiaojiao-hit/p/4637978.html
Copyright © 2020-2023  润新知