2019年互联网面试题第二季(1.1)
作者:互联网
2019年互联网面试题第二季
一.JUC多线程及并发包
1.1谈谈你对volatile的理解
1.volatile是Java虚拟机提供的轻量级的同步机制
1.1 保证可见性
1.2 不保证原子性
1.3 静止指令重排
2.理解JMM
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念 并不真实存在,它描述的是一组规则或规范通过规范定制了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.
JMM关于同步规定:
1.线程解锁前,必须把共享变量的值刷新回主内存
2.线程加锁前,必须读取主内存的最新值到自己的工作内存
3.加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:
JMM的特点: 保证数据安全性
-
可见性:各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存操作后再写回主内存中的
-
原子性:number++在多线程下是非线程安全的,如何不加synchronized解决?
-
有序性
JMM可见性 代码验证
package com.ybzn._01.juc;
import java.util.concurrent.TimeUnit;
class MyDate{
volatile int number =0;
public void addTO60(){
this.number=60;
}
}
/**
* 1.验证volatile可见性
* 1.1 假如 int number =0; number变量之前没有加volatile修饰
*/
public class VolatileDemo {
public static void main (String[] args) {// main 线程
seeOkByVolatile();
}
/**
* volatile可以保证数据可见性,及时通知其他线程,可以尝试去掉volatile
*/
private static void seeOkByVolatile () {
MyDate myDate=new MyDate();
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t come in");
try {
TimeUnit.SECONDS.sleep(3);
myDate.addTO60();
System.out.println(Thread.currentThread().getName()+" update"+myDate.number);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AAA").start();
//第二个线程 就是我们的main线程
while (myDate.number==0){
// main 线程一直在等待循环,直到number不为0
}
System.out.println(Thread.currentThread().getName()+" over out");
}
}
原子性 代码验证 未解决Case
package com.ybzn._01.juc;
import java.util.concurrent.TimeUnit;
class MyDate{
volatile int number =0;
// 请注意,此时 number前面也是加了volatile关键字,volatile不保证原子性
public void addPlus(){
number++;
}
}
/**
* 1.验证volatile可见性
* 1.1 假如 int number =0; number变量之前没有加volatile修饰
* 1.2 添加了volatile可以解决 可见性问题
* 2. 验证volatile不保证原子性
* 2.1 原子性是什么意思?
* 不可分割,操作完整性,即,某个线程在做某个具体业务的时候,中间不可能
* 被加塞,或者分割,需要整体数据的同时成功和同时失败。
*/
public class VolatileDemo {
public static void main (String[] args) {// main 线程
MyDate myDate =new MyDate();
for (int i = 1; i <20 ; i++) {
new Thread(() -> {
for (int j = 0; j <10000 ; j++) {
myDate.addPlus();
}
}, String.valueOf(i)).start();
}
//需要等待上面20个线性都运行完毕以后,在用main线程最终取得结果值;
while (Thread.activeCount()>2)
{
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" \t finally number value: "+myDate.number);
}
}
输出结果: 最终的结果,并不是我们想要的200000 而是 另外一个数
原子性 代码验证 解决Case
package com.ybzn._01.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyDate{
int number =0;
int numberLow=0;
AtomicInteger atomicInteger = new AtomicInteger();
public AtomicInteger getAtomicInteger () {
return atomicInteger;
}
}
/**
* 1.验证volatile可见性
* 1.1 假如 int number =0; number变量之前没有加volatile修饰
* 1.2 添加了volatile可以解决 可见性问题
* 2. 验证volatile不保证原子性
* 2.1 原子性是什么意思?
* 不可分割,操作完整性,即,某个线程在做某个具体业务的时候,中间不可能
* 被加塞,或者分割,需要整体数据的同时成功和同时失败。
* 2.2 why 通过javac -p观察字节码
* 2.3 如何解决原子性?
* ** 1. synchronized
* ** 2. 使用atomicInteger JUC方法
*/
public class VolatileDemo {
public static void main (String[] args) {// main 线程
MyDate myDate =new MyDate();
for (int i = 1; i <=20 ; i++) {
new Thread(() -> {
for (int j = 0; j <1000 ; j++) {
myDate.number = myDate.getAtomicInteger().getAndIncrement();
myDate.numberLow++;
}
}, String.valueOf(i)).start();
}
//需要等待上面20个线性都运行完毕以后,在用main线程最终取得结果值;
while (Thread.activeCount()>2)
{
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" \t finally number value: "+myDate.number);
System.out.println(Thread.currentThread().getName()+" \t finally number value: "+myDate.numberLow);
}
}
代码执行: 正确计算出来
禁止指令重排,主要通过增加 Load-Load,Load-Store 内存屏障实现的,具体请查看上一篇博客
3 单例模式不安全
在多线程的环境下,之前所适用的单例模式生成,会产生不可预料的效果,下面请看代码演示:
package com.ybzn._01.juc;
/**
* 多线程环境下 单例模式
*/
public class SingletonDemo {
private static SingletonDemo instance =null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+" \t 构造方法Sing");
}
public static SingletonDemo getInstance () {
if (instance==null)
instance= new SingletonDemo();
return instance;
}
public static void main (String[] args) {
// 单线程
// System.out.println(SingletonDemo.getInstance()==SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance()==SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance()==SingletonDemo.getInstance());
//多线程
for (int i = 1; i <10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}
}
结果分析:本来只需要打印一次的,却打印出了多次,
再次运行:每次打印的结果都是不一样的
解决方案:
- 在
getInstance()
方法中添加synchronized
关键字可以解决,缺点是 太浪费资源,杀鸡用牛刀 - 采用
DCL (Double Check Lock 双重检测锁)
实现,但是成功率为99%,任然不安全,原因是因为,有指令重排的存在【因为没有数据依赖性】,需要加入volatile禁止指令重排。 修改getInstance()
其他不变即可。 - 推荐方法 针对
SingletonDemo
添加volatile
关键字,禁止指令重排
// DCL (Double Check Lock 双重检测锁)
public static SingletonDemo getInstance () {
if (instance==null)
{
synchronized (SingletonDemo.class){
if(instance==null){
instance= new SingletonDemo();
}
}
}
return instance;
}
标签:面试题,1.1,number,2019,线程,内存,volatile,SingletonDemo,public 来源: https://www.cnblogs.com/blogger-Li/p/14402982.html