并发编程[4]_synchronized关键字


介绍synchronized关键字

1. 概念

同步代码块(Synchronized Block)是Java提供的一种内置锁机制。用关键字synchronized来修饰的方法,就是同步代码块。线程在进入同步代码块的时候会自动获取锁,退出代码块时会自动释放锁。内置锁是互斥锁,最多只有一个线程能持有,被锁保护的同步代码块会以原子方式执行,保证了多个线程在执行该代码块时不会互相干扰。
synchronized关键字可以使用在代码块和方法中。
根据锁不同的位置,被锁的对象是不同的

锁的修饰部分 锁内容 说明 例子
普通实例方法 类的实例对象 等同于this锁 synchronized void method(){
......
}
静态方法 类对象 等同于锁A.class static synchronized void method(){
......
}
代码块 任意对象锁 synchronized(任意对象) synchronized(任意对象){
......
}

2. 不加锁的情况

新建Count类,新建两个线程对同一count实例的num变量进行一万次加一和一万次减一的修改。
例子1:

public class Test1 {
    private static final Logger log = LoggerFactory.getLogger(Test1.class);

    public static void main(String[] args) throws InterruptedException {
        Count myCount = new Count();

        Thread thread1 = new Thread("thread1") {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    myCount.increase();
                }
            }
        };

        Thread thread2 = new Thread("thread2") {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    myCount.decrease();
                }
            }
        };

        thread1.start();
        thread2.start();

        // 保证两个线程都执行完再输出
        thread1.join();
        thread2.join();
        log.debug("{}", myCount.getNum());
    }

    // 自定义Count类
    static class Count {
        private int num;

        public void increase() {
            this.num++;
        }

        public void decrease() {
            this.num--;
        }

        public int getNum() {
            return num;
        }
    }
}

运行结果:

2021-04-18 11:11:13.100  [main] - -149

[注:运行结果跟cpu当时的调度有关,所以每次运行结果可能不同]
单线程情况下加一万再减一万的结果肯定是0,但是多线程情况下,结果就不一定为0了。
情况分析:

  • 线程1获取cpu时间片时,要执行+1的操作,首先取值num,此时num为x,计算加一结果为x+1,在要将结果x+1写入到num时,此时cpu时间片恰好用完,线程2执行代码
  • 线程2获取cpu时间片时,要执行-1的操作,首先取值num,因为线程1的+1操作的结果还未赋值给num,所以num此时也为x,计算结果x-1,x-1赋值给num,此时cpu时间片用完,线程1执行代码
  • 线程1将x+1写入到num

这样的情况,经过线程1加1,线程2减1,结果不是0却是x+1。
同样的,根据不同的线程调度方法结果也会出现x-1,和0的这三种情况。
解决这种情况,可以用上synchronized关键字为代码加锁。
解决办法1:3. synchronized 修饰普通方法
解决办法2:5. synchronized 修饰代码块

3. synchronized 修饰普通方法

将例子1中Count类中的方法做调整,给三个方法同时加锁。

    static class Count {
        private int num;

        public synchronized void increase() {
            this.num++;
        }

        public synchronized void decrease() {
            this.num--;
        }

        public synchronized int getNum() {
            return num;
        }
    }

此时锁是加在类的实例对象上的(main 方法中的 myCount),相当于:

    static class Count {
        private int num;

        public void increase() {
            synchronized (this){
                this.num++;
            }
        }

        public void decrease() {
            synchronized (this){
                this.num--;
            }
        }

        public synchronized int getNum() {
            synchronized (this){
                return num;
            }
        }
    }

运行结果:

2021-04-18 11:24:52.542  [main] - 0

例子2:
新建一个Display类,创建两个实例对象,给print方法加synchronized关键字,运行

public class Test1 {
    private static final Logger log = LoggerFactory.getLogger(Test1.class);

    public static void main(String[] args) throws InterruptedException {
        Display myDisplay1 = new Display();
        Display myDisplay2 = new Display();

        Thread thread1 = new Thread("thread1") {
            @Override
            public void run() {
                myDisplay1.print();
            }
        };

        Thread thread2 = new Thread("thread2") {
            @Override
            public void run() {
                myDisplay2.print();
            }
        };

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
    }

    static class Display {
        public synchronized void print(){
            try {
                log.debug("print start...");
                Thread.sleep(5000);
                log.debug("print end...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

2021-04-18 11:41:04.698  [thread1] - print start...
2021-04-18 11:41:04.698  [thread2] - print start...
2021-04-18 11:41:09.699  [thread1] - print end...
2021-04-18 11:41:09.699  [thread2] - print end...

从输出的时间点可以看出,thread1和thread2两个线程都是没有出现互相阻塞的情况,因为synchronized修饰的是普通方法,所以锁的对象分别是myDisplay1和myDisplay2两个实例。

4. synchronized 修饰静态方法

修改例子2,将print方法改为静态方法

    static class Display {
        public synchronized static void print(){
            try {
                log.debug("print start...");
                Thread.sleep(5000);
                log.debug("print end...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

运行结果:

2021-04-18 11:45:19.601  [thread1] - print start...
2021-04-18 11:45:24.602  [thread1] - print end...
2021-04-18 11:45:24.602  [thread2] - print start...
2021-04-18 11:45:29.603  [thread2] - print end...

从运行结果可以看出,在thread1调用完成print方法后,thread2才执行print方法。
因为synchronized 修饰静态方法,所得是整个Display类,相当于

    static class Display {
        public static void print(){
            synchronized (Display.class){
                try {
                    log.debug("print start...");
                    Thread.sleep(5000);
                    log.debug("print end...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

5. synchronized 修饰代码块

synchronized 修饰代码块,obj为具体的锁的对象

synchronized (obj){
    ......
}

我们可以用synchronized修饰代码块,解决例子1出现的问题

public class Test1 {
    private static final Logger log = LoggerFactory.getLogger(Test1.class);

    public static void main(String[] args) throws InterruptedException {
        Count myCount = new Count();
        // 新建一个对象,待会加锁
        Object obj = new Object();
        Thread thread1 = new Thread("thread1") {
            @Override
            public void run() {
                // 这边演示锁obj对象,实际上不用新建obj对象,直接锁myConut更方便
                synchronized (obj){
                    for (int i = 0; i < 10000; i++) {
                        myCount.increase();
                    }
                }
            }
        };

        Thread thread2 = new Thread("thread2") {
            @Override
            public void run() {
                synchronized (obj){
                    for (int i = 0; i < 10000; i++) {
                        myCount.decrease();
                    }
                }
            }
        };

        thread1.start();
        thread2.start();

        // 保证两个线程都执行完再输出
        thread1.join();
        thread2.join();
        log.debug("{}", myCount.getNum());
    }

    static class Count {
        ......
    }
}

运行结果:

2021-04-18 12:04:15.201  [main] - 0
posted @ 2024-08-23 10:42  Aeons  阅读(6)  评论(0编辑  收藏  举报