并发编程相关面试题四

一、Java开发中用过哪些锁

1、乐观锁

  乐观锁顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS(Compare and Swap 比较并交换)实现的

  乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升;  

  乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

2、悲观锁

  悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。比如Java里面的同步原语synchronized关键字的实现就是悲观锁。

  悲观锁适合写操作非常多的场景;

  悲观锁在Java中的使用,就是利用各种锁;

3、独享锁

  独享锁是指该锁一次只能被一个线程所持有。

  独享锁通过AQS来实现的,通过实现不同的方法,来实现独享锁。

  对于Synchronized而言,当然是独享锁。

4、共享锁

  共享锁是指该锁可被多个线程所持有。

  读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。

  共享锁也是通过AQS来实现的,通过实现不同的方法,来实现共享锁。

5、互斥锁

  互斥锁在Java中的具体实现就是ReentrantLock。

6、读写锁

  读写锁在Java中的具体实现就是ReadWriteLock。

7、可重入锁    

  重入锁也叫作递归锁,指的是同一个线程外层函数获取到一把锁后,内层函数同样具有这把锁的控制权限;
  synchronized和ReentrantLock就是重入锁对应的实现;
  synchronized重量级的锁 ;
  ReentrantLock轻量级的锁;

8、公平锁

  公平锁是指多个线程按照申请锁的顺序来获取锁。

  对于Java ReetrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

9、非公平锁

  非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。  

  对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

10、分段锁

  分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

  我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7和JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

  当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

  但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

  分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

11、偏向锁  

  偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

12、轻量级锁

  轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

13、重量级锁

  重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让他申请的线程进入阻塞,性能降低。

14、自旋锁

  在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

二、synchronized关键字理解

  使用了synchronized关键字可以轻松地解决多线程共享数据同步问题。

  synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

  synchronized取得的锁都是对象;每个对象只有一个锁(lock)与之相关联;实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

  synchronized的4种用法:

    1. 方法声明时使用,线程获得的是成员锁;

    2. 对某一代码块使用,synchronized后跟括号,括号里是变量,线程获得的是成员锁;

    3. synchronized后面括号里是一对象,此时,线程获得的是对象锁;

    4. synchronized后面括号里是类,此时,线程获得的是对象锁;

三、CAS无锁机制

  CAS:Compare and Swap,即比较交换;

  jdk1.5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronized同步锁的一种乐观锁。jdk1.5之前java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是悲观锁;

  本身无锁,采用乐观锁的思想,在数据操作时对比数据是否一致,如果一致代表之前没有线程操作该数据,那么就会更新数据,如果不一致代表有县城更新则重试;

  CAS当中包含三个参数CAS(V,E,N),V标识要更新的变量,E标识预期值,N标识新值  

  运行过程:

    1.线程访问时,先会将主内存中的数据同步到线程的工作内存当中;

    2.假设线程A和线程B都有对数据进行更改,那么假如线程A先获取到执行权限;

    3.线程A先会对比工作内存当中的数据和主内存当中的数据是否一致,如果一致(V==E)则进行更新,不一致则刷新数据,重新循环判断;

    4.这时更新完毕后,线程B也要进行数据更新,主内存数据和工作内存数据做对比,如果一致则进行更新,不一致则将主内存数据重新更新到工作内存,然后循环再次对比两个内存中的数据,直到一致为止; 

  CAS无锁机制存在一个问题

    ABA问题,如果将原来A的值改为了B,然后又改回了A,虽然最终结果没有发生改变,但是在过程中是对该数据进行了修改操作

    解决该问题:在Java中并发包下有一个原子类:AtomicStampedReference,在该类当中通过版本控制判断值到底是否被修改

    解释:如果对值进行了更改则版本号+1,那么在CAS当中不仅仅对比变量的值,还要对比版本号,如果值和版本号都相等则代表没有被修改,如果有一方不相等代表进行过更改

    那么就从主内存中重新刷新数据到工作内存然后循环对比,直到成功为止~

四、AQS

  AQS:全称AbstractQueueSynchronizer,抽象队列同步器,这个类在java.util.concurrent.locks包下

  它是一个底层同步工具类,比如CountDownLatch,Sammphore,ReentrantLock,ReentrantReadWriteLock等等都是基于AQS

    底层三个内容:

      1.state(用于计数器)

       2.线程标记(哪一个线程加的锁)

       3.阻塞队列(用于存放阻塞线程)

  AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,如下图所示。AQS为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。

    

  J.U.C是基于AQS实现的,AQS是一个同步器,设计模式是模板模式。

  核心数据结构:双向链表 + state(锁状态)

  底层操作:CAS

    

五、ReentrantLock底层实现

  ReentrantLock是基于AQS的,AQS是Java并发包中众多同步组件的构建基础,它通过一个int类型的状态变量state和一个FIFO队列来完成共享资源的获取,线程的排队等待等。AQS是个底层框架,采用模板方法模式,它定义了通用的较为复杂的逻辑骨架,比如线程的排队,阻塞,唤醒等,将这些复杂但实质通用的部分抽取出来,这些都是需要构建同步组件的使用者无需关心的,使用者仅需重写一些简单的指定的方法即可(其实就是对于共享变量state的一些简单的获取释放的操作)。

  无参构造器(默认为非公平锁) 

public ReentrantLock() {
       sync = new NonfairSync();//默认是非公平的
}

  synchronized是ReentrantLock内部实现的一个同步组件,它是Reentrantlock的一个静态内部类,继承于AQS;

  带布尔值的构造器(是否公平)

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();//fair为true,公平锁;反之,非公平锁
}

  此处可以指定是否采用公平锁,FailSync和NonFailSync亦为Reentrantlock的静态内部类,都继承于synchronized;

六、ReentrantLock和synchronized之间的区别

  • synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
  • synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
  • synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
  • synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
  • synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
  • synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放

七、ReentrantReadWriteLock(读写锁)

  相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些。

  假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(译者注:也就是说:读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题。

  Java5在java.util.concurrent包中已经包含了读写锁。

package com.zn.lockTest;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLock {
    //创建一个集合
    static Map<String,String> map=new HashMap<String,String>();
    //创建一个读写锁
    static ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    //获取读锁
    static Lock readLock=lock.readLock();
    //获取写锁
    static Lock writeLock=lock.writeLock();
    //写操作
    public Object put(String key,String value){
        writeLock.lock();
        try {
            System.out.println("Write正在执行写操作~");
            Thread.sleep(100);
            String put = map.put(key, value);
            System.out.println("Write写操作执行完毕~");
            return put;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            writeLock.unlock();
        }
        return null;

    }

    //写操作
    public Object get(String key){
        readLock.lock();
        try {
            System.out.println("Read正在执行读操作~");
            Thread.sleep(100);
            String value = map.get(key);
            System.out.println("Read读操作执行完毕~");
            return value;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readLock.unlock();
        }
        return null;

    }

    public static void main(String[] args) {
        ReadWriteLock lock=new ReadWriteLock();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    //写操作
                    lock.put(finalI +"","value"+finalI);
                    //读操作
                    System.out.println(lock.get(finalI+""));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}

控制台效果:

  

八、BlockingQueue阻塞队列的实现方式

  阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个附加的操作是:

    在队列为空时,获取元素的线程会等待队列变为非空;

    当队列满时,存储元素的线程会等待队列可用;

  阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器拿元素;

  在java中,BlockingQueue的接口位于java.util.concurrent包中,阻塞队列是线程安全的;

  在新增呢的concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题,通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利;

  常用的队列主要由以下两种:

    先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能,从某种程度上来说这种队列也体现了一种公平性;

    后进后出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件;

1.ArrayBlockingQueue

  ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组,有边界意思就是它的容量是有限的,我们必须在其初始化的时候执行它的容量大小,容量大小一旦执行就不可改变;

  ArrayBlockingQueue是以先进先出的方式存储数据,最新插入的对象是尾部,最新移除的对象是头部;

package com.zn.queueTest;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class ArrayBlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> arrays=new ArrayBlockingQueue<String>(3);
        arrays.add("张三");
        arrays.add("李四");
        arrays.add("王五");

        //添加阻塞队列
        arrays.offer("赵六",1, TimeUnit.SECONDS);

        //poll方法相当于消费了队列中的数据,队列的数据就会删除
        System.out.println(arrays.poll());
        System.out.println(arrays.poll());
        System.out.println(arrays.poll());
        System.out.println(arrays.poll());
    }
}

控制台效果:

  

如果先出队一条数据,此时被阻塞的数据就可以添加进来:

package com.zn.queueTest;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class ArrayBlockingQueueTest {
    //出队一条数据
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> arrays=new ArrayBlockingQueue<String>(3);
        arrays.add("张三");
        arrays.add("李四");
        arrays.add("王五");
        //出队一条数据
        System.out.println(arrays.poll());

        //添加阻塞队列
        arrays.offer("赵六",1, TimeUnit.SECONDS);

        //poll方法相当于消费了队列中的数据,队列的数据就会删除
        System.out.println(arrays.poll());
        System.out.println(arrays.poll());
        System.out.println(arrays.poll());
    }
}

控制台效果:

  

2.LinkedBlockingQueue

  LinkedBlockingQueue阻塞队列大小的配置时可选的,如果我们初始化时指定大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE容量,它的内部是一个链表;

  和ArrayBlockingQueue一样,LinkedBlockingQueue也是以先进先出的方式存储数据,最新插入的对象是尾部,最新移除的对象是头部;

package com.zn.queueTest;

import java.util.concurrent.LinkedBlockingQueue;

public class LinkedBlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        LinkedBlockingQueue linkedBlockingQueue=new LinkedBlockingQueue(3);
        linkedBlockingQueue.add("A");
        linkedBlockingQueue.add("B");
        linkedBlockingQueue.add("C");
        System.out.println(linkedBlockingQueue.poll());
        System.out.println(linkedBlockingQueue.size());
    }
}

控制台效果:

  

3.PriorityBlockingQueue

  PriorityBlockingQueue是一个没有边界的队列,它的排序规则和java.util.PriorityQueue一样。需要注意,PriorityBlockingQueue中国允许插入null对象;

  所有插入PriorityBlockingQueue的对象必须实现java.lang.Comparable接口,队列优先级的排序规则就是按照我们对这个接口的实现来定义的;

  另外,我们可以从PriorityBlockingQueue获得一个迭代器Iterator,但这个迭代器并不保证按照优先级顺序进行迭代;

package com.zn.queueTest;

import java.util.concurrent.PriorityBlockingQueue;

public class PriorityBlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        PriorityBlockingQueue<String> priorityBlockingQueue=new PriorityBlockingQueue<String>(3);
        priorityBlockingQueue.add("AA");
        priorityBlockingQueue.add("BB");
        priorityBlockingQueue.add("CC");
        System.out.println(priorityBlockingQueue.poll());
        System.out.println(priorityBlockingQueue.size());
    }
}

控制台效果:

  

4.SynchronousQueue

  SynchronousQueue队列内部仅容纳一个元素,当一个线程插入一个元素后会被阻塞,除非这个元素被另一个线程消费;

九、ConcurrentLinkedQueue

  ConcurrentLinkedQueue:是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue,它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null元素;

  ConcurrentLinkedQueue重要方法:

    add()和offer()都是加入元素的方法(在ConcurrentLinkedQueue中这两个方法没有任务区别);

    poll()和peek()都是取头元素节点,区别在于前者会删除元素,后者不会;

package com.zn.queueTest;

import java.util.concurrent.ConcurrentLinkedQueue;

public class ConcurrentLinkedQueueTest {
    public static void main(String[] args) {
        //准备队列
        ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
        //存放数据
        queue.offer("张三");
        queue.offer("李四");
        queue.offer("王五");

        //获取队列中数据个数
        System.out.println("队列中当前有:"+queue.size()+"个数据~");
        //获取队列中头数据  poll()方法相当于消费了队列中的数据,队列数据会随之删除
        System.out.println("获取队列中的数据:"+queue.poll());
        System.out.println("队列中当前有:"+queue.size()+"个数据~");
        //获取队列中数据,但是不会删除
        System.out.println("获取队列中的数据:"+queue.peek());
        System.out.println("获取队列中的数据:"+queue.peek());
        System.out.println("队列中当前有:"+queue.size()+"个数据~");

    }
}

控制台效果:

  

原文地址:https://www.cnblogs.com/Zzzzn/p/12586656.html

时间: 2024-11-13 04:03:25

并发编程相关面试题四的相关文章

【Java并发编程实战】—– AQS(四):CLH同步队列

在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了头结点和尾节点,他们分别指向队列的头和尾,尝试获取锁.入队列.释放锁等实现都与头尾节点相关.而且每一个节点都引入前驱节点和后兴许节点的引用:在等待机制上由原来的自旋改成堵塞唤醒. 其结构例如以下: 知道其结构了,我们再看看他的实现.在线程获取锁时会调用AQS的acquire()方法.该方法第一次尝试获取锁假设

并发编程常见面试题

1.进程和线程还有协程之间的关系   1.1 进程,直观点说,保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内存体,这个内存体有自己独立的地址空间,有自己的堆,上级挂靠单位是操作系统. 操作系统会以进程为单位,分配系统资源(CPU时间片.内存等资源),进程是资源分配的最小单位. 1.2 线程,有时被称为轻量级进程(Lightweight Process,LWP),是操作系统调度(CPU调度)执行的最小单位. 1.3 协程,协程是一种用户态的轻量级线程,协程的调度完全由用户控制.协程拥有

并发编程相关知识

1.并发编程领域的关键问题 线程之间的通信 线程间的同步 1.1 线程之间的通信 线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信. 在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()和notify() 1.2 线程间的同步 同步是指程序用于控制不同线程之间操作发生相对顺序的机制 2.计算机

《Java并发编程实战》第四章 对象的组合 读书笔记

一.设计线程安全的类 在设计线程安全类的过程中,须要包括下面三个基本要素: . 找出构成对象状态的全部变量. . 找出约束状态变量的不变性条件. . 建立对象状态的并发訪问管理策略. 分析对象的状态,首先从对象的域開始. 变量按作用域划分: . 全局变量 . 局部变量 . 方法行參 . 异常处理參数 1. 收集同步需求 假设不了解对象的不变性条件与后验条件,那么就不能确保线程安全性.要满足在状态变量的有效值或状态转换上的各种约束条件.就须要借助原子性和封装性. 说的更简略些是Java线程安全都是

Java并发编程原理与实战四十五:问题定位总结

背景   “线下没问题的”. “代码不可能有问题 是系统原因”.“能在线上远程debug么”    线上问题不同于开发期间的bug,与运行时环境.压力.并发情况.具体的业务相关.对于线上的问题利用线上环境可用的工具,收集必要信息 对定位问题十分重要.    对于导致问题的bug.资源瓶颈很难直观取得数据,需要根据资源使用数据.日志等信息推测问题根源.并且疑难问题的定位通常需要使用不同的方法追根溯源.    这篇wiki我对自己使用过的工具做了整理,并分享一些案例. 1.  常见问题1.1 可用性

Java并发编程原理与实战四十三:CAS ---- ABA问题

CAS(Compare And Swap)导致的ABA问题 问题描述 多线程情况下,每个线程使用CAS操作欲将数据A修改成B,当然我们只希望只有一个线程能够正确的修改数据,并且只修改一次.当并发的时候,其中一个线程已经将A成功的改成了B,但是在线程并发调度过程中尚未被调度,在这个期间,另外一个线程(不在并发中的请求线程)将B又修改成了A,那么原来并发中的线程又可以通过CAS操作将A改成B 测试用例: public class AbaPro { private static final Rando

Java并发编程原理与实战四十一:重排序 和 happens-before

一.概念理解 首先我们先来了解一下什么是重排序:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段. 从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序,如下图所示 上述的1属于编译器重排序,2和3属于处理器重排序.这些重排序可能会导致多线程程序出现内存可见性问题.在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是as-if-serial语义允许对存在控制依赖的操作做重排序的原因):但在多线程程序中,对存在控制依赖的操作重排序,可能会改变

转: 【Java并发编程】之十四:图文讲述同步的另一个重要功能:内存可见性

转载请注明出处:http://blog.csdn.net/ns_code/article/details/17288243 加锁(synchronized同步)的功能不仅仅局限于互斥行为,同时还存在另外一个重要的方面:内存可见性.我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且还希望确保当一个线程修改了对象状态后,其他线程能够看到该变化.而线程的同步恰恰也能够实现这一点. 内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果.为了确保所有的线程都能看到

【Java并发编程】之十四:图文讲述同步的另一个重要功能:内存可见性

加锁(synchronized同步)的功能不仅仅局限于互斥行为,同时还存在另外一个重要的方面:内存可见性.我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且还希望确保当一个线程修改了对象状态后,其他线程能够看到该变化.而线程的同步恰恰也能够实现这一点. 内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果.为了确保所有的线程都能看到共享变量的最新值,可以在所有执行读操作或写操作的线程上加上同一把锁.下图示例了同步的可见性保证. 当线程A执行某个同步代码块