美文网首页
(5)synchronized关键字

(5)synchronized关键字

作者: 一个菜鸟JAVA | 来源:发表于2020-06-30 20:57 被阅读0次

    引出

    下面代码段,使用两个线程执行,每个线程执行1000次,每次执行i++

    public class App1 implements Runnable{
        static Integer i = 0;
    
        public void add(){
            i++;
        }
    
        @Override
        public void run() {
            for (int j = 0; j < 1000; j++) {
                add();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            App1 app1 = new App1();
            Thread t1 = new Thread(app1);
            Thread t2 = new Thread(app1);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("i = " + i);
        }
    }
    

    最后i的结果并不是我们想象中的2000.那么导致这种情况的发生主要是因为多个线程,对同一个共享变量进行修改,就可能会引发线程不安全.因为i++并不是一个原子操作,它由多个操作组成.要解决这个问题,最简单的方法就是使用synchronized修饰add()方法.
    当使用synchronized修饰add()方法时,线程在执行add()方法时,需要先获得app1这个实例的对象锁,只有获得这个锁,才能执行add()方法内部的逻辑.所以保证了,在多线程并发的情况下,每次只能有一个线程执行i++,这样就保证了线程的安全性.

    三种应用方式

    作用在实例方法

    当使用synchronized修饰实例方法时,它的锁就在当前这个实例上.下面演示一个错误的例子.

    public class App2 implements Runnable{
        static Integer i = 0;
    
        public synchronized void add(){
            i++;
        }
    
        @Override
        public void run() {
            for (int j = 0; j < 1000; j++) {
                add();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            App2 app1 = new App2();
            App2 app2 = new App2();
            Thread t1 = new Thread(app1);
            Thread t2 = new Thread(app2);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("i = " + i);
        }
    }
    

    上面代码中,add()方法已经使用synchronized,但是最后结果还是错误.因为app1app2不是同一实例,t1和t2在执行add()方法时,各自获得了app1app2实例的锁,而不是同一实例,所以synchronized失效.修改代码如下:

    public static void main(String[] args) throws InterruptedException {
            App2 app1 = new App2();
            Thread t1 = new Thread(app1);
            Thread t2 = new Thread(app1);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("i = " + i);
        }
    

    作用在静态方法

    当使用synchronized修饰静态方法时,其锁就是当前类的class对象锁.

    public class App3 implements Runnable{
        static Integer i = 0;
    
        public static synchronized void add(){
            i++;
        }
    
        @Override
        public void run() {
            for (int j = 0; j < 100000; j++) {
                add();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            App3 app1 = new App3();
            App3 app2 = new App3();
            Thread t1 = new Thread(app1);
            Thread t2 = new Thread(app2);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("i = " + i);
    
        }
    }
    

    上面代码不管运行多少次,最后的答案都是200000.可以看见,我们使用的是两个不同的实例,分别为app1app2,如果是作用在实例方法上,那么一定会有线程安全问题.但是现在是作用于静态方法,是类的class对象锁,所以是线程安全的.如何证明?看下面实例代码:

    public class App3 implements Runnable{
        static Integer i = 0;
    
        public static synchronized void add(){
            i++;
        }
    
        @Override
        public void run() {
            for (int j = 0; j < 100000; j++) {
                add();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            App3 app1 = new App3();
            App3 app2 = new App3();
            Thread t1 = new Thread(app1);
            Thread t2 = new Thread(app2);
            Thread t3 = new Thread(() -> {
                for (int j = 0; j < 100000; j++) {
                    i++;
                }
            });
            t1.start();
            t2.start();
            t3.start();
            t1.join();
            t2.join();
            t3.join();
            System.out.println("i = " + i);
    
        }
    }
    

    新增加一个线程,没有使用synchronized修饰i++,结果可想而知,可能会小于300000.如果上述条件成立,修改代码如下,对i++使用synchronized修饰,使用App3.class做同步对象.

        public static void main(String[] args) throws InterruptedException {
            App3 app1 = new App3();
            App3 app2 = new App3();
            Thread t1 = new Thread(app1);
            Thread t2 = new Thread(app2);
            Thread t3 = new Thread(() -> {
                for (int j = 0; j < 100000; j++) {
                    synchronized (App3.class){
                        i++;
                    }
                }
            });
            t1.start();
            t2.start();
            t3.start();
            t1.join();
            t2.join();
            t3.join();
            System.out.println("i = " + i);
        }
    

    经过测试,每次结果都是300000,说明synchronized作用在静态方法上,同步的锁为类class对象锁.

    synchronized同步代码块

    有些时候我们并不需要把整个方法都同步,我们只有一小部分代码需要同步.如果整个方法使用synchronized同步,能保证线程安全,但是效率比较低.所以我们只需要同步代码块就行.使用方法入下代码所示:

    public class App4 implements Runnable{
        static int i = 0;
        private final Object o;
    
        public App4(Object o) {
            this.o = o;
        }
    
        public void add(){
            System.out.println("不需要同步的费时代码块1");
            synchronized (o){
                i++;
            }
            System.out.println("不需要同步的费时代码块2");
        }
    
        @Override
        public void run() {
            for (int j = 0; j < 1000; j++) {
                add();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Object lock = new Object();
            App4 app1 = new App4(lock);
            App4 app2 = new App4(lock);
    
            Thread t1 = new Thread(app1);
            Thread t2 = new Thread(app2);
    
            t1.start();
            t2.start();
            t1.join();
            t2.join();
    
            System.out.println("i = " + i);
    
        }
    }
    

    从代码看出,将synchronized作用于一个给定的实例对象lock,即当前实例对象就是锁对象.每次线程想进入代码块中都必须先获取lock实例的对象锁.

    可重入性和不可中断性

    可重入性

    可重入性是指同一线程在外层函数获得锁之后,内层函数可以直接再次获取该锁.而不需要释放当前锁,再去重新获取该锁.

    public class App5 {
        private final Object o;
    
        public App5(Object o) {
            this.o = o;
        }
    
        public void method1(){
            synchronized (o){
                System.out.println(Thread.currentThread().getName()+":执行method1");
                method2();
            }
        }
    
        public void method2(){
            synchronized (o){
                System.out.println(Thread.currentThread().getName()+":执行method2");
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            App5 app = new App5(new Object());
    
            Thread t1 = new Thread(app::method1);
            Thread t2 = new Thread(app::method1);
            Thread t3 = new Thread(app::method1);
    
            t1.start();
            t2.start();
            t3.start();
    
            t1.join();
            t2.join();
            t3.join();
        }
    }
    

    上面代码,method1()的同步代码块中,再次调用method2(),它并不需要先释放同步对象锁再次获取.

    不可中断

    一旦锁已经被别人获得,如果我还想获得同一个锁,我只能等待或者阻塞,直到别人释放这个锁.如果别人永远不释放锁,那么我只能永远等待下去.

    示例代码

    笔记示例代码

    相关文章

      网友评论

          本文标题:(5)synchronized关键字

          本文链接:https://www.haomeiwen.com/subject/ofjfqktx.html