1.请谈谈对volatile的理解
volatile是java虚拟机提供的一种轻量级的同步机制,基本遵守了JMM规范,保证多线程可见性、有序性(禁止指令重排序),不保证原子性;synchronized全都可以保证。
JMM(Java Memerory Mode):java内存模型,描述的是一种多线程下访问共享内存的规范。
对同步的规定:
线程加锁前,必须读取主内存一份最新的值到自己的工作内存;
线程解锁前,必须将自己工作空间的共享变量的最新值刷新回主内存
加锁解锁必须是同一个线程
JVM运行程序的实体是线程,JVM会为每个线程都创建一个私有的工作内存(栈空间)。Java内存模型规定所有的变量都存储主内存中,被所有的线程共享,但是线程对变量的操作必须在自己的工作空间进行,首先要从主内存拷贝一个副本到工作内存,操作完再刷新回主内存。
2.DCL(Double Check Lock)+volatile改造单例模式
传统版单例模式在多线程环境下无法保证单例
package concurent;
/**
* @ClassName: SingletonDemo
* @description: 单例模式
* @author: leijing
* @Date: 2019/7/21 下午7:31
* @Version: 1.0
*/
public class SingletonDemo {
private static SingletonDemo instance;
public SingletonDemo(){
System.out.println("new SingletonDemo");
}
public static SingletonDemo getInstance(){
if (null == instance){
instance = new SingletonDemo();
}
return instance;
}
public static void main(String[] args) {
//但线程下没问题
// System.out.println(getInstance() == getInstance());
// System.out.println(getInstance() == getInstance());
// System.out.println(getInstance() == getInstance());
// System.out.println(getInstance() == getInstance());
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
getInstance();
}
},"t"+i).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
}
/**无法保证单例
new SingletonDemo
new SingletonDemo
*/
}
第二版:
package concurent;
/**
* @ClassName: SingletonDemo
* @description: 单例模式DCL
* @author: leijing
* @Date: 2019/7/21 下午7:31
* @Version: 1.0
*/
public class SingletonDemo {
private static SingletonDemo instance;
public SingletonDemo(){
System.out.println("new SingletonDemo");
}
public static SingletonDemo getInstance(){
if (null == instance){
synchronized (SingletonDemo.class){
if (null == instance){
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
getInstance();
}
},"t"+i).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
}
}
运行很多次都没问题,但是可能会有指令重排序
有可能访问到还没初始化完成的对象
第三版:
package concurent;
/**
* @ClassName: SingletonDemo
* @description: 单例模式DCL+volatile
* @author: leijing
* @Date: 2019/7/21 下午7:31
* @Version: 1.0
*/
public class SingletonDemo {
private static volatile SingletonDemo instance;
public SingletonDemo(){
System.out.println("new SingletonDemo");
}
public static SingletonDemo getInstance(){
if (null == instance){
synchronized (SingletonDemo.class){
if (null == instance){
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
getInstance();
}
},"t"+i).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
}
}
2.什么是CAS
i++;其实是三个步骤
load i
i+1
store i
CAS(Compare And Swap)比较并交换,是操作系统的一条CPU并发原语。功能是判断内存某位置的值是否为期望值,如果是就更新为新值,这个过程是原子的,线程安全。
Unsafe是CAS的核心类,提供一系列native方法,直接调用操作系统底层资源,java不能直接访问底层系统,所以需要native方法来访问,Unsafe相当于一个后门。
CAS的核心就是Unsafe的cas和自旋锁。
private static final Unsafe unsafe = Unsafe.getUnsafe();//unsafe类保证原子性
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));//value在内存中的偏移量
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
AtomicInteger.getAndIncrement() -> unsafe.getAndAddInt(this, valueOffset, 1);
/**
Object var1 对象
long var2 offset
int var4 1
**/
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;//expected
do {
var5 = this.getIntVolatile(var1, var2);//根据对象和偏移量拿到最新的原值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//如果主内存中的值与var5相等就交换,否则继续查询最新的主内存的值
return var5;
}
缺点:
循环时间长,开销大,会导致CPU利用率飕升
只能保证一个共享变量的原子操作
有ABA问题
3.ArrayList线程安全吗
package concurent;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @ClassName: ContainerUnSafe
* @description: java容器线程安全问题
原因:并发竞争修改导致,数据不一致,并发修改异常
解决方案有三种:
1.new Vector<>();
2.Collections.synchronizedList(new ArrayList());
3.new CopyOnWriteArrayList<>(); 写时复制,读写分离思想
* @author: leijing
* @Date: 2019/7/21 下午9:57
* @Version: 1.0
*/
public class ContainerUnSafe {
public static void main(String[] args) {
final List list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(new Runnable() {
@Override
public void run() {
list.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(list);
}
}).start();
}
}
}
Exception in thread "Thread-2" Exception in thread "Thread-17" Exception in thread "Thread-6" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at concurent.ContainerUnSafe$1.run(ContainerUnSafe.java:22)
at java.lang.Thread.run(Thread.java:745)
3.哪些对象可以被垃圾回收,如何判定是垃圾,GC ROOT有哪些?
不再被引用的对象可以被垃圾回收,通过引用计数器法或者枚举根结点做可达性分析,因为引用计数器法无法解决循环引用的问题,只是一种理念不会被使用,没有被GC ROOT引用到的对象就是垃圾。GC ROOT有四种,虚拟机栈局部变量表中引用的对象、本地方法栈中引用的对象、方法区中静态成员引用的对象、方法区中常量引用的对象。
4、GC算法有哪些?GC垃圾回收器有哪些?
四种垃圾回收算法:引用计数器法、复制、标记-清除、标记-整理,算法是方法论
8种垃圾回收器:串行Serial 、 新生代并发ParNew、并行Parallel
老年代串行Serial Old、并发标记清除CMS、 老年代并行Parallel Old、G1、ZGC
5、可重入锁
重入锁(递归锁):一个线程可以自动获得已经持有的锁所同步着的代码块
synchronized/ReentrantLock是典型的可重入锁(递归锁)
package concurent;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @ClassName: ReentrantLockDemo
* 重入锁(递归锁):一个线程可以自动获得已经持有的锁所拥有的同步方法
* @description: synchronized/ReentrantLock是典型的可重入锁(递归锁)
* @author: leijing
* @Date: 2019/7/22 上午2:34
* @Version: 1.0
*/
class Phone implements Runnable{
//1、synchronized
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName()+" sendSMS");
sendMail();
}
public synchronized void sendMail(){
System.out.println(Thread.currentThread().getName()+" sendMail");
}
//
//2、ReentrantLock
Lock lock = new ReentrantLock();
@Override
public void run() {
sendLockSMS();
}
public void sendLockMail(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+" sendLockMail");
}finally {
lock.unlock();
}
}
public void sendLockSMS(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+" sendLockSMS");
sendLockMail();
}finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) throws InterruptedException {
final Phone phone = new Phone();
//1.synchronized方式
new Thread(new Runnable() {
@Override
public void run() {
phone.sendSMS();
}
},"t1").start();
new Thread(new Runnable() {
@Override
public void run() {
phone.sendSMS();
}
},"t2").start();
Thread.sleep(1000);
System.out.println("============================");
/
new Thread(phone , "t3").start();
new Thread(phone , "t4").start();
}
}
6.synchronized和Lock的区别
synchronized | Lock | |
原始构成 | 关键字 | 接口 |
底层实现 | 对象头monitorentry(1次)、monitorexit(2次确保异常也能释放) | |
阻塞/唤醒方式 | wait/notify/notifyAll必须在synchronized块里边才能调用 | await/signal |
释放方式 | 自动 | 手动 |
是否支持中断 | 否 | 是 |
是否公平 | 否 | 支持公平锁,默认非公平 |
是否支持精确唤醒 | 不支持,随机 | 支持,可以绑定多个Condition |