您的位置:首页 > 职场人生

2020大厂面试JUC线程重要技术点【集合+线程+阻塞队列+线程池】

2020-07-22 17:06 543 查看

一、集合安全问题

1.1 ArrayList

  • 空的集合初始值为10
  • object类型的数组
  • 扩容Arrays.copyOf 原始大小的一倍
  • 线程不安全

1.1.1 不安全

java.util.concurrentModificationException

  • Vector加了锁保证了数据一致性,但是并发性急剧下降,所以很少用!
  • ArrayList牺牲了线程安全从而保证并发性

1.1.2 如何解决ArrayList线程不安全问题

1.new Vector<>()
2.Collection与Collections

  • Collection为集合类的父接口
  • Collections为辅助类来解决ArrayList线程不安全问题
List<String> list = Collections.synchronizedList(new ArrayList<>());

3.CopyOnWriteArrayList<>()类
写时复制 读写分离的思想

List<String> list = new CopyOnWriteArrayList<>();

private tranisent volatile []…

1.2 HashSet

底层:HashMap 初始值16 负载因子0.75

线程不安全解决的问题与上面雷同
解决办法一Collections.synchronizedSet():

解决办法二CopyOnWriteArraySet<>():

1.3 HashMap

演示错误 java.util.concurrentModificationException


解决办法一:

Map<String,String> map = new ConcurrentHashMap<>();

解决办法二:

Collections.synchronizedMap();

二、JAVA锁机制

公平锁:多个新线程按照申请顺序来获取锁,先到先得 非
非公平锁:多个线程并不是按照申请的顺序,有可能造成优先级反转或者饥饿现象。

2.1 可重入锁【递归锁】

ReentrantLock
线程可以进入任何一个它已经拥有的锁同步着的代码块
通过构造函数制定该锁是否为公平锁,默认是非公平锁
非公平锁的优势在于吞吐量比较大
对于Synchronized而言,也是一种非公平锁
作用:避免死锁

2.2 自旋锁

是指尝试获取锁的线程不会阻塞,而是采用循环的方式来尝试乎获取锁,这样的好处就是减少线程上下文的切换消耗,缺点是循环会消耗CPU.
do
while()
CAS
期望值与工作区间的值比较

2.2.1 自旋锁代码

public class SpinLock{
//原子引用线程
AtomicReference<Thread> ar = new AtomicReference<>();

public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"come in");
while(!ar.compareAndSet(null,thread)){

}
}

public void myUnLock(){
Thread thread = Thread.currentThread();
ar.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"unlock");
}

public static void main(String[] args){
SpinLock sl = new SpinLock();

new Thread(()-> {
// 加锁
sl.myLock();
// 暂停一会
try{TimeUnit.SECONDS.sleep(5);}catch(...){}
// 解锁
sl.myUnLock();
},"AA").start();

try{TimeUnit.SECONDS.sleep(1);}catch(...){}

new Thread(()-> {
// 加锁
sl.myLock();
try{TimeUnit.SECONDS.sleep(1);}catch(...){}
// 解锁
sl.myUnLock();
},"BB").start();
}
}

2.3 独占锁(写锁)/共享锁(读锁)

独占锁:指该锁一次只能被一个线程所持有的。
ReentrantLock Synchronized 都是独占锁
共享锁:该锁可以被多个线程所持有
ReentrantReadWriteLock为共享锁,写锁为独占锁
读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程都是互斥的。

一个线程去写【原子+独占】绝对不可以被阻断,多个线程可以读
【问题描述如下:】

class MyCache{//缓存资源类
//volatile 可见性 不保证原子性 禁止指令重排
private volatile Map<String,Object> map = new HashMap<>();
//解决问题 原子性
//private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock ();

public void put(String key,Object value){

//加写锁
rwLock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"正在写入"+key);
try{Time.MILLSECONDS.sleep(300);}catch(){};
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完成");
}catch(Exception e){
}finally{
rwLock.writeLock().unlock();
}
}

public void get(String key,Object value){
//加读锁
rwLock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"正在读取"+key);
try{Time.MILLSECONDS.sleep(300);}catch(){};
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完成"+result);
}catch(Exception e){
}finally{
rwLock.readLock().unlock();
}

}
}

public class ReadWriteLockDemo{
public static void main(String[] args){
MyCache myCache = new MyCache();
//写
for(int i = 1;i <= 5;i++){
new Thread(() -> {
final int tempInt = i;
myCache.put(tempInt+"",tempInt+"");
},String.valueOf(i)).start();
}

//读
for(int i = 1;i <= 5;i++){
new Thread(() -> {
final int tempInt = i;
myCache.get(tempInt+"");
},String.valueOf(i)).start();
}
}
}

这样既保证了数据一致性,有保证了并发性,读写分离。
Synchronized太重量。

三、CountDownLatch【线程做减法倒计时】

3.1 离开教室锁门问题产生!

public class CountDownLatchDemo{
public static void main(String[] args){
for(int i = 1;i<=6;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"上完自习,离开教室");
},String.valueOf(i)).start();
}
System.out,println(Thread.currentThread().getName()+"班长最后关门走人");

}

}

3.2 解决问题:CountDownLatch

public class CountDownLatchDemo{
public static void main(String[] args){
// 计数
CountDownLatch countDownLatch = new CountDownLatch(6);

for(int i = 1;i<=6;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"上完自习,离开教室");
countDownLatch.countDown();//减1操作
},String.valueOf(i)).start();
}
// 主线程等待
countDownLatch.await();
System.out,println(Thread.currentThread().getName()+"班长锁门,最后关门走人");

}

}

四、CyclicBarrier【加法】

加法 与CountDownLatch【减法】相反
加到一定的数值然后做事

最后一个线程到达屏障时候才会进行

public class CountDownLatchDemo{

public static void main(String[] args){
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{System.out.println("***召唤神龙***");});
for(int i = 1;i<=7;i++){
final int tempInt  = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集到第"+tempInt +"颗龙珠");
cyclicBarrier.await();
},String.valueOf(i)).start();
}
}
}

五、Semaphore【信号量】

多个共享资源的互斥使用
并发线程数量的控制

public class CountDownLatchDemo{

public static void main(String[] args){
// 模拟3个停车位
Semaphore semaphore = new Semaphore(3);
for(int i = 1; i <= 6;i++){
new Thread(()->{
try{
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"停车3S后,离开车位");
}catch(...){
}finally{
semaphore.release();
}

},String.valueOf(i)).start();
}
}
}

六、阻塞队列【MQ核心】


6.1 阻塞队列ArrayBlockingQueue<>()


报异常

没有异常,直接返回布尔类型false

一直阻塞,取出用take方法

过时不候

6.2 阻塞队列 SynchronousQueue<>()


不消费,不会继续插下一个,会卡在 put(1)

6.3 生产者-消费者案例【新方式】

案例:一个初始值为0的变量,两个线程交替操作,一个加一,一个减一,来5轮

class SahreData{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// 加法
public void increament(){
lock.lock{
try{
//1.判断
while(number != 0){
condition.await();
}
//2.干活
number++;
System.out.println(Thread.currentThread().getName()+ number);
//3.通知唤醒
condition.signalAll();
}catch(...){
}finally{
lock.unlock();
}
}
}

// 减法
public void decreament(){
lock.lock{
try{
//1.判断
while(number == 0){
condition.await();
}
//2.干活
number--;
System.out.println(Thread.currentThread().getName()+ number);
//3.通知唤醒
condition.signalAll();
}catch(...){
}finally{
lock.unlock();
}
}
}
}

public class ProdConsumer{
public static void main(String[] args){
ShareData shareData = new ShareData();

new Thread(()->{
for(int i = 1;i<=5;i++){
shareData.increament();
}
},"A").start();

new Thread(()->{
for(int i = 1;i<=5;i++){
shareData.decreament();
}
},"B").start();
}
}

6.4 虚假唤醒

防止虚假唤醒 一定要用while 不要用if
6.3 中的代码换成If,多添加几个线程就会出现问题!
会出现结果 1 2 -1 0等等,并没有控制住结果。

七、Synchrinized与Lock的区别

1.前者JVM层面,是Java的关键字,后者是API层面,java5以后的出现的。
2. synchronized不可以中断
3. Reentranrlock可以中断,设置超时,或者中断方法
4.synchronized默认非公平锁
5.Reentranrlock可以分组唤醒,精确唤醒
6.synchronized要么随即唤醒一个,要么唤醒全部notify() notifyAll()
实现案例:

多线程之间要按照顺序调用,实现A-B-C三个线程启动:
AA打印5次,BB打印10次,CC打印15次
然后
AA打印5次,BB打印10次,CC打印15次

循环10次

7.1 打印案例【新的Lock版本】

class ShareResource{
private int number = 1;//A1 B2 C3
private Lock lock = new ReentrantLock();
private Condition c1 = new lock.newCondition();
private Condition c2 = new lock.newCondition();
private Condition c3 = new lock.newCondition();

public void prints5(){
lock.lock();
try{
//1.判断
while(number != 1){
c1.await();
}
//2.干活
for(int i = 1;i<=5;i++){
System.out.println(Thread.currentThread().getName()+"\t"+number);
}
//3.通知2
number = 2;
c2.signal();
}catch(){}finally{
lock.unlock();
}
}

public void prints10(){
lock.lock();
try{
//1.判断
while(number != 2){
c2.await();
}
//2.干活
for(int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"\t"+number);
}
//3.通知2
number = 3;
c3.signal();
}catch(){}finally{
lock.unlock();
}
}

public void prints15(){
lock.lock();
try{
//1.判断
while(number != 3){
c3.await();
}
//2.干活
for(int i = 1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"\t"+number);
}
//3.通知2
number = 1;
c1.signal();
}catch(){}finally{
lock.unlock();
}
}

}

public class SyncAndReentrantLockDemo{

ShareSource shareSource = new ShareSource();
new Thread(()->{
for(int i=0;i<=10;i++){
shareSource.prints5();
}
},"A").start();

new Thread(()->{
for(int i=0;i<=10;i++){
shareSource.prints10();
}
},"B").start();

new Thread(()->{
for(int i=0;i<=10;i++){
shareSource.prints15();
}
},"C").start();
}

7.2 生产消费案例【阻塞队列版本】高并发

class MyResource{
private volatile boolean FLAG = true;//默认开启,生产+消费
private AtomicInteger atomicInteger = new AtomicInteger();

BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue){
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}

public void myProd(){
String data = null;
boolean retValue;
while(FLAG){
data = atomInteger.incrementAndGet()+"";
retValue = blockingQueue.offer(data,2L,TimeUnit.SECONDS);
if(retValue){
System.out.println(Thread.currentThread().getName()+"\t 插入队列"+data+"成功");
}else{
System.out.println(Thread.currentThread().getName()+"\t 插入队列"+data+"失败");
}
TimeUint.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName()+"\t 生产叫停,false 生产结束");
}

public void MyConsumer(){
String result = null;
while(FLAG){
result = blockingQueue.poll(2L,TimeUnit.SECONDS);
if(null == result || result.equalsIngoreCase("")){
FLAG = false;
System.out.println(Thread.currentThread().getName()+"\t 超过2S没有消费,消费退出");
return;
}
System.out.println(Thread.currentThread().getName()+"\t 消费队列"+result+"成功");
}
}

public void stop(){
this.FLAG = fasle;
}
}

public class ProdConsumer_BlockQueueDemo{
public static void main(String[] args){
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));

new Thread(()->{
System.out.println(Thread.currentThread().getName()+ "\t 生产线成启动");
myResource.myProd();
},"Prod").start();

new Thread(()->{
System.out.println(Thread.currentThread().getName()+ "\t 消费线成启动");
myResource.myConsumer();
},"Consumer").start();

//暂停一会
try{TimeUnit.SECONDS.sleep(5);catchh(...){}}
System.out.println("5S结束,大老板叫停,活动结束");
myResource.stop();
}

}

八、线程池

8.1 Runnable与 Callable

class MyThread implements Runnable{
@Override
public void run(){
}
}

class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception{
System.out.println("进来了");
return 1024;
}
}

public class CallableDemo{
public static void main(String[] args){
FuterTask<Integer> futerTask = new FuterTask<>(new MyThread);
Thread t1 = new Thread(futerTask,"AAA");
t1.start();
int result01 = 100;
int result02 = futerTask.get();

System.out.println(result01+result02);
}
}

结果:1124

8.2 线程池的优势

Executor顶级接口和工具包Executors

底层:阻塞队列

1.降低资源消耗
2.提高响应速度
3.提高线程的可管理性
// 拓展工具类
// Array Arrays
// Collection Collections
// Executor Executors

8.3 线程池实现的方式【3种核心】


工作中你用那个??? 哪个都不用的
阿里巴巴开发手册:不允许使用Executors区创建,而是使用ThreadPoolExecutor的方式,这样的处理方式更加明确线程池的运行规则,避免资源耗尽。


但是也要学习!!!!!!如下:
第一种【重要】:

public class MyThreadPoolDemo{
public static void main(String[] args){
// 1个线程池 5个线程
ExecutorService threadPool = Executors.newFixedThreadPool(5);

try{
for(int i = 1;i<=10;i++){//10个用户
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}catch(...){
}finally{
thread.shutdown();
}
}
}


第二种:

public class MyThreadPoolDemo{
public static void main(String[] args){
// 1个线程池 1个线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();

try{
for(int i = 1;i<=10;i++){//10个用户
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}catch(...){
}finally{
thread.shutdown();
}
}
}


第三种:

public class MyThreadPoolDemo{
public static void main(String[] args){
// 1个线程池 不定线程
ExecutorService threadPool = Executors.newCacgedThreadPool();

try{
for(int i = 1;i<=10;i++){//10个用户
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}catch(...){
}finally{
thread.shutdown();
}
}
}

8.4 线程池7大参数


8.5 线程池拒绝策略

等待队列已经满了,再也塞不下新任务了
线程池中的max线程也达到了最大,无法继续为新任务服务

8.6 手写线程池【大厂工作核心–7大参数】

public class MyThreadPoolDemo{
public static void main(String[] args){
ExecutorService threadPool = new ThreadPoolExecutor(
2,//核心数
5,//最大线程数
1L,//活跃时间
TimeUint.SECONDS,//单位
new LinkedBlockingQueue<Runnable>(3),//阻塞队列大小个数
Executors.defaultThreadFactory(),//线程工厂
new ThreadPoolExecutor.AbortPolicy());//拒绝策略,会抛异常

try{
for(int i = 1;i<=5;i++){
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}catch(...){
}finally{
threadPool.shutdown();
}
}
}

8.7 如何合理配置线程的数量呢?

回答:

1.CPU密集型?
2.IO密集型?

1.获取CPU密集型
Runtime.getRuntime().getProcessors()
一般为:CPU核数+1个线程

2.IO密集型
io密集型的任务并不是一直在执行任务,应该配置尽可能多的线程
一般为:CPU核数*2

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: