zoukankan      html  css  js  c++  java
  • Java同步(Synchronization)

    前言

    线程间的通信主要通过共享对字段的访问和对象引用字段的引用,可能会产生两种错误,线程干扰和内存一致性错误。Java的同步就是防止这些错误,但当多个线程访问同一资源会导致线程执行缓慢,甚至暂停执行。

    线程干扰(Thread Interference)

    例子

    class Counter {
        private int c = 0;
    
        public void increment() {
            c++;
        }
    
        public void decrement() {
            c--;
        }
    
        public int value() {
            return c;
        }
    
    }
    View Code

    如果现在有两个线程,线程A执行increment,线程B执行decrement,它们都使用了相同的变量c,这时会发生干扰。即便是执行非常简单的语句,但简单语句也可以转化为Java虚拟机的多个步骤,例如c++可以分解为三个步骤:

    1、检索c的当前值。

    2、将检索值加1。

    3、将值存储回c中。

    PS:c—也可以分解为相同步骤,只是第二步是减1。

    假如线程A和线程B几乎同时调用,c的初始值为0,则它们交错的步骤可能是以下顺序:

    线程A:检索c。
    线程B:检索c。
    线程A:增加检索值;结果是1。
    线程B:减少检索值;结果是-1。
    线程A:将结果存储在c中; c现在是1。
    线程B:将结果存储在c中; c现在是-1。

    线程A的结果丢失了,被线程B覆盖了。这种交错只是一种可能性,也可能是B的结果丢失,也可能不会出错。也就是因为顺序是不可预测的,所以线程干扰的错误可能难以发现和修复。

    内存一致性错误

    不同线程看到的内存中的同一份数据却有不同的“视图”,原因很复杂,作为程序员最需要做的是处理好“happens-before”的关系,避免问题。

    同步方法

    Java提供了两种基本的同步方法:同步方法和同步语句。同步的行为是依赖锁来构建的,每一个对象都有与之相关的固定锁,想要独占访问对象的线程必须先获取对象的锁(拿不到阻塞线程),完成后释放锁,同时与请求同一锁的线程建立一个happens-before关系。

    同步方法的作用:

    1、当一个线程正在执行一个对象的同步方法时,所有其他调用该对象的同步方法的线程将被阻塞,直到第一个线程执行完成。

    2、当一个同步方法退出后,会自动建立与后续调用的同步方法(相同对象)的一个happens-before关系,保证所有线程对对象状态的修改可见。

    注意:

    1、在构造函数中使用synchronized关键字是语法错误。同步构造函数没有意义,因为只有创建对象的线程在构建时才能访问它。

    2、如果一个对象对多个线程可见,则通过同步方法完成对该对象变量的所有读取或写入操作,不然还是会出现线程干扰和内存一致性错误。

    public class SynchronizedCounter {
        private int c = 0;
    
        public synchronized void increment() {
            c++;
        }
    
        public synchronized void decrement() {
            c--;
        }
    
        public synchronized int value() {
            return c;
        }
    }
    View Code

    同步语句

    另一种创建同步代码的方法,与同步方法不同,同步语句必须指定提供内部锁的对象。同步语句有利于通过细粒度同步来提高并发性。

    例如,假设类MsLunch具有两个实例字段c1和c2,它们从不一起使用(很重要的前提条件!)。这些字段的所有更新都必须同步,但没有理由阻止c1的更新与c2的更新交错,这样做会创建不必要的阻塞来降低并发性。我们不使用同步方法,而是创建两个对象来提供锁。

    public class MsLunch {
        private long c1 = 0;
        private long c2 = 0;
        private Object lock1 = new Object();
        private Object lock2 = new Object();
    
        public void inc1() {
            synchronized(lock1) {
                c1++;
            }
        }
    
        public void inc2() {
            synchronized(lock2) {
                c2++;
            }
        }
    }
    View Code

    volatile关键字

    原子操作:一个原子操作,要么发生,要么不发生。比如 c=0;(非long和double类型) 这个操作是执行了就会发生。再比如:c++;可分割成三个操作步骤,执行时可能会丢失某些步骤,就不是一个原子操作。非原子操作都会存在线程安全问题,同步方法和同步语句可以让它变成一个原子操作。

    volatile变量是一种稍弱的同步机制,所有声明为volatile的变量(包括long和double),读取和写入都是原子的。

    volatile变量特性:

    1、当一个线程读取一个volatile变量时,它看到总是最新的值。

    2、原子动作不能交错,使用volatile变量不用担心线程干扰。

    PS:java.util.concurrent包下提供了一些原子类。

    参考文献

    https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

  • 相关阅读:
    Python 学习笔记 11.模块(Module)
    Python 学习笔记 8.引用(Reference)
    Python 学习笔记 9.函数(Function)
    Python 学习笔记 6.List和Tuple
    Python 学习笔记 4.if 表达式
    Python 学习笔记 2.自省
    Python 学习笔记 3.简单类型
    Python 学习笔记 7.Dictionary
    Python 学习笔记 5.对象驻留
    Python 学习笔记 10.类(Class)
  • 原文地址:https://www.cnblogs.com/lovesong/p/8934440.html
Copyright © 2011-2022 走看看