并发进阶
synchronized、volatile
synchronized:
概述:同步方法是根据ACC_SYNCHRONIZED标志 来完成同步操作的
同步代码块是根据monitorenter、monitorexit结合 来完成同步操作的
1、 说一说自己对于 synchronized 关键字的了解 synchronized关键字解决的是多个线程间访问资源的同步性,它可以保证被它修饰的方法或者代码块在任意时刻都只能有一个线程执行。
synchronized 同步语句块实现使用的是monitorenter(同步代码块开始位置)和monitorexit(结束位置)指令,当线程执行monitorenter时,
线程尝试获取monitor(monitor对象存在于每个Java对象的对象头中,也就是为什么java中任意对象可以作为锁的原因)的持有权,如果计数器为0,则可以获取成功获取
获取后将锁计数器设置为1。其它线程等待锁释放,当线程执行完monitorexit后,将计数器设为0。表明锁已被释放,线程又可以继续进行获取锁操作。 2. 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗 synchronized加到static静态方法和synchronzied(class)代码块上都是给Class类加锁。 synchronzied加到实例方法和synchronized(this)代码块上都是给对象实例加锁。 3、单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理 public class Singleton { private volatile static Singleton uniqueInstance; private Singleton() { } public static Singleton getUniqueInstance() { //先判断对象是否已经实例过,没有实例化过才进入加锁代码 if (uniqueInstance == null) { //类对象加锁 synchronized (Singleton.class) { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } } 另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也很有必要。 uniqueInstance = new Singleton();这段代码其实是分三步执行: 1、为uniqueInstance分配内存空间 2、初始化 uniqueInstance 3、将uniqueInstance 指向分配的内存地址 但由于jvm具有指令重排的特性,执行的顺序可能变为为1->3->2。指令重排在单线程环境下不会出现问题。但是在多线程环境下会导致一个线程获得还没有初始化的实例。
例如,线程T1执行了1和3,此时T2调用getUniqueInstance()后发现uniqueInstance不为空,因此返回uniqueInstance,但此时uniqueInstance还未被初始化,
使用volatile可以禁止JVM的指令重排,保证在多线程环境下也能正常运行
volatile
概述:被volatile修饰的变量,通过lock标记, 通知其它线程该变量值不太稳定,其它线程会通过cpu在cache中找到与该变量与之对应的cache line,
并将其cache line置为无效,此后,其它线程就会从主内存中读取该变量的值,而不再直接从工作空间(cache)进行读取。
1、 讲一下Java内存模型 在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,
线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,
而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。 要解决这个问题,就需要把变量声明为volatile,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取(代替在寄存器中读取)。 说白了, volatile 关键字的主要作用就是保证变量的可见性,然后还有一个作用是防止指令重排序,因此它也具有有序性。
synchronzied和volatile关键字区别: 1、volatile解决变量在多个线程之间的可见性,synchronized解决的是多个线程之间访问资源的同步性。 2、volatile只能保证可见性,不能保证原子性,synchronized两者都能保证。 3、多线程访问volatile不会发生阻塞,而synchronized会发生阻塞。
4、volatile只能修饰变量,synchronized只能修饰方法或者语句块。
1、实现Runnable接口和Callable接口的区别 Runnable接口不会返回结果或抛出检查异常,而Callable接口可以返回结果或抛出检查异常。 2、执行execute()和submit()方法的区别 execute方法用于提交不需要返回值的任务 submit()方法用于提交需要返回值的任务,线程池会返回一个Future类型 的对象,通过这个Future对象可以判断任务是否执行成功,并且可以通过get() 方法来获取返回值。get()方法会阻塞当前线程直到任务完成。