• Synchronized之一:基本使用


    目录:

    Java并发编程之三:volatile关键字解析 转载

    volatile之一--volatile不能保证原子性

    Synchronized之一:基本使用

    Synchronized作用

    1、Synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或者代码块。

    2、同步的作用不仅仅是互斥,它的另一个作用就是共享可变性,当某个线程修改了可变数据并释放锁后,其它线程可以获取被修改变量的最新值。如果没有正确的同步,这种修改对其它线程是不可见的。

    一、Synchronized的基本使用

      Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题。从语法上讲,Synchronized总共有四种不同的同步块:

    1.同步的实例方法(锁用的是其实例对象本身。所有的非静态同步方法执行需要顺序执行,即不能并行执行。)

    public synchronized void addTask(Task task) {
    }
    //等价
    public void addTask(Task task) {
       synchronized(this) {
       }
    }

    2.同步的静态方法(锁用的是其类对象本身。所有的静态同步方法执行需要顺序执行,即不能并行执行。)

    public static synchronized void addTask(Task task) {
    }
    //等价
    public void addTask(Task task) {
       synchronized(TaskPool.class) {
       }
    }

    3.实例方法中的同步块(锁是自己指定的,但不能是引用性对象及null对象
    4.静态方法中的同步块(锁是自己指定的,但不能是引用性对象及null对象

    静态同步方法与非静态同步方法区别
    所有的非静态同步方法用的都是同一把锁——实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。

    而所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
    实例方法中的同步块与静态方法中的同步块:
    而对于同步块,由于其锁是可以选择的,所以只有使用同一把锁的同步块之间才有着竞态条件,这就得具体情况具体分析了,但这里有个需要注意的地方,同步块的锁是可以选择的,但是不是可以任意选择的!!!!这里必须要注意一个物理对象和一个引用对象的实例变量之间的区别!使用一个引用对象的实例变量作为锁并不是一个好的选择,因为同步块在执行过程中可能会改变它的值,其中就包括将其设置为null,而对一个null对象加锁会产生异常,并且对不同的对象加锁也违背了同步的初衷!这看起来是很清楚的,但是一个经常发生的错误就是选用了错误的锁对象,因此必须注意:同步是基于实际对象而不是对象引用的!多个变量可以引用同一个对象,变量也可以改变其值从而指向其他的对象,因此,当选择一个对象锁时,我们要根据实际对象而不是其引用来考虑!作为一个原则,不要选择一个可能会在锁的作用域中改变值的实例变量作为锁对象!

     Java中多线程锁释放的条件:
    1)执行完同步代码块,就会释放锁。(synchronized)
    2)在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。(exception)
    3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进入对象的等待池。(wait)
    从上面的三点我就可以看到stop方法释放锁是在第二点的,通过抛出异常来释放锁,通过证明,这种方式是不安全的,不可靠的。
     
    使用synchronized关键字修饰方法,在jvm字节码层面并没有任何特别的指令,而是在Class文件的方法表的access_flags字段中的synchronized标志位置1,表示该方法是同步方法,并将调用该方法的对象或该方法所属的Class在jvm的内部对象作为锁对象。
    synchronized在实际应用中应该尽量减少同步代码块的范围以提升性能,方法如下:
    Object lock = new Object();
    public void queryStatus() {
       synchronized(lock) {
       }
    }

    接下来我就通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了Synchronized的使用方式不同以外,其他基本保持一致)。

    1.1、没有同步的情况:

    package com.dxz.synchronize;
    
    public class SynchronizedTest {
        public void method1() {
            System.out.println(Thread.currentThread().getName() + " Method 1 start");
            try {
                System.out.println(Thread.currentThread().getName() + " Method 1 execute");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method 1 end");
        }
    
        public void method2() {
            System.out.println(Thread.currentThread().getName() + " Method 2 start");
            try {
                System.out.println(Thread.currentThread().getName() + " Method 2 execute");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method 2 end");
        }
    
        public static void main(String[] args) {
            final SynchronizedTest test = new SynchronizedTest();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            },"thread1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            },"thread2").start();
        }
    }

    执行结果如下,线程1和线程2同时进入执行状态,线程2执行速度比线程1快,所以线程2先执行完成,这个过程中线程1和线程2是同时执行的。

    thread1 Method 1 start
    thread1 Method 1 execute
    thread2 Method 2 start
    thread2 Method 2 execute
    thread2 Method 2 end
    thread1 Method 1 end

    1.2、对普通方法同步:

    package com.dxz.synchronize;
    
    public class SynchronizedTest2 {
        public synchronized void method1() {
            System.out.println(Thread.currentThread().getName() + " Method 1 start");
            try {
                System.out.println(Thread.currentThread().getName() + " Method 1 execute");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method 1 end");
        }
    
        public synchronized void method2() {
            System.out.println(Thread.currentThread().getName() + " Method 2 start");
            try {
                System.out.println(Thread.currentThread().getName() + " Method 2 execute");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method 2 end");
        }
        
        public void method3() {
            System.out.println(Thread.currentThread().getName() + " Method 3 start");
            try {
                System.out.println(Thread.currentThread().getName() + " Method 3 execute");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method 3 end");
        }
    
        public static void main(String[] args) {
            //test2demo1();
            //test2demo2();
            test2demo3();
        }
        private static void test2demo1() {
            final SynchronizedTest2 test = new SynchronizedTest2();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            },"thread1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            },"thread2").start();
        }
        private static void test2demo2() {
            final SynchronizedTest2 test = new SynchronizedTest2();
            final SynchronizedTest2 test2 = new SynchronizedTest2();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            },"thread1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.method2();
                }
            },"thread2").start();
        }
        
        private static void test2demo3() {
            final SynchronizedTest2 test = new SynchronizedTest2();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            },"thread1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method3();
                }
            },"thread2").start();
        }
    }

    test2demo1的执行结果:

    thread1 Method 1 start
    thread1 Method 1 execute
    thread1 Method 1 end
    thread2 Method 2 start
    thread2 Method 2 execute
    thread2 Method 2 end

    test2demo2的执行结果:

    thread2 Method 2 start
    thread2 Method 2 execute
    thread1 Method 1 start
    thread1 Method 1 execute
    thread2 Method 2 end
    thread1 Method 1 end

    test2demo3的执行结果:

    thread1 Method 1 start
    thread1 Method 1 execute
    thread2 Method 3 start
    thread2 Method 3 execute
    thread2 Method 3 end
    thread1 Method 1 end

    2.1、test2demo2测试方法中说明:test和test2属于不同的对象,所以同步互不干扰,线程1和线程2会并行执行。

    2.2、通过test2demo1与test2demo3对比说明:同一个类中的所有synchronized修饰的方法是不能同时调用的,也就是说同时只能调用其中一个方法,比如线程1调用method1方法,在method1方法执行完之前,线程2调用method2方法,这个时候线程2就会阻塞,直到线程1调用完method1方法后,线程2才开始执行method2方法。(不仅仅是多个线程调用同一个同步方法)。

    2.3、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。

    3、静态方法(类)同步

    package com.dxz.synchronize;
    
    public class SynchronizedTest3 {
        public static synchronized void method1() {
            System.out.println(Thread.currentThread().getName() + " Method 1 start");
            try {
                System.out.println(Thread.currentThread().getName() + " Method 1 execute");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method 1 end");
        }
    
        public static synchronized void method2() {
            System.out.println(Thread.currentThread().getName() + " Method 2 start");
            try {
                System.out.println(Thread.currentThread().getName() + " Method 2 execute");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method 2 end");
        }
        
        public synchronized void method3() {
            System.out.println(Thread.currentThread().getName() + " Method 2 start");
            try {
                System.out.println(Thread.currentThread().getName() + " Method 2 execute");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method 2 end");
        }
    
        public static void main(String[] args) {
            //test3demo1();
            //test3demo2();
            test3demo3();
        }
    
        private static void test3demo1() {
            final SynchronizedTest3 test = new SynchronizedTest3();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            },"thread1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            },"thread2").start();
        }
        
        private static void test3demo2() {
            final SynchronizedTest3 test = new SynchronizedTest3();
            final SynchronizedTest3 test2 = new SynchronizedTest3();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            },"thread1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.method2();
                }
            },"thread2").start();
        }
        
        private static void test3demo3() {
            final SynchronizedTest3 test = new SynchronizedTest3();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            },"thread1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method3();
                }
            },"thread2").start();
        }
    }

    test3demo1执行结果:

    thread1 Method 1 start
    thread1 Method 1 execute
    thread1 Method 1 end
    thread2 Method 2 start
    thread2 Method 2 execute
    thread2 Method 2 end

    test3demo2执行结果:

    thread1 Method 1 start
    thread1 Method 1 execute
    thread1 Method 1 end
    thread2 Method 2 start
    thread2 Method 2 execute
    thread2 Method 2 end

    test3demo3执行结果:

    thread1 Method 1 start
    thread1 Method 1 execute
    thread2 Method 2 start
    thread2 Method 2 execute
    thread2 Method 2 end
    thread1 Method 1 end

    3.1、test3demo1与test3demo2对比:对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使test和test2属于不同的对象,但是它们都属于SynchronizedTest类的实例,所以也只能顺序的执行method1和method2,不能并发执行。

    3.2、test3demo2与test3demo3对比说明:实例对象上的锁与类对象的锁是两个,所以可以并行的执行method1和method3。

    4、代码块同步

    package com.dxz.synchronize;
    
    public class SynchronizedTest4 {
        public void method1(){
            System.out.println(Thread.currentThread().getName() + " Method 1 start");
            try {
                synchronized (this) {
                    System.out.println(Thread.currentThread().getName() + " Method 1 execute");
                    Thread.sleep(3000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method 1 end");
        }
    
        public void method2(){
            System.out.println(Thread.currentThread().getName() + " Method 2 start");
            try {
                synchronized (this) {
                    System.out.println(Thread.currentThread().getName() + " Method 2 execute");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Method 2 end");
        }
    
        public static void main(String[] args) {
            test4demo1();
        }
    
        private static void test4demo1() {
            final SynchronizedTest4 test = new SynchronizedTest4();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            },"thread1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            },"thread2").start();
        }
        
        private static void test4demo2() {
            final SynchronizedTest4 test = new SynchronizedTest4();
            final SynchronizedTest4 test2 = new SynchronizedTest4();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            },"thread1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.method2();
                }
            },"thread2").start();
        }
    }

    test4demo1执行结果:

    thread1 Method 1 start
    thread1 Method 1 execute
    thread2 Method 2 start
    thread1 Method 1 end
    thread2 Method 2 execute
    thread2 Method 2 end

    test4demo2执行结果:

    thread1 Method 1 start
    thread2 Method 2 start
    thread2 Method 2 execute
    thread1 Method 1 execute
    thread2 Method 2 end
    thread1 Method 1 end

    4.1、虽然线程1和线程2都进入了对应的方法开始执行,但是线程2在进入同步块之前,需要等待线程1中同步块执行完成。

     

  • 相关阅读:
    gtest(C++单元测试框架)
    tinyXML入门
    笔记 解决vue3动态绑定本地图片失效问题
    面试技巧
    vuex 状态管理
    插槽的使用
    Vue-router 路由
    Vue组件
    (转)JS 常用 DOM
    9-26
  • 原文地址:https://www.cnblogs.com/duanxz/p/4746881.html
Copyright © 2020-2023  润新知