zoukankan      html  css  js  c++  java
  • Java 线程与锁

    Synchronization

    synchronized语法可以获取锁, 当其他线程持有锁的时候该线程想要获取锁将会进入等待状态, 直到没有其他线程持有该锁

    显示使用 synchronized (lock) 将会获取lock对象的锁

    没有显示指定锁对象将会获取当前类的class对象的锁

    Wait and Notification

    每个对象m都有一个wait set, 调用该对象的wait方法的线程会被加入到wait set

    1. Wait

    调用某个对象m的wait方法会使调用线程t进入等待状态, 他有如下几个派生方法

    wait(): 无限期等待, 直到被wait对象被notify或notifyAll

    wait(long timeout): 等待timeout毫秒, 或被notify

    wait(long timeout, int nanos): 等待 1000000*timeout+nanos 纳秒

    调用lock.wait()必须持有lock的monitor(锁), 否则会抛出IllegalMonitorStateException, 例如

        private final static Object lock = new Object();
        
        public static void main(String[] args) throws InterruptedException {
        
            synchronized (lock) { // 该线程必须持有锁
                while (condition not hold) {
                    lock.wait();
                }
                // logic code
            }
            
        }

    线程t会在如下情况从对象的wait set移除:

    1) 在t被选为从wait set中移除的线程的时候, 调用m的notify方法

    2) 调用m的notifyAll方法

    3) t被中断

    4) wait的时间到达指定值

    *: 一般来说wait都是放在一个循环里, 这个循环会一直判断wait()的条件是否成立, 这是因为如果有evil线程将wait唤醒, 有可能你继续执行的条件还未成立, 这样就可以继续进入wait状态

    调用wait()会释放锁, 当被notify()的时候, 会重新获得锁

    2. Notification

    调用m的notify方法的线程t必须持有m的monitor, 否则抛出IllegalMonitorStateException

    1) notify(): 随机唤醒m的wait set中的其中一个线程t1, 并将t1从wait set中移除

    2) notifyAll(): 唤醒所有m的wait set中的所有线程并移除

    调用notify()并不会释放锁, 所以, 即使调用了notify(), 另一个正在wait()的线程也需要等到notify()线程中的同步块执行完毕释放锁以后才能继续执行, 例如

    synchronized (lock) {
       monitor.notify()
       foo()
    }

    则必须等到foo()执行完毕, await()线程重新获得锁以后才能继续执行

    3. Interruptions

    调用线程t.interrupt()会将t的 interruption status 设置为 true

    如果 t 在 m 的 wait set 里, 调用 t.interrupt() 会将 t 从 m 的 wait set 里移除, 然后wait()方法会抛出InterruptedException, 并将 interruption status 清除.

    4. Waits, Notification, Interruption的交互

    如果一个线程处于waiting状态, 但是同时被notify和interrupt 它要么:

    a. 如果notify在前, 则正常唤醒, 并调用线程的interrupt, interrupt标志位置为true

    b. 如果interrupt在前, 则t抛出InterruptedException, 然后另一个线程u将被唤醒并从wait set移除

    Sleep and Yield

    yeild 会让当前执行线程暂停, 并让同优先级的线程执行.

    sleep 指定某个线程的暂停一定的时间

    yeild和sleep都不需要获得锁, 也不能被唤醒

    调用 sleep 和 yield 不会刷新寄存器缓存到共享内存中, 也不会让寄存器缓存重载.

    while (!this.done)
        Thread.sleep(1000);

    所以如果 this.done 如果不是volatile的, 则有可能使得循环内一直使用相同的this.done值, 这样循环可能永远停不下来, 即使别的线程修改了this.done的值

    Memory Model

    1. 没有正确同步的程序

    由于Java编译器和微处理器会对程序进行优化, 如果不进行正确的同步操作, 可能会出现很诡异的行为.

    例如, 以下程序使用本地变量 r1, r2, 并共享变量 A B, 最初 A == B == 0

    Thread 1Thread 2
    1: r2 = A; 3: r1 = B;
    2: B = 1; 4: A = 2;

    可能会觉得 r2 == 2, r1 == 1 并不可能. 从直觉上来看, 指令1先执行, r2则看不到指令4中A的变化, 指令3先执行, r1则看不到指令2中B的变化.

    然而, 由于编译器的优化, 很有可能会打乱执行顺序, 造成最后的结果是r2 == 2, r1 == 1, 如下

    Thread 1Thread 2
    B = 1; r1 = B;
    r2 = A; A = 2;

    像这种程序在多线程中是不正确的:

    1. 在一个线程中有写共享变量操作

    2. 在另一个线程中读这个共享变量

    3. 读和写的顺序没有被同步控制

    这种情况叫做数据竞争(data race), 如果代码里出现数据竞争, 则有可能出现反直觉的结果.

    另一个例子如下, 最初 p == q 并且 p.x == 0. 这个程序也是不正确的, 因为它在不同的线程中读写共享变量, 并且不适用同步控制

    Thread 1Thread 2
    r1 = p; r6 = p;
    r2 = r1.x; r6.x = 3;
    r3 = q;  
    r4 = r3.x;  
    r5 = r1.x;  

    这种情况下, 编译器可能将r2的值复用优化给r5, 变为如下

    Thread 1Thread 2
    r1 = p; r6 = p;
    r2 = r1.x; r6.x = 3;
    r3 = q;  
    r4 = r3.x;  
    r5 = r2;  

     假设thread 2 的 r6.x=3 操作发生在 thread 1 的 r2=r1.x 和 r3=q 之间, 则最后的结果是 r2 == r5 == 0, r4 == 3, 从编程者的角度来看, p.x 的值从 0 变为 3, 又变回了 0
     
    内存模型决定了在程序的任何位置什么值能被读取到.

    2. 共享变量

    可以被不同的线程共享的内存叫共享内存或堆内存, 所有的实例变量, 静态变量, 数组元素都存在对内存. 在这章里变量包括上述所有类型的变量. 下面提到的变量都是指这些变量

    本地变量, 方法参数, 异常处理器参数不会被线程共享, 也不会受内存模型的影响.

    如果不同线程对同一个共享变量的访问(读写)中含有写操作, 则这些访问是冲突的.

    3. Actions

    线程间行为(inter-thread action)指的是由一个线程执行, 但是可以被其他线程检测到或直接影响到的行为. 包括如下几种:

    a. 读非volatile(非本地)变量

    b. 写非volatile变量

    c. 同步行为.

      1) Volatile读

      2) Volatile写

      3) Lock 锁行为

      4) UnLock 解锁行为

    一个 Action 由 < t, k, v, u > 元组构成

    a. t - 执行action的线程

    b. k - action类型

    c. v - action 中包含的变量或者monitor

    对于Lock action, v 是被锁住的monitor; 对于 unlock action, v 是被解锁的monitor

    如果action是读, 则v是被读得变量

    如果action是写, 则v是被写的变量

    d. u - 任意唯一action标识符

    4. Synchronization Order

    每次执行都有一个同步顺序, 同步顺序是一次执行中所有同步行为的总顺序. 对于同一个线程t来说, t内的同步行为的同步顺序跟t的程序顺序是一致的

    同步行为包括 synchronized-with的关系, 如下:

    a. 对monitor m 的unlock行为和lock行为是同步的

    b. 对volatile变量v的写操作和任何线程对他的读操作是同步的

    c. 线程的start操作和线程start时的第一个action同步

    d. 变量的默认值写操作和线程中的第一个action同步

    e. 线程T1的最后一个action和线程T2中用来检查T1是否终止的操作同步. 例如: T2 may accomplish this by calling T1.isAlive() or T1.join().

    5. Happens-before Order

    参考这个. http://www.iteye.com/topic/260515/ 写的不错

  • 相关阅读:
    0x01 虚拟环境搭建
    python操作mysql8windows环境
    Navicat 导入sql文件执行失败问题的处理
    mysql8.0.16免安装教程
    zend studio 9.0.3 注册码
    oneplus8手机蓝牙连接tws耳机无法双击退出语音助手
    竞品分析
    源码阅读方法
    Tomcat内核1
    Asp.NetCore3.1开源项目升级为.Net6.0
  • 原文地址:https://www.cnblogs.com/zemliu/p/3714283.html
Copyright © 2011-2022 走看看