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 }

 

 

下面是运行截图的一部分

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 }

 

 

运行结果部分

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();
    }
}

 

 

运行结果

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 }

 

 

运行部分结果

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

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     }

 

 

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


 

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

全部评论
666
点赞
送花
回复
分享
发布于 2018-09-21 11:34

相关推荐

点赞 2 评论
分享
牛客网
牛客企业服务