• 对象中同时有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

  • 相关阅读:
    timer.Interval用法简介
    .net 未被引用的错误
    将Sql查询语句获取的数据插入到List列表里面
    .net 里面打不出来ConfigurationManager
    .net MVC框架里怎么写控件
    数据库里面几个字符类型的区别
    怎么在.net里面解析JSON文件?
    SqlCommand.ExecuteScalar()方法
    git学习笔记
    可变参数的lambda表达式
  • 原文地址:https://www.cnblogs.com/dhcao/p/11567002.html
Copyright © 2020-2023  润新知