java线程并发面试_多线程并发编程面试常考

对象在内存中的内存布局

2370544c7a65dd33f770e62597d3decb.png

用sychronized锁住对象后该对象的锁状态升级过程:new - 无锁态 - 偏向锁 - 轻量级锁/自旋锁/无锁 (CAS)- 重量级锁 - GC标记信息

0e7f50196b960cf70d23542a63cf5779.png

eef1f5327093c33fa68eb2696ede20d9.png

4d41dac27d3600a676308cd3c12ffadf.png

80b4824a0eb8147158c73a43045f3736.png

fd7dba4f2548e69176f9cc19fb038a8f.png

线程的几个状态

NEW(新建状态)

Runnable

Ready(就绪状态,线程被放在等待队列中,等着被CPU执行)

Running(运行状态,被扔到CPU中执行)

Blocked

Waiting

TimedWaiting

Terminated(终止态)

三种新建线程的方法

实现Thread类

实现Runnable接口

线程池

线程的常用方法:

sleep(),沉睡一段时间(当前线程回到就绪状态),这段时间CPU执行其它线程

yield(),和sleep()类似,让出CPU,当前线程回到就绪状态。使用很少见

join(),通知其它线程获得CPU执行,比如在t1线程内运行t2.join(),意思就是t1线程通知t2线程执行,自己回到就绪状态。

Synchronized讲解

085ed57e2614a1ad4cb6a478ca16707d.png

synchronized实现过程:(不能禁止指令重排)

Java代码:synchronized

monitorenter、moniterexit

执行过程中自动升级(偏向锁、自旋锁、重量级锁)

更底层的实现lock comxchg

volatile讲解:

保证变量的各线程可见性/数据一致性 (多个线程要用到变量时,重新去内存拿)

禁止CPU指令重排(在单线程没问题,多线程就会出现问题。为什么要指令重排,其实就是因为CPU太快了,而访问内存比访问缓存又慢了太多)

举个例子:对象的初始化三个步骤Person p = new Person("zeng", 24);

申请对象Person的内存,这个时候给实例变量设置了默认值,比如name = null; age = 0;

调用该对象的构造函数进行真正的初始化实例变量name = "zeng"; age = 24;

返回对象Person给p

volatile不能实现synchronized的原子性操作

比如定义一个变量volatile int count = 0;10个线程分别count++加1000次,最终的count不一定会是10000,因为这里的count++并不是一个原子性操作,它包含好几个指令,所以为了要实现整个的count++原子性操作,也就是必须要使用sychronized对count++加锁。

再注意一些问题:

在用synchronized锁住一个对象时,这个时候不能将这个引用去指向另一个对象

不要用synchronized去锁一个String、Integer等基本数据类型的封装类的对象

ThreadLocal讲解

ThreadLocal可以作为每个线程存放属于自己变量的容器。

存放的容器是ThreadLocalMap。其中ThreadLocalMap属于每一个线程,也就是说每一个线程都有自己的ThreadLocalMap。

ThreadLocalMap中的Entry的key为什么要设置为虚引用?(这个是重点)

首先ThreadLocalMap的底层存储格式为Entry的key-value数组,其中的key就是当前的引用型变量ThreadLocal(如下图所示)。试想,如果我们在线程运行过程中,将ThreadLocal置为null,那么这个时候Entry中的ThreadLocal理应被回收了,但如果Entry的key被设置成强引用则该ThreadLocal就不能被回收,所以需要设置为弱引用,设置为弱引用之后,垃圾回收线程只要发现有弱引用指向的对象,那么就会回收这个对象,这样在多线程使用中可以避免内存泄漏。

a760db1a601939db024d69c6eb1f8511.png

CAS(无锁优化/自旋):

CompareAndSwap

Java里面java.util.concurrnet.atomic.AtomicXXX开头的类都是使用CAS自旋锁实现的。内部都是使用UnSafe这个类的compareAndSet等操作实现线程安全地修改值

举个例子:AtomicInteger count = new AtomicInteger(0);在上面的volatile的讨论中,count++如果不加sychronized锁会导致非原子性操作,但这里直接使用AtomicInteger即可实现线程可见、原子性操作,将count++到10000。并且不需要volatile、synchronized。

ABA问题(1变为2又被变为1),加版本号version

所有的Java中CAS的操作基本上都是用的UnSafe这个类,这个UnSafe使Java语言有了像C++的直接操作JVM内存的能力。

ReentrantLock(可重入锁,公平锁(默认是非公平锁))本身底层也是CAS

可以替代synchronized,替换方法:lock.lock();

可以通过lock.interupt的方法将该锁设置为可以通过interup方法唤醒正在wait的线程

相比上个特点,synchronized的线程,wait之后必须通过其它线程的notify()才能唤醒

如果设置为公平锁,那么线程在抢一个资源时,会进入优先队列排队按先后顺序等待

synchronized是非公平锁

synchronized自动加锁解锁,ReentrantLock手动加锁解锁lock.lock()

底层实现:ReentrantLock是CAS的实现,synchronized底层是有锁的升级过程(4种)

CountDownLatch锁(倒计时完了继续执行(门栓))

CyclicBarrier锁(当线程数目到达某个数目(栅栏值)时,继续执行后面的事物)

Phase锁(阶段锁,CyclicBarrier的升级版本,有多个阶段,比如结婚现场有7个人,先7人到达现场,再7人吃完饭,再xxxxx)

ReadWriteLock(共享锁、排他锁、多个线程可以一起执行)

Semaphore(信号量,用于限流(仅允许几个线程同时工作))

Exchanger(两个线程运行时交换值)

LockSupport(可以通过park()方法随时将线程停止,并通过unpark()方法随时让某线程就绪)

面试题1:定义两个线程,A线程往容器里放数据,B线程监测容器容量为5时,停止运行

有3种方法

使用wait()与notify()方法的组合。这个很重要

使用门栓锁CountDownLatch

使用LockSupport直接park()与unpark()

面试题2:顺序打印A1B2C3……

面试题3:生产者消费者问题

版本1 通过synchronized、wait()、notify()实现

package zr.thread;

import java.util.LinkedList;

import java.util.concurrent.TimeUnit;

/*

生产者与消费者实现1

写一个固定容量同步容器,拥有put和get方法, 以及getCount方法

能够支持2个生产者线程以及10个消费者线程的阻塞调用

使用wait()和notifyAll()来实现

这个方法是有瑕疵的,因为使用notifyAll()会唤醒所有的其它等待队列的线程,包括生产者、消费者

有没有办法只唤醒生产者,或者只唤醒消费者?

*/

/**

* @author ZR

* @Classname MyContainer1

* @Description 生产者消费者最简单写法

* @Date 2020/9/12 21:02

*/

public class MyContainer1 {

final private LinkedList lists = new LinkedList<>();

// 最多10个元素

final private int MAX = 10;

private int count = 0;

// 因为++count所以要加synchronized

public synchronized void put(T t){

// 想想为什么用while而不是if

while(lists.size() == MAX){

try{

this.wait();

}catch (InterruptedException e){

e.printStackTrace();

}

}

lists.add(t);

++count;

// 通知所有消费者线程消费

// 这个方法其实是有点小瑕疵的,因为notifyAll()会叫醒所有的其它wait()线程,也包括了另一个生产者

this.notifyAll();

}

// 因为--count所以要加synchronized

public synchronized T get(){

T t = null;

while(lists.size() == 0){

try{

this.wait();

}catch (InterruptedException e){

e.printStackTrace();

}

}

t = lists.removeFirst();

--count;

// 通知生产者进行生产

// 这个方法其实是有点小瑕疵的,因为notifyAll()会叫醒所有的其它wait()线程,也包括了其它消费者

this.notifyAll();

return t;

}

public static void main(String[] args){

MyContainer1 c = new MyContainer1<>();

// 启动消费者线程

for(int i = 0; i < 10; i++){

new Thread(()->{

for(int j = 0; j < 5; j++)

System.out.println(c.get());

}, "customer" + i).start();

}

try {

TimeUnit.SECONDS.sleep(2);

} catch (InterruptedException e) {

e.printStackTrace();

}

// 启动生产者线程

for(int i = 0; i < 2; i++){

new Thread(()->{

for(int j = 0; j < 25; j++)

c.put(Thread.currentThread().getName() + " " + j);

}, "producer" + i).start();

}

}

}

版本2 通过ReentrantLock实现

package zr.thread;

import com.sun.org.glassfish.external.statistics.CountStatistic;

import java.util.LinkedList;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

/**

* @author ZR

* @Classname MyContainer2

* @Description TODO

* @Date 2020/9/12 21:27

*/

public class MyContainer2 {

final private LinkedList lists = new LinkedList<>();

// 最多10个元素

final private int MAX = 10;

private int count = 0;

private Lock lock = new ReentrantLock();

// Condition的本质就是等待队列,在这里生产者在生产者的队列,消费者在消费者的队列

// 在Container1例中,等待队列只有一个,生产者和消费者都在里边儿

private Condition producer = lock.newCondition();

private Condition customer = lock.newCondition();

public void put(T t){

try {

// 需要手动加锁

lock.lock();

while(lists.size() == MAX)

producer.await();

lists.add(t);

++count;

// 通知消费者线程进行消费

customer.signalAll();

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

// 手动解锁

lock.unlock();

}

}

public T get(){

T t = null;

try {

lock.lock();

while(lists.size() == 0)

customer.await();

t = lists.removeFirst();

--count;

// 通知生产者线程生产

producer.signalAll();

} catch (InterruptedException e){

e.printStackTrace();

} finally {

lock.unlock();

}

return t;

}

public static void main(String[] args){

MyContainer2 c = new MyContainer2<>();

// 启动消费者线程

for(int i = 0; i < 10; i++){

new Thread(()->{

for(int j = 0; j < 5; j++)

System.out.println(c.get());

}, "customer" + i).start();

}

try {

TimeUnit.SECONDS.sleep(2);

} catch (InterruptedException e) {

e.printStackTrace();

}

// 启动生产者线程

for(int i = 0; i < 2; i++){

new Thread(()->{

for(int j = 0; j < 25; j++)

c.put(Thread.currentThread().getName() + " " + j);

}, "producer" + i).start();

}

}

}

AQS(CLH)队列

89f717fab001d4efb90f5efcefc9798f.png

注意这里面的state根据不同的同步锁取不同的意义,比如:

ReentrantLock,state = 0代表unlock,state = 1代表lock

CountDownLatch,state = 5代表需要倒计数5个数,才继续下面的操作

下面的Node表示一个双向链表,这里面存储的就是线程Thread!!!!!注意看下图!!!!!!

da7b71fe174f710d54b2851eebc2bf62.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值