为什么Synchronized block要使用this作同步

来源:互联网 发布:linux 命令抓包 编辑:程序博客网 时间:2024/05/22 16:55

为什么Synchronized block要使用this作同步
学Java并发编程时遇到过一个问题:为什么凡是涉及到Synchronized block的代码,总是使用synchronized(this)这样的代码。一开始很疑惑不解,既然Synchronized block可以通过获得对象的锁来使得多个线程对同一对象的访问同步,那么Synchronized block可以使用任何对象(比如成员变量)来作同步,而为什么偏偏使用this来做同步呢?后来经过一番思索想同了其中的奥妙:如果使用其他对象来做同步,会产生线程不安全。先看如下一段代码:

 

package concurrent;

 

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.TimeUnit;

 

class Testee {

    private Integer i = new Integer(1);

 

    public void f() {

        synchronized (i) {

            System.out.println(Thread.currentThread().getName() + " get i = "

                    + i);

            System.out.println(Thread.currentThread().getName()

                    + " change i = new Integer(2)");

            i = new Integer(2);

            try {

                System.out.println(Thread.currentThread().getName()

                        + " Sleep 2 sec");

                TimeUnit.SECONDS.sleep(2); // 保证Thread#2获得新的i的锁

                System.out.println(Thread.currentThread().getName()

                        + " Sleep finished");

                System.out.println(Thread.currentThread().getName()

                        + " get i = " + i + " again");

            } catch (InterruptedException e) {

            }

        }

    }

 

    public void g() {

        // 先睡1秒,保证Thread#1完成对i的重新赋值工作

        try {

            TimeUnit.SECONDS.sleep(1);

            synchronized (i) {

                System.out.println(Thread.currentThread().getName()

                        + " get i = " + i);

                System.out.println(Thread.currentThread().getName()

                        + " Sleep 2 sec");

                TimeUnit.SECONDS.sleep(2);

                System.out.println(Thread.currentThread().getName()

                        + " Sleep finished");

            }

        } catch (InterruptedException e) {

        }

    }

}

 

public class CriticalSectionTest {

 

    public static void main(String[] args) {

        final Testee testee = new Testee();

        Thread t1 = new Thread("Thread#1") {

            public void run() {

                testee.f();

            }

        };

        Thread t2 = new Thread("Thread#2") {

            public void run() {

                testee.g();

            }

        };

        ExecutorService exec = Executors.newCachedThreadPool();

        exec.execute(t1);

        exec.execute(t2);

        exec.shutdown();

    }

 

}

Output:
pool-1-thread-1 get i = 1
pool-1-thread-1 change i = new Integer(2)
pool-1-thread-1 Sleep 2 sec
pool-1-thread-2 get i = 2
pool-1-thread-2 Sleep 2 sec
pool-1-thread-1 Sleep finished
pool-1-thread-1 get i = 2 again
pool-1-thread-2 Sleep finished可以看到,Testee类的f()方法和g()方法是通过其内部成员Integer i来做同步的,在本例中f()方法和g()方法分别在Thread#1和Thread#2中调用。Thread#2先sleep 2秒以保证Thread#1首先获得i的锁,然后Thread#1对i做了一件很危险的事情,Thread#1把i指向了一个新的Integer对象,接着Thread#1就去睡了2秒。此时Thread#2醒来,立即获得了i的锁(此时的i引用的是新的对象),然后它啥事情也没做又去睡了2秒。当Thread#1醒来时,它也可以立即获得(从它在Thread#2醒来之前就能够获得i看出),因为它之前同步的i和这次获得的i不是同一个对象。结果就是,经过Thread#1一开始的给i重新赋值的操作,两个线程对i的操作都不安全了,因为实际上两个线程的synchronized block所用的对象根本就是两个对象。
所以,在很多使用synchronized block的代码中,都使用this来作同步,因为this是不可重新赋值的,也就避免了上面例子的问题。