面试题
谈谈你对volatile的理解
可见性
java内存模型规定所有变量存储到主内存,多个线程操作属性时 会从主内存中拷贝一份到自己的工作内存空间,其中某一个线程修改完写会主内存 其他线程是不知道的,让其他线程知道主内存的属性改变就叫可见性
class Person{
volatile int age =0;
public void show(){
this.age=60;
}
}
public class Demo {
public static void main(String[] args) {
Person p = new Person();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"come in"+p.age);
try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}
p.show();
System.out.println(Thread.currentThread().getName()+"ok"+p.age);
},"AAA").start();
while(p.age==0){
// System.out.println("循环");
}
System.out.println(Thread.currentThread().getName()+"执行完毕"+p.age);
}
}
不保证原子性
import java.util.concurrent.atomic.AtomicInteger;
class Person{
int age =0;
AtomicInteger atomicInteger = new AtomicInteger();
public void show(){
age++;
}
public void show1(){
atomicInteger.getAndIncrement();
}
}
public class Demo {
public static void main(String[] args) {
Person p = new Person();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
for (int j = 0; j <2000 ; j++) {
p.show();
}
},i+"").start();
}
for (int i = 0; i < 1000; i++) {
new Thread(()->{
for (int j = 0; j <2000 ; j++) {
p.show1();
}
},i+"").start();
}
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"执行完毕"+p.age);
System.out.println(Thread.currentThread().getName()+"执行完毕"+p.atomicInteger);
}
}
上面的案例 :因为age++由三步组成 发现volatile 无法保证原子性 使用synchronized大材小用,所以加入了AtomicInteger 来保证操作的整数是原子性的 那么他为什么可以保证原子性 后面CAS说
禁止指令重排
cpu中加入屏障 屏障前后不能重排序
class Person{
int age =0;
boolean flag=false;
public void show(){
this.age=1; //语句1
this.flag=true; //语句2 这俩条语句如果发生重拍 结果会不一样
System.out.println(age+"show");
}
public void show1(){
if(this.flag){
age=age+5;//5 6
System.out.println(age+"show1");
}
}
}
你在哪些地方用到了volatile
单例模式
以懒汉式的单例模式举例 ,会有并发问题存在 可以加synchronized 解决 但是并发量会下降
double check lock
class Person{
private Person(){
System.out.println("构造被调用");
}
private static Person p = null;
public static Person getInstance() {
if (p == null) {
synchronized (Person.class) {
if(p==null) {
p = new Person();
}
}
}
return p;
}
}
public class Demo {
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(()->{
Person.getInstance();
}).start();
}
}
}
这个程序正确率99.99999% 但为什么还是出错
因为 p=new Person 不存在数据依赖 可能会发生指令重排
分三步 1内存开辟空间 2变量p指向内存空间 3初始化对象 重排之后 1 3 2 线程A执行完 3 此时对象创建好但是p还是为空 B线程过来 又创建了一个对象 这样就会发生多个对象的情况
CAS你知道么
比较并交换
多个线程操作的时候 先把主内存的值读取到各自的工作内存 每个线程都会设置期望值 就是在写回的时候比较期望值和开始主内存的值是否一样 如果不一样说明被人修改过 就要重新读取主内存的值
import java.util.concurrent.atomic.AtomicInteger;
public class Demo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2019));//true
System.out.println(atomicInteger.compareAndSet(5, 2020));//flse
}
}
说一说CAS原理
为什么 atomicInteger.getAndIncrement();能够保证原子性 地层以来了unsafe类
unsafe底层用c语言调用内存地址
再回头有看CAS 期望值就是指定内存的值
源码
CAS缺点
循环时间长 开销大 只能保证一个功效变量的原子操作 ABA问题
ABA问题
T1线程执行得慢 取出来的值是A 期望值是A B线程 执行的快 取出A 改成B 又改成C 最后又改成A,中间的一系列操作之后 T1写回 发现和期望值一样 对于T1来说 没人动过这个数据 单其实被该国很多次了
原子引用类型
也就是自定义一个原子类
原子版本号解决
类 AtomicStampReference
加入版本号 每修改一次版本号加一次
AtomicStampReference.compareAndSet(期望值 ,设置值,期望版本号 ,设置版本号)
AtomicStampReference.getStamp() 获取版本号
AtomicStampReference.getReference() 获取值
公平锁与非公平锁
Lock lock = new Reentrantlock() 默认是非公平锁 传true就是公平锁
对于synchroized锁来说也是非公平锁
公平锁 多个线程按照申请顺序来获取锁
非公平锁:多个线程获取锁的顺序和申请顺序无关,有可能造成饥饿现象
可重入锁(递归锁)
同步方法A调用同步方法B 会自动获取B的锁
synchroized和Reentrantlock是可重入锁 避免死锁 ,
自旋锁
不适用锁 而是使用循环达到锁的目的
import java.util.concurrent.atomic.AtomicReference;
class MyThread{
AtomicReference<Thread> reference = new AtomicReference<>();
Thread t = Thread.currentThread();
public void mylock(){
System.out.println(Thread.currentThread().getName()+"进入");
while (!reference.compareAndSet(null,t)){
System.out.println(Thread.currentThread().getName()+"循环");
}
System.out.println(Thread.currentThread().getName()+"完成");
}
public void myUnLock(){
reference.compareAndSet(t,null);
System.out.println(Thread.currentThread().getName()+"释放锁");
}
}
public class Demo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(()->{
myThread.mylock();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread.myUnLock();
},"AA").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
myThread.mylock();
myThread.myUnLock();
},"BB").start();
}
}
独占锁(写锁)共享锁(读锁)互斥锁
读写锁
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Demo{
public static void main(String[] args) {
Book book = new Book();
for (int i = 0; i <5 ; i++) {
String a = String.valueOf(i);
new Thread(()->{
book.put(a,a);
},String.valueOf(i)).start();
}
for (int i = 0; i <5 ; i++) {
String b = String.valueOf(i);
new Thread(()->{
book.get(b);
},String.valueOf(i)).start();
}
}
}
class Book{
private Map<String,String> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key,String value){
readWriteLock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"\t正在写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"\t写入完成");
}catch(Exception e){
e.printStackTrace();
}finally{
readWriteLock.writeLock().unlock();
}
}
public void get(String key){
readWriteLock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"\t正在读"+key);
String value = map.get(key);
System.out.println(Thread.currentThread().getName()+"\t读完成"+value);
}catch(Exception e){
e.printStackTrace();
}finally{
readWriteLock.readLock().unlock();
}
}
}