zoukankan      html  css  js  c++  java
  • Day5 多线程 锁

    synchronized保证操作原子性

    这是因为对变量进行读取和写入时,结果要正确,必须保证是原子操作。原子操作是指不能被中断的一个或一系列操作。
    通过加锁和解锁的操作,就能保证3条指令总是在一个线程执行期间,不会有其他线程会进入此指令区间。即使在执行期线程被操作系统中断执行,其他线程也会因为无法获得锁导致无法进入此指令区间。只有执行线程将锁释放后,其他线程才有机会获得锁并执行。这种加锁和解锁之间的代码块我们称之为临界区(Critical Section),任何时候临界区最多只有一个线程能执行。

    可见,保证一段代码的原子性就是通过加锁和解锁实现的。Java程序使用synchronized关键字对一个对象进行加锁:

    synchronized(lock) {
        n = n + 1;
    }
    

    synchronized保证了代码块在任意时刻最多只有一个线程能执行。

    synchronized(Counter.lock) { // 获取锁
        ...
    } // 释放锁
    

    它表示用Counter.lock实例作为锁,两个线程在执行各自的synchronized(Counter.lock) { ... }代码块时,必须先获得锁,才能进入代码块进行。执行结束后,在synchronized语句块结束会自动释放锁。这样一来,对Counter.count变量进行读写就不可能同时进行。
    使用synchronized解决了多线程同步访问共享变量的正确性问题。但是,它的缺点是带来了性能下降。因为synchronized代码块无法并发执行。此外,加锁和解锁需要消耗一定的时间,所以,synchronized会降低程序的执行效率。
    注意,要锁住同一个
    如果:

    public class Main {
        public static void main(String[] args) throws Exception {
            var add = new AddThread();
            var dec = new DecThread();
            add.start();
            dec.start();
            add.join();
            dec.join();
            System.out.println(Counter.count);
        }
    }
    
    class Counter {
        public static final Object lock1 = new Object();
        public static final Object lock2 = new Object();
        public static int count = 0;
    }
    
    class AddThread extends Thread {
        public void run() {
            for (int i=0; i<10000; i++) {
                synchronized(Counter.lock1) {
                    Counter.count += 1;
                }
            }
        }
    }
    
    class DecThread extends Thread {
        public void run() {
            for (int i=0; i<10000; i++) {
                synchronized(Counter.lock2) {
                    Counter.count -= 1;
                }
            }
        }
    }
    

    为两个线程各自的synchronized锁住的不是同一个对象!这使得两个线程各自都可以同时获得锁:因为JVM只保证同一个锁在任意时刻只能被一个线程获取,但两个不同的锁在同一时刻可以被两个线程分别获取。
    因此,使用synchronized的时候,获取到的是哪个锁非常重要。锁对象如果不对,代码逻辑就不对。
    用synchronized修饰方法可以把整个方法变为同步代码块,synchronized方法加锁对象是this

    对一个静态方法添加synchronized修饰符

    对于static方法,是没有this实例的,因为static方法是针对类而不是实例。但是我们注意到任何一个类都有一个由JVM自动创建的Class实例,因此,对static方法添加synchronized,锁住的是该类的class实例。

    可重入锁

    一个加锁的方法里调用另一个加锁的方法
    JVM允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁,就叫做可重入锁。,所以,获取锁的时候,不但要判断是否是第一次获取,还要记录这是第几次获取。每获取一次锁,记录+1,每退出synchronized块,记录-1,减到0的时候,才会真正释放锁。

    死锁

    public void add(int m) {
        synchronized(lockA) { // 获得lockA的锁
            this.value += m;
            synchronized(lockB) { // 获得lockB的锁
                this.another += m;
            } // 释放lockB的锁
        } // 释放lockA的锁
    }
    
    public void dec(int m) {
        synchronized(lockB) { // 获得lockB的锁
            this.another -= m;
            synchronized(lockA) { // 获得lockA的锁
                this.value -= m;
            } // 释放lockA的锁
        } // 释放lockB的锁
    }
    

    在获取多个锁的时候,不同线程获取多个不同对象的锁可能导致死锁。对于上述代码,线程1和线程2如果分别执行add()和dec()方法时:

    线程1:进入add(),获得lockA;
    线程2:进入dec(),获得lockB。
    随后:

    线程1:准备获得lockB,失败,等待中;
    线程2:准备获得lockA,失败,等待中。
    此时,两个线程各自持有不同的锁,然后各自试图获取对方手里的锁,造成了双方无限等待下去,这就是死锁。

    死锁发生后,没有任何机制能解除死锁,只能强制结束JVM进程。

    因此,在编写多线程应用时,要特别注意防止死锁。因为死锁一旦形成,就只能强制结束进程。

    wait notify

    https://www.liaoxuefeng.com/wiki/1252599548343744/1306580911915042
    wait()方法的执行机制非常复杂。首先,它不是一个普通的Java方法,而是定义在Object类的一个native方法,也就是由JVM的C代码实现的。其次,必须在synchronized块中才能调用wait()方法,因为wait()方法调用时,会释放线程获得的锁,wait()方法返回后,线程又会重新试图获得锁。
    如何让等待的线程被重新唤醒,然后从wait()方法返回?答案是在相同的锁对象上调用notify()方法。

    wait和notify用于多线程协调运行:

    在synchronized内部可以调用wait()使线程进入等待状态;

    必须在已获得的锁对象上调用wait()方法;

    在synchronized内部可以调用notify()或notifyAll()唤醒其他等待线程;

    必须在已获得的锁对象上调用notify()或notifyAll()方法;

    已唤醒的线程还需要重新获得锁后才能继续执行。

    个人小站:http://jun10ng.work/ 拥抱变化,时刻斗争,走出舒适圈。
  • 相关阅读:
    APICloud学习笔记之Click事件优化
    APICloud学习笔记之doT模板
    APICloud学习笔记之上拉加载
    APICloud学习笔记之下拉刷新
    5. JavaScript 正则表达式
    4. JavaScript 控制语句
    3. JavaScript 数据类型
    2. 语法
    1.开篇
    C++内存申请容易产生的错误
  • 原文地址:https://www.cnblogs.com/Jun10ng/p/12373273.html
Copyright © 2011-2022 走看看