Happens-Before解决可见性问题

生活富能量大约 5 分钟

Happens-Before解决可见性问题

关于Happens-Before的理解翻译问题

JSR-133 提出了 happens-before 的概念,通过这个概念来阐述操作之间的内存可见性

因为是在多个线程之间并且有可见性的问题,可以把这个单词理解为: 之前发生的事情。 一个线程可以看到另一个线程之前的操作

1. 程序顺序规则

一个线程中,按照程序顺序,前面的操作 Happens-Before 于后续的任意操作。

下面的代码代码1Happens-Before 代码2, 因此结果肯定是66。 这就是Happens-Before原则, 编译器也不会对这段代码进行排序优化。

public class HappensBeforeExample {

    int a = 0;
 
    public void write() {
        a = 66;    //代码1
      System.out.println(a);   // 代码2
    }
   
}

2. volatile变量规则

对一个volatile变量的写操作先行发生于后面对这个变量的读操作。

可以看下面这段代码,假设线程A 执行到了代码1, 线程B 执行结果 应该是 age = 19 ,这里显示 19 是因为 volatile在内存中读取结果在起作用。 同时可见性也在起作用。这个可性见起了作用,是为了给 happens-before的传递性做铺垫。


public class HappensBeforeExample {

    int a = 0;
    volatile int age = 0;

	// 线程A执行
    public void writer() {
        a = 66;
        age = 19; // 代码1
    }

    // 线程B
    public void read() {
        System.out.println("age = " +age);
        System.out.println("a = " + a); 
    }

}

3. 传递性

如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

因为程序顺序性,线程A的两个步骤都有happes-before, 因为volatile,线程A和线程B之间也有happens-before, 又因为顺序性线程B的两个步骤也有了happes-before。 最后因为传递性, 线程A的第一步和线程B的最后一步之前也就有了happens-before了。 因此线程B读取到的a=66

4. 管程锁定规则(Monitor Lock Rule)

一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是“同一个锁”,而“后面”是指时间上的先后。

一个synchronized就是一个java版的管程。


    private static Object lock = new Object();
    private static boolean condition = false;

    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(()->{
            changeCondition();
        });

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

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {

                while (!condition) {
                    try {
                        System.out.println("Thread " +  Thread.currentThread().getId() + "进入等待状态");
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 线程在临界区内的操作
                System.out.println("Thread " + Thread.currentThread().getId() + "执行完成");
            }
        }
    }

    public static void changeCondition() {
        synchronized (lock) {
            System.out.println("Thread " +  Thread.currentThread().getId() + "修改condition状态");
            // 修改共享变量
            condition = true;

            System.out.println("Thread " +  Thread.currentThread().getId() + "唤醒等待的线程");

            // 唤醒等待的线程
            lock.notifyAll();
        }

    }

在这个示例中,我们定义了一个静态的共享对象lock作为管程锁定的对象,并且还定义了一个共享的布尔变量condition

main()方法中,我们创建了两个线程thread1thread2,它们都使用相同的MyRunnable实例作为任务。然后,我们启动这两个线程。

MyRunnablerun()方法中,我们首先使用synchronized关键字锁定lock对象,我们使用了一个循环来检查condition变量的值。如果conditionfalse,则当前线程会调用lock.wait()来等待,并释放对lock对象的锁定。这样,其他线程可以进入临界区修改condition变量。

当另一个线程调用changeCondition()方法时,它会获取lock对象的锁定,并将condition设置为true。然后,它使用lock.notifyAll()唤醒所有等待的线程。

这样,在这个例子中,通过使用管程锁定规则,我们确保了在临界区内的操作只有当condition变量为true时才执行。当condition变量被修改并且等待的线程被唤醒时,它们将进入临界区执行相应的操作。

代码执行结果 :

Thread 12进入等待状态
Thread 13修改condition状态
Thread 13唤醒等待的线程
Thread 12执行完成

5. 线程启动规则(Thread Start Rule)

Thread对象的start()方法先行发生于此线程的每一个动作。

如果线程A在某个时刻调用线程B的start()方法来启动线程B,那么在线程B中可以看到线程A的操作

public class ThreadStartExample {
    private static boolean flag = false;
    
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        
        // 线程启动之前的操作
        flag = true;
        
        // 启动线程
        thread.start();
        
        // 线程启动之后的操作
        if (flag) {
            System.out.println("Flag is true");
        }
    }
    
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            // 在这里可以保证启动线程之前的操作对于该线程是可见的
            if (flag) {
                System.out.println("Flag is true");
            }
        }
    }
}

6. 线程终止规则(Thread Termination Rule)

线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行

下面的例子就是介绍

public class HappensBeforeExample {

    private static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new IncrementRunnable());
        Thread thread2 = new Thread(new DecrementRunnable());

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

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

        System.out.println("Counter: " + counter);
    }

    static class IncrementRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                counter++;
            }
        }
    }

    static class DecrementRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                counter++;
            }
        }
    }




}

输出结果 : Counter: 20

上面的例子中两个线程操作同一个变量, 因为有join(),thread1和thread2之前有了happens-before,变量counter在两个线程之间是可见的。

7. 线程中断规则(Thread Interruption Rule)

当一个线程中断另一个线程时,被中断线程将会看到中断请求之前的所有修改.

private static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyRunnable());
        thread.start();

        Thread.sleep(2000); // 等待2秒钟
        counter = -1;
        thread.interrupt(); // 中断线程

        System.out.println("Main thread is exiting.");
    }

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 1; i <= 10; i++) {
                System.out.println("Count: " + counter++);
                try {
                    Thread.sleep(1000); // 休眠1秒钟
                } catch (InterruptedException e) {
                    System.out.println("Thread is interrupted.");
                    System.out.println("Count: " + counter++);
                    break;
                }
            }
            System.out.println("Child thread is exiting.");
        }
    }

运行结果

Count: 0
Count: 1
Main thread is exiting.
Thread is interrupted.
Count: -1
Child thread is exiting.

8. 对象终结规则(Finalizer Rule)

一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.5