1.并发编程的三要素是什么?
2.java如何创建线程
继承Thread类创建线程
实现Runnable接口创建线程
使用Callable和Future创建线程 有返回值
使用线程池创建线程
代码演示
import java.util.concurrent.*;
public class threadTest{public static void main(String[] args) throws ExecutionException, InterruptedException {//继承threadThreadClass thread = new ThreadClass();thread.start();Thread.sleep(100);System.out.println("#####################");//实现runnableRunnableClass runnable = new RunnableClass();new Thread(runnable).start();Thread.sleep(100);System.out.println("#####################");//实现callableFutureTask futureTask = new FutureTask(new CallableClass());futureTask.run();System.out.println("callable返回值:" + futureTask.get());Thread.sleep(100);System.out.println("#####################");//线程池ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));threadPoolExecutor.execute(thread);threadPoolExecutor.shutdown();Thread.sleep(100);System.out.println("#####################");//使用并发包ExecutorsExecutorService executorService = Executors.newFixedThreadPool(5);executorService.execute(thread);executorService.shutdown();}
}class ThreadClass extends Thread{@Overridepublic void run() {System.out.println("我是继承thread形式:" + Thread.currentThread().getName());}
}class RunnableClass implements Runnable{@Overridepublic void run(){System.out.println("我是实现runnable接口:" + Thread.currentThread().getName());}
}class CallableClass implements Callable<String> {@Overridepublic String call(){System.out.println("我是实现callable接口:");return "我是返回值,可以通过get方法获取";}
}
————————————————
leader_song
原文链接:https://blog.csdn.net/leader_song/article/details/132094080
3.java常见的锁
4.synchronized的用法
5.synchronized 和 Lock 的区别
- synchronized是可重入锁、不可中断锁,非公平锁、
lock的ReentrantLock是可重入锁,可中断锁,可以是公平锁也可以是非公平锁 - synchronized是JVM层次通过监视器实现的,Lock是通过AQS实现的
5.1什么是AQS锁?
AQS是一个抽象类,可以用来构造锁和同步类,如ReentrantLock,Semaphore,CountDownLatch,CyclicBarrier。
AQS的原理是,AQS内部有三个核心组件,
一个是state代表加锁状态初始值为0,
一个是获取到锁的线程,
还有一个阻塞队列。
当有线程想获取锁时,会以CAS的形式将state变为1,CAS成功后便将加锁线程设为自己。当其他线程来竞争锁时会判断state是不是0,不是0再判断加锁线程是不是自己,不是的话就把自己放入阻塞队列。这个阻塞队列是用双向链表实现的
可重入锁的原理就是每次加锁时判断一下加锁线程是不是自己,是的话state+1,释放锁的时候就将state-1。当state减到0的时候就去唤醒阻塞队列的第一个线程。
5.2有哪些常见的AQS锁
AQS分为独占锁和共享锁
-
ReentrantLock(独占锁):可重入,可中断,可以是公平锁也可以是非公平锁,非公平锁就是会通过两次CAS去抢占锁,公平锁会按队列顺序排队
-
Semaphore(信号量):设定一个信号量,当调用acquire()时判断是否还有信号,有就获取一个信号量,没有就阻塞等待其他线程释放信号量,当调用release()时释放一个信号量,唤醒阻塞线程。
应用场景:允许多个线程访问某个临界资源时,如上下车,买卖票
- CountDownLatch(倒计数器):给计数器设置一个初始值,当调用CountDown()时计数器减一,当调用await() 时判断计数器是否归0,不为0就阻塞,直到计数器为0。
应用场景:启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行
- CyclicBarrier(循环栅栏):给计数器设置一个目标值,当调用await() 时会计数+1并判断计数器是否达到目标值,未达到就阻塞,直到计数器达到目标值
应用场景:多线程计算数据,最后合并计算结果的应用场景
6.为什么使用线程池
7.线程池的核心属性
8.线程池的执行流程
9.线程池的拒绝策略
10.线程池的几种创建方式
11.volatile关键字
11.1说说你对volatile关键字的理解
什么是内存可见性,什么又是重排序呢?
单例模式双重校验锁变量为什么使用 volatile 修饰?
禁止 JVM 指令重排序,new Object()分为三个步骤:
为实例对象分配内存,
用构造器初始化成员变量,
将实例对象引用指向分配的内存;
实例对象在分配内存后实才不为null。如果分配内存后还未初始化就先将实例对象指向了内存,那么此时最外层的if会判断实例对象已经不等于null就直接将实例对象返回。而此时初始化还没有完成。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/w20001118/article/details/125724647
11.2volatile 与 synchronized 的区别?
12.ThreadLocal
12.1ThreadLocal 是什么?
12.2ThreadLocal 可以作用于哪些场景?
12.3为了解决什么问题?
解决了对象在被共享访问时带来的线程安全问题通过 threadLocal.set() 方法将对象实例保存在每个线程自己所拥有的 threadLocalMap 中,
这样的话每个线程都使用自己的对象实例,彼此不会影响从而达到了隔离的作用。
12.4ThreadLocal 内存泄漏的原因?
13.线程的状态转换有什么(生命周期)
-
新建状态(New) :线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
-
就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
-
运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
-
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
-
等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
-
同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
-
其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
-
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
————————————————leader_song
原文链接:https://blog.csdn.net/leader_song/article/details/132094080
14.进程和线程的区别,进程间如何通信
- 进程:系统运行的基本单位,进程在运行过程中都是相互独立,但是线程之间运行可以相互影响。
- 线程:独立运行的最小单位,一个进程包含多个线程且它们共享同一进程内的系统资源
进程间通过管道、 共享内存、信号量机制、消息队列通信
15.什么是死锁
死锁指多个线程在执行过程中,因争夺资源造成的一种相互等待的僵局
16.死锁的必要条件
-
互斥条件:同一资源同时只能由一个线程读取
-
不可抢占条件:不能强行剥夺线程占有的资源
-
请求和保持条件:请求其他资源的同时对自己手中的资源保持不放
-
循环等待条件:在相互等待资源的过程中,形成一个闭环
- 想要预防死锁,只需要破坏其中一个条件即可,比如使用定时锁、尽量让线程用相同的加锁顺序,还可以用银行家算法可以预防死锁
17.CAS
什么是CAS
CAS(Compare-and-Swap)是一种乐观锁的实现方式,全称为“比较并交换”,是一种无锁的原子操作。
在并发编程中,我们都知道i++操作是非线程安全的,这是因为 i++操作不是原子操作,我们之前在讲多线程带来了什么问题中有讲到,大家应该还记得吧?
如何保证原子性呢?
常见的做法就是加锁。
在 Java 中,我们可以使用 synchronized关键字 和 CAS(Compare-and-Swap)来实现加锁效果。
**synchronized 是悲观锁,**尽管随着 JDK 版本的升级,synchronized 关键字已经“轻量级”了很多(前面有细讲,戳链接回顾),但依然是悲观锁,线程开始执行第一步就要获取锁,一旦获得锁,其他的线程进入后就会阻塞并等待锁。
CAS 是乐观锁,线程执行的时候不会加锁,它会假设此时没有冲突,然后完成某项操作;如果因为冲突失败了就重试,直到成功为止。
17.1乐观锁与悲观锁
锁可以从不同的角度来分类。比如我们在前面讲 synchronized 四种锁状态的时候,提到过偏向锁、轻量级锁、重量级锁,对吧?乐观锁和悲观锁也是一种分类方式。
悲观锁
对于悲观锁来说,它总是认为每次访问共享资源时会发生冲突,所以必须对每次数据操作加上锁,以保证临界区的程序同一时间只能有一个线程在执行。
乐观锁
乐观锁,顾名思义,它是乐观派。乐观锁总是假设对共享资源的访问没有冲突,线程可以不停地执行,无需加锁也无需等待。一旦多个线程发生冲突,乐观锁通常使用一种称为 CAS 的技术来保证线程执行的安全性。
由于乐观锁假想操作中没有锁的存在,因此不太可能出现死锁的情况,换句话说,乐观锁天生免疫死锁。
乐观锁多用于“读多写少“的环境,避免频繁加锁影响性能;
悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。
17.2什么是 CAS
在 CAS 中,有这样三个值:
V:要更新的变量(var)
E:预期值(expected)
N:新值(new)
比较并交换的过程如下:
判断 V 是否等于 E,如果等于,将 V 的值设置为 N;如果不等,说明已经有其它线程更新了 V,于是当前线程放弃更新,什么都不做。
这里的预期值 E 本质上指的是“旧值”。
我们以一个简单的例子来解释这个过程:
如果有一个多个线程共享的变量i原本等于 5,我现在在线程 A 中,想把它设置为新的值 6;
我们使用 CAS 来做这个事情;
首先我们用 i 去与 5 对比,发现它等于 5,说明没有被其它线程改过,那我就把它设置为新的值 6,此次 CAS 成功,i的值被设置成了 6;
如果不等于 5,说明i被其它线程改过了(比如现在i的值为 2),那么我就什么也不做,此次 CAS 失败,i的值仍然为 2。
在这个例子中,i就是 V,5 就是 E,6 就是 N。
CAS锁可以保证原子性,思想是更新内存时会判断内存值是否被别人修改过,如果没有就直接更新。如果被修改,就重新获取值,直到更新完成为止。这样的缺点是
-
(1)只能支持一个变量的原子操作,不能保证整个代码块的原子操作
-
(2)CAS频繁失败导致CPU开销大
-
(3)ABS问题:线程1和线程2同时去修改一个变量,将值从A改为B,但线程1突然阻塞,此时线程2将A改为B,然后线程3又将B改成A,此时线程1将A又改为B,这个过程线程2是不知道的,这就是ABA问题,可以通过版本号或时间戳解决
-
那有没有可能我在判断了i为 5 之后,正准备更新它的新值的时候,被其它线程更改了i的值呢?
不会的。因为 CAS 是一种原子操作,它是一种系统原语,是一条 CPU 的原子指令,从 CPU 层面已经保证它的原子性。
当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。
17.3 CAS的缺点
(1)ABA问题
如果一个线程t1正修改共享变量的值A,但还没修改,此时另一个线程t2获取到CPU时间片,将共享变量的值A修改为B,然后又修改为A,此时线程t1检查发现共享变量的值没有发生变化,但是实际上却变化了。
- 解决办法: 使用版本号,在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A-B-A 就会变成1A-2B-3A。从Java1.5开始JUC包里提供了一个类AtomicStampedReference来解决ABA问题。AtomicStampedReference类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前版本号是否等于预期版本号,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
(2)循环时间长开销会比较大:
自旋重试时间,会给CPU带来非常大的执行开销
(3)只能保证一个共享变量的原子操作,不能保证同时对多个变量的原子性操作
- 解决办法:
从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,
你可以把多个变量放在一个对象里来进行CAS操作
17.4 CAS使用注意事项
- (1)CAS需要和volatile配合使用
CAS只能保证变量的原子性,不能保证变量的内存可见性。CAS获取共享变量的值时,需要和volatile配合使用,来保证共享变量的可见性
- (2)CAS适用于并发量不高、多核CPU的情况
CPU多核情况下可以同时执行,如果不合适就失败。而并发量过高,会导致自旋重试耗费大量的CPU资源