对象中同时有2个方法使用synchorized修饰

问:当一个对象中有2个方法同时用synchronized修饰,那么当线程一在访问方法1时,其他线程是否可以访问方法二?

答案:由于对象的内置锁(监视器锁)是唯一的,所以当线程一在访问对象的方法1时,持有了该对象的内置锁,那么再线程一释放该内置锁之前,其他线程是无法获取该对象内置锁,所以其他线程无法访问方法二。

验证一:只有方法一使用synchronized修饰时,其他线程可以随意访问方法二。

/**
 * 我们测试,如果统一个对象,有2个方法都使用synchronized。
 * 验证:每个对象有唯一的对象锁;(称为:内置锁或监视器锁)
 * 那么当线程一在访问方法一时,已经持有方法该对象锁。
 * 其他线程若想执行方法二,必须等待线程一释放该对象锁。
 * @Author: dhcao
 * @Version: 1.0
 */
public class SynchronizedFac {

    public synchronized void methodOne() throws Exception {
        String threadName = Thread.currentThread().getName();
        Thread.sleep(3000);
        System.out.println(threadName + "  执行方法1");
    }

    /**
     * 第一次测试,方法二不加锁,我们预期:
     * 当线程一执行方法1时,其他线程是可以访问方法二的。
     */
    public void methodTwo() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "  执行方法2");
    }


    public static void main(String[] args) throws Exception{
				// 保证对象相同
        final SynchronizedFac fac = new SynchronizedFac();

        for (int i = 0; i < 5; i++) {
            // 我们让线程一来访问方法一
            if (i == 0) {
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            fac.methodOne();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }, "thread -- " + i).start();
            }else{
                // 其他线程则访问方法二
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            fac.methodTwo();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }, "thread -- " + i).start();
            }

        }
    }
}

运行结果:由于方法1中有线程等待,所以其他线程率先执行完了方法二

thread -- 1 执行方法2
thread -- 2 执行方法2
thread -- 3 执行方法2
thread -- 4 执行方法2
thread -- 0 执行方法1

验证二:方法一和方法二同时使用synchronized修饰时,必须等线程一释放对象锁之后其他线程才能获取锁访问方法二。

package org.dhcao.relax.synchronizedOneInstance;

/**
 * 我们测试,如果统一个对象,有2个方法都使用synchronized。
 * 验证:每个对象有唯一的对象锁;(称为:内置锁或监视器锁)
 * 那么当线程一在访问方法一时,已经持有方法该对象锁。
 * 其他线程若想执行方法二,必须等待线程一释放该对象锁。
 * @Author: dhcao
 * @Version: 1.0
 */
public class SynchronizedFac {

    public synchronized void methodOne() throws Exception {
        String threadName = Thread.currentThread().getName();
        Thread.sleep(3000);
        System.out.println(threadName + "  执行方法1");
    }

    /**
     * 第二次测试,方法二加锁,我们预期:
     * 当线程一执行方法1时,其他线程是不可以访问方法二的;
     * 必须等待线程一释放锁
     */
    public synchronized void methodTwo() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "  执行方法2");
    }


    public static void main(String[] args) throws Exception{

        final SynchronizedFac fac = new SynchronizedFac();

        for (int i = 0; i < 5; i++) {
            // 我们让线程一来访问方法一
            if (i == 0) {
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            fac.methodOne();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }, "thread -- " + i).start();
            }else{
                // 其他线程则访问方法二
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            fac.methodTwo();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }, "thread -- " + i).start();
            }

        }
    }
}

执行结果:在线程1(thread — 0)访问方法1时,该对象锁已经被线程一持有,则其他线程需等待该线程退出方法一,释放对象锁,才能进入被synchronized修饰的方法;

thread -- 0 执行方法1
thread -- 4 执行方法2
thread -- 3 执行方法2
thread -- 2 执行方法2
thread -- 1 执行方法2

应用:在属性的get和set方法上面加synchronized,那么这个属性就是线程安全的

/**
 * 这是一个线程安全类
 * @Author: dhcao
 * @Version: 1.0
 */
public class SynchronizedInteger {
    
    /** 定义属性 */
    private int value;
    
    public synchronized int getValue() {
        return value;
    }

    public synchronized void setValue(int value) {
        this.value = value;
    }
}

若是没有前面的所说的synchronized的使用方法,那么有可能会认为这个类是线程不安全的。但是现在可知:当有线程在调用setValue方法时,由于它持有了对象的内置锁,那么其他线程是无法通过getValue方法访问到value值的。只有在set方法调用完成,value值更新,线程释放内置锁后,其他线程才能调用get方法,这时,value值已经更新了!

思考:根据上述内容,我们能总结的说:加锁(synchronized)不仅仅保证执行顺序同步,还保证了内存的可见性。

​ 首先对内存的可见性要有一定的了解:

https://www.cnblogs.com/dhcao/p/10982278.html

工作线程和主内存的关系如上篇博客(虽然其他写得不太好,但是这个内存关系应该容易理解)。分别有x、y、z三条线程分别从主线程中读取数据a=1。那么对于线程x、y、z来说,a=1是独立的。即,当线程x执行a=2,那么线程y和线程z会知道a已经更新成为2了么。答案是不知道,因为在x执行a=2之前,线程y和z已经从主内存中加载了a=1这个信息。那么这是称为线程x和线程y、z的内存是不可见的!即:工作线程之间内存不可互相察觉!

​ 但是通过上述分析我们可知,如果变量使用了synchronized来修饰。那么得到的内存结果是:当线程x在读取主内存中a=1信息时,由于对象锁被线程x持有,那么线程x、y只能等待,而不能从主内存中加载a=1这个信息。当x线程执行完set方法:a=2。然后将a=2这个值写到主内存中去,再释放对象的内置锁,此时线程x、y才能读取到主内存中的a的值,读取的结果是a=2。

​ 这个现象我们可以理解为:线程x的工作内存将a=1修改为a=2。并且线程x、y知晓了这个修改。可以叫做线程x的内存对于线程y和z是可见的!

扩展:对象锁跟类锁有什么区别?

​ 如上文,都是使用的同一个实例来执行。如果使用不同的实例,new出不同的对象来,那么上述顺序还能保证么。

答案肯定是不能,对象锁的作用域只在该对象中

扩展一:不同对象使用synchronized修饰的2个方法是否能保证同步?

package org.dhcao.relax.synchronizedOneInstance;

/**
 * @Author: dhcao
 * @Version: 1.0
 */
public class SynchronizedFac {

    public synchronized void methodOne() throws Exception {
        String threadName = Thread.currentThread().getName();
        Thread.sleep(3000);
        System.out.println(threadName + "  执行方法1");
    }

    public synchronized void methodTwo() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "  执行方法2");
    }

    public static void main(String[] args) throws Exception{
			// 不再使用统一个对象
//        final SynchronizedFac fac = new SynchronizedFac();

        for (int i = 0; i < 5; i++) {
            // 我们让线程一来访问方法一
            if (i == 0) {
                new Thread(new Runnable() {
                    public void run() {
                        try {
                          	// 每次new一个对象来执行
                            new SynchronizedFac().methodOne();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }, "thread -- " + i).start();
            }else{
                // 其他线程则访问方法二
                new Thread(new Runnable() {
                    public void run() {
                        try {
                         	 // 每次new一个对象来执行
                            new SynchronizedFac().methodTwo();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }, "thread -- " + i).start();
            }

        }
    }
}

运行结果:如我们所料,synchornized在方法上默认使用对象的内置锁,当不同的对象时,自然不能保证同步。

thread -- 1 执行方法2
thread -- 2 执行方法2
thread -- 3 执行方法2
thread -- 4 执行方法2
thread -- 0 执行方法1

扩展二:不同对象使用synchronized修饰的2个方法如何能保证同步?

​ 如上所说,不同对象处理2个方法时,使用对象的内置锁必然是失效的,那么如果我就要保证同步呢,那么我们可以看如下代码:使用类锁(class)

public class SynchronizedFac {

  	/**
  	 *使用锁:this.getClass
  	 */
    public void methodOne() throws Exception {
        synchronized (this.getClass()){
            String threadName = Thread.currentThread().getName();
            Thread.sleep(3000);
            System.out.println(threadName + "  执行方法1");
        }

    }

    /**
     * 使用锁:this.getClass
     */
    public void methodTwo() {
        synchronized (this.getClass()){
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "  执行方法2");
        }

    }


    public static void main(String[] args) throws Exception{

        for (int i = 0; i < 5; i++) {
            // 我们让线程一来访问方法一
            if (i == 0) {
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            new SynchronizedFac().methodOne();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }, "thread -- " + i).start();
            }else{
                // 其他线程则访问方法二
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            new SynchronizedFac().methodTwo();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                }, "thread -- " + i).start();
            }

        }
    }
}

运行结果:如我们所料,依然能保证同步,因为此时锁使用的是class的对象锁,就算不同的实例,那么class对象依然是相同的!当然还可以用其他的锁,只要能保证使用同一个锁

thread -- 0 执行方法1
thread -- 4 执行方法2
thread -- 3 执行方法2
thread -- 2 执行方法2
thread -- 1 执行方法2

posted @ 2019-09-22 12:38  undifinedException  阅读(733)  评论(0编辑  收藏  举报