• Java synchronized 总结


          在Java开发的时候经常会用到关键字synchronized来对代码进行同步,在使用的过程中,对于synchronized确不是很熟悉,最近在看Spring源码时,发现有不少地方都用到同步,因此,趁此机会,研究一下。

          1. synchronized锁的对象

          Java中的每一个对象都可以作为锁。

                1)对于同步方法,锁是当前实例对象。

                2)对于静态同步方法,锁是当前对象的Class对象。因为在Java 虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。

                3)对于同步方法块,锁是Synchonized括号里配置的对象。

           下面我们将一一根据代码来介绍这几种锁。

           2. 同步实例方法

                 对于同步方法,锁是当前实例对象。即在同一时刻只能有一个线程可以访问该实例的同步方法。但是请注意,如果有多个实例对象,那么不同示例之间不受影响,线程可以同时访问不同示例的同步方法。看下面这段代码:

    package com.bj.chenfeic.concurrency;
    
    /**
     * 同步测试,提供了用于同步的实例方法
     * 
     * @author chenfei0801
     * 
     */
    public class Sync {
    
        public synchronized void testSync() {
            long id = Thread.currentThread().getId();
            System.out.println("线程:" + id + "进入同步块");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + id + "退出同步块");
        }
    }
    package com.bj.chenfeic.concurrency;
    
    /**
     * 
     * @author chenfei0801
     * 
     */
    public class SyncTread implements Runnable {
    
        @Override
        public void run() {
            Sync sync = new Sync();
            sync.testSync();
        }
    
    }
    package com.bj.chenfeic.concurrency;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 
     * @author chenfei0801
     *
     */
    public class SyncTest {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
            for(int i=0;i<3;i++) {
                newFixedThreadPool.execute(new SyncTread());
            }
            newFixedThreadPool.shutdown();
            
        }
    
    }

    执行结果:

    线程:11进入同步块
    线程:13进入同步块
    线程:12进入同步块
    线程:13退出同步块
    线程:11退出同步块
    线程:12退出同步块

          从执行结果中,我们可以看到testSync并没有同步,synchronized没有生效。这是因为在SyncTherd中每次都是new Sync();这样synchronized其实是作用到不同的对象上去了,所以各个线程之间其实并没有同步。

          按照如下修改SynsThread、SyncTest代码,即可达到同步作用

    package com.bj.chenfeic.concurrency;
    
    /**
     * 
     * @author chenfei0801
     * 
     */
    public class SyncTread implements Runnable {
        private Sync sync;
        public SyncTread(Sync sync) {
            this.sync = sync;
        }
    
        @Override
        public void run() {
            this.sync.testSync();
        }
    
    }
    package com.bj.chenfeic.concurrency;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 
     * @author chenfei0801
     * 
     */
    public class SyncTest {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            Sync sync = new Sync();//只实例化一次
            ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 3; i++) {
                newFixedThreadPool.execute(new SyncTread(sync));
            }
            newFixedThreadPool.shutdown();
    
        }
    
    }

    执行结果

    线程:11进入同步块
    线程:11退出同步块
    线程:12进入同步块
    线程:12退出同步块
    线程:13进入同步块
    线程:13退出同步块

          从上面结果中可以看出,SyncThread中使用的是同一个对象,此时线程是互斥访问testSync()方法的,即达到了同步的作用。

    3、同步静态方法

            对于静态方法,锁的对象不再是类的某个实例,而是类对象(Class)。因为在Java 虚拟机中一个类只能对应一个类对象,所以在同一时刻只允许一个线程执行该类中的静态同步方法。因此它的同步可以认为是全局的。如下面的例子所示:
            
    package com.bj.chenfeic.concurrency;
    
    /**
     * 同步测试,提供了用于同步的实例方法
     * 
     * @author chenfei0801
     * 
     */
    public class Sync {
    
        //同步静态方法块
        public synchronized static void testSync() {
            long id = Thread.currentThread().getId();
            System.out.println("线程:" + id + "进入同步块");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + id + "退出同步块");
        }
    }
    package com.bj.chenfeic.concurrency;
    
    /**
     * 
     * @author chenfei0801
     * 
     */
    public class SyncTread implements Runnable {
    
    	@Override
    	public void run() {
    		// Sync.testSync();
    new Sync().testSync();//为了说明同步机制,我们此处先实例化对象。正常调用应该还是按照上行那样处理。 } }

      

    package com.bj.chenfeic.concurrency;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 
     * @author chenfei0801
     * 
     */
    public class SyncTest {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 3; i++) {
                newFixedThreadPool.execute(new SyncTread());
            }
            newFixedThreadPool.shutdown();
    
        }
    
    }

    执行结果:

    线程:12进入同步块
    线程:12退出同步块
    线程:13进入同步块
    线程:13退出同步块
    线程:11进入同步块
    线程:11退出同步块

           从执行结果上,可以看到线程是同步访问代码块的。为了说明我们在SyncTread中每次都通过new的方式,实例化了新的对象。虽然在每个线程中,每个Sync对象都不相同,但是由于同步的是静态方法,因此所锁作用在所有Sync对象上。

         
    4、同步方法块

            除了将对整个方法加锁进行同步外,Java还支持对方法内部的方法快进行同步,这样做个人觉得有两个好处

             1)一个方法可能还含有不需要同步的部分,而且有可能这个不需要同步的代码执行过程很耗时,这样就会影响其他线程执行,严重影响效率。

    package com.bj.chenfeic.concurrency;
    
    /**
     * 同步测试,提供了用于同步的实例方法
     * 
     * @author chenfei0801
     * 
     */
    public class Sync {
    
        public void testSync() {
            long id = Thread.currentThread().getId();
            synchronized (this) {
                System.out.println("线程:" + id + "进入同步块");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + id + "退出同步块");
            }
            try {
                Thread.sleep(10000);//耗时操作,未同步
                System.out.println("线程 "+ id +" 未同步部分");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }
    package com.bj.chenfeic.concurrency;
    
    /**
     * 
     * @author chenfei0801
     * 
     */
    public class SyncTread implements Runnable {
        private Sync sync;
        
        public SyncTread(Sync sync) {
            this.sync = sync;
        }
        
        public SyncTread() {
            this.sync = new Sync();//不传参数时每次实例化一个对象
        }
    
        @Override
        public void run() {
             this.sync.testSync();
        }
    
    }
    package com.bj.chenfeic.concurrency;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 
     * @author chenfei0801
     * 
     */
    public class SyncTest {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            Sync sync = new Sync();
            ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 3; i++) {
                newFixedThreadPool.execute(new SyncTread(sync));//同一个sync对象
            }
            newFixedThreadPool.shutdown();
    
        }
    
    }

    执行结果:

    线程:11进入同步块
    线程:11退出同步块
    线程:13进入同步块
    线程:13退出同步块
    线程:12进入同步块
    线程:12退出同步块
    线程 11 未同步部分
    线程 13 未同步部分
    线程 12 未同步部分

         从执行结果中可以看出,synchronized同步的代码块都是互斥访问的,对于耗时的操作则可以异步执行。提高了效率。注意的是,例子中synchronized后面括号中为this。说明锁是加在当前Sync实例对象上的。如果SyncTread中Sync对象不是同一个,则各个对象之间的代码不会同步。

        上面的例子中,锁对象为当前的对象实例(this)。跟我们在“同步实例方法”中遇到一个同样的问题,如果每个线程都是重新实例化了一个对象,那么这些锁都会加在不同的实例对象上,这样此时每个对象之间都是互相独立的,这样就达不到同步的目的了。

        修改SyncTest类的代码:

    package com.bj.chenfeic.concurrency;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 
     * @author chenfei0801
     * 
     */
    public class SyncTest {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 3; i++) {
                newFixedThreadPool.execute(new SyncTread());//没有传递任何参数
            }
            newFixedThreadPool.shutdown();
    
        }
    
    }
    SyncTread在实例化时如果没有传递参数,则每次都会实例化一个Sync对象。

    运行结果如下:

    线程:11进入同步块
    线程:12进入同步块
    线程:13进入同步块
    线程:11退出同步块
    线程:13退出同步块
    线程:12退出同步块
    线程 12 未同步部分
    线程 13 未同步部分
    线程 11 未同步部分

         此时该如何处理?我们在“同步静态方法”中提到,静态方法之所以是全局的,是因为锁的对象不再是类的某个实例,而是整个类对象(Class)。因此我们在对方法块处理时,也可以同样的处理。

         在上述代码的基础上修改Sync类

    package com.bj.chenfeic.concurrency;
    
    /**
     * 同步测试,提供了用于同步的实例方法
     * 
     * @author chenfei0801
     * 
     */
    public class Sync {
    
        public void testSync() {
            long id = Thread.currentThread().getId();
            synchronized (Sync.class) {//全局锁
                System.out.println("线程:" + id + "进入同步块");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + id + "退出同步块");
            }
            try {
                Thread.sleep(10000);//耗时操作
                System.out.println("线程 "+ id +" 未同步部分");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }

       执行结果为

    线程:11进入同步块
    线程:11退出同步块
    线程:12进入同步块
    线程:12退出同步块
    线程:13进入同步块
    线程:13退出同步块
    线程 11 未同步部分
    线程 12 未同步部分
    线程 13 未同步部分

        从执行结果上可以看出,各个线程虽然都实例化了各自的Sync对象,但是却是在互斥访问Sync中的同步块。

         2)同步方法块,除了对可以减少同步区域,还可以减小锁的范围,在对方法进行同步时,同步的对象都是对象的实例或者类对象。如果一个类有多个同步方法,如果当前线程进入了其中一个方法里,这个类的其他方法此时也被锁住了,其他线程无法进入。一般情况下,可能没有问题,但是如果如果这些方法或者部分方法之间其实并没有什么关系,那么互斥访问显然会影响效率,看下面的例子:

          

    package com.bj.chenfeic.concurrency;
    
    /**
     * 同步测试,提供了用于同步的实例方法
     * 
     * @author chenfei0801
     * 
     */
    public class Sync {
    
        /** Synchronization monitor for the "active" flag 。仅仅用于同步*/
        private final Object activeMonitor = new Object();
    
        /** Synchronization monitor for the "refresh" and "destroy"仅仅用于同步 */
        private final Object startupShutdownMonitor = new Object();
        
        
        public void refresh() {
            long id = Thread.currentThread().getId();
            synchronized (this.startupShutdownMonitor) {
                System.out.println("线程:" + id + "进入同步块--refresh()");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + id + "退出同步块-----refresh()");
            }
    
        }
        
    
        public void destroy() {
            long id = Thread.currentThread().getId();
            synchronized (this.startupShutdownMonitor) {
                System.out.println("线程:" + id + "进入同步块--destroy()");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + id + "退出同步块-----destroy()");
            }
    
        }
    
        public void active() {
            long id = Thread.currentThread().getId();
            synchronized (this.activeMonitor) {
                System.out.println("线程:" + id + "进入同步块--active()");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + id + "退出同步块-----active()");
            }
    
        }
        
    }

         类Sync中有三个同步的方法,分别是refresh,destory,active。其中前两者是用的是同一个对象锁(Sync的startupShutdownMonitor对象),后者是另外的对象锁,现在我们看下测试的类。

    package com.bj.chenfeic.concurrency;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 
     * @author chenfei0801
     * 
     */
    public class SyncTest {
        
        /**
         * @param args
         */
        public static void main(String[] args) {
            Sync sync = new Sync();
            SyncTest syncTest = new SyncTest();
            ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
            newFixedThreadPool.execute(syncTest.new ActiveThread(sync));
            newFixedThreadPool.execute(syncTest.new RefreshThread(sync));
            newFixedThreadPool.execute(syncTest.new DestoryThread(sync));
            newFixedThreadPool.execute(syncTest.new ActiveThread(sync));
            newFixedThreadPool.shutdown();
        }
    
        class RefreshThread implements Runnable {
    
            private Sync sync;
    
            public RefreshThread(Sync sync) {
                this.sync = sync;
            }
    
            @Override
            public void run() {
                sync.refresh();
            }
    
        }
    
        class DestoryThread implements Runnable {
    
            private Sync sync;
    
            public DestoryThread(Sync sync) {
                this.sync = sync;
            }
    
            @Override
            public void run() {
                sync.destroy();
            }
    
        }
    
        class ActiveThread implements Runnable {
    
            private Sync sync;
    
            public ActiveThread(Sync sync) {
                this.sync = sync;
            }
    
            @Override
            public void run() {
                sync.active();
            }
    
        }
    
    }

    在测试类中,有3类线程,分布式调用Sync中的refreash,destory,active。在测试类中,启动了4个线程,我们看一下执行结果

    线程:11进入同步块--active()
    线程:13进入同步块--destroy()
    线程:11退出同步块-----active()
    线程:14进入同步块--active()
    线程:13退出同步块-----destroy()
    线程:12进入同步块--refresh()
    线程:14退出同步块-----active()
    线程:12退出同步块-----refresh()

          从执行结果中,可以看出active之间是同步的,但是与destory、refresh之间却没有同步。线程11在在active方法时,线程13可以执行destory方法。

    destory方法和refresh是同步的。

          由此可见,通过对方法内部的方法快进行同步,可以灵活的决定同步块的范围和相应的锁对象。

    最后,一定要记住synchronized锁住的是括号里的对象,而不是代码(方法或者方法快)

  • 相关阅读:
    Distributed System
    APP专项测试之兼容性测试
    面试如何正确谈薪?
    跳槽季,如何做好技术面试准备?
    Appium基础
    设置安装程序位置
    获取文件版本号
    28-PV&PVC
    Docker-文件拷贝
    Kubemark压测常用命令
  • 原文地址:https://www.cnblogs.com/chenfei0801/p/3716869.html
Copyright © 2020-2023  润新知