个人整理笔记,如有欠缺之处还请多多指教
1.Java如何开启线程?
首先介绍什么是线程,什么是进程。进程是操作系统进行资源分配的最小单元;而线程是进行任务分配的最小单元。进程中的资源不能共享,但是一个进程中的多个线程可以实现资源共享。换句话说,一个进程可以有多个线程,而一个线程只能属于一个进程。当我们打开任务管理器的时候会显示我们正在运行的进程,像这样,而进程中每个任务就把他叫做线程。
然后介绍开启线程的几种办法
①.继承Thread类 重写run()方法,调用start()方法;(jvm去调用run()方法)
②.实现Runnable接口,实现run()方法;
③.实现Callable接口,实现call()方法;
④.使用线程池。
其中呢①和②相比一个是继承类,一个是实现方法,这里就会提到Java中的单继承和多实现,一个类只能去继承一个类,但是可以实现多个接口,假如说我们现在要去创建一个线程,但是这个类已经继承了另一个类,我们就只能去选择②这种方法(1和2相比)
②和③相比呢,直观来看就是当我们想知道某一个线程执行返回的结果我们就选择③,也就是说③有返回值,而②没有返回值。
对于线程池而言,这里简单的介绍一下:
- 定长线程池 Executors.newFixedThreadPool,可以控制线程的并发数,如果执行的线程按到最大阈值,超出的线程则会在队列中等待
- 可缓存线程池 Executors.newCachedThreadPool,回收空闲线程,若没有空闲线程回收,则创建新线程。
- Executors.newScheduledThreadPool,创建一个定时长线程池,也就是周期性执行。
- Executors.newSingleThreadEceecutor,创建单线程线程池,也就是只有一个线程去执行任务,所有的任务都需要一个一个的来。
2.怎么保证线程安全?
常用的就是锁机制,其中包括有synchronized 和 lock (这里不细说二者的区别),这里的话需要提到的就是volatile关键字,volatile不保证线程安全,它只保证可见性,同时在单例模式的Double Check 问题中可以防止指令的重排序(指令重排序是指:加入我有三个线程1,2,3.我正常执行顺序是1->2->3,但是线程二执行的速度会比较慢,等到线程3执行结束,线程2才执行结束,实际我返回的执行结果是1->3->2,像这样为了防止我的指令重排序问题)。
3.Java中锁机制是什么样的?
首先我们要知道什么是锁,Java中的锁就是在对象的Markword中记录一个所得状态
其中锁的状态又可以分为:无锁、偏向锁、轻量级锁、重量级锁,根据资源的竞争程度不断的进行锁升级。当只有一个线程的时候就是无锁状态,不需要考虑线程安全问题;当再进来一个线程之后(两个线程去访问资源)将会升级为偏向锁,jvm决定偏向于哪个线程;再有其他资源来访问的时候,偏向锁升级为轻量级锁(自旋锁),当资格线程去执行的时候,其他线程会进行自旋(举个不恰当的例子,当你去厕所的时候,发现厕所已经满了,但是你现在还有工作要做,那么你就会隔一段时间来看一眼厕所是否有空位置,有空位置你就去执行“lashi”,不是很恰当,帮助理解);那么当很多线程都来竞争这个资源的时候,就会把轻量级锁升级为重量级锁。当有线程去执行的时候,其他线程都在自旋,而且自旋是需要一定的资源的,当自旋的线程非常多的时候,jvm说,哎呀,我管不了了,他会上报给老大操作系统,让操作系统介入处理。老大来了就有老大的威风,这时候老大跟下面所有的自旋的线程说,你们都给我排好队,我让你们谁进去谁就进去。很显然这样的话,重量级锁的每一步都需要操作系统去下达命令,效率就会比较慢。
按不同的作用锁的分类也会不同,这里简单的提一下可以分为:
-
乐观锁:我就认为不会发生线程不安全的情况
-
悲观锁:我很悲观,我就认为只要去执行就会发生线程安全问题,所以不管三七二十一我都给你加锁
-
公平锁:按顺序,先来后到,谁先来谁先执行
-
非公平锁:我可不管谁先来的,大家一起竞争,竞争到了我就去执行
-
独享锁:这个锁我只能让一个线程占有(比较专一)
-
共享锁:可以被多个线程使用(中央空调)
4.什么是CAS?
CAS 即 (compare and swap)比较交换的意思,多用于在乐观锁中,其中有三个重要的参数(V,E,N),V代表所在位置,E代表预期值,N代表想要改变成的值,当我们的V和E相等时,说明此时此刻没有其他线程去改变这个值,我们就可以吧V的值修改成N,当V的值和E的值不相等的时候,说明V的值已经被其他线程修改过了,此时我们就不能把N的值赋给V,只能选择放弃操作或者重新读取V的值再次去和E去比较。
5.什么是AQS?
AQS全称AbstractQueuedSynchronizer,叫抽象队列同步器。AQS的核心由一个阻塞队列和一个volatile修饰的state变量组成。AQS可以通过CAS对state变量进行修改,一般来说,state为0时表示无锁状态,state大于0时表示有线程获得锁。
6.怎么去判断一个线程是否上锁?
在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。
7.如何保证三个线程同时执行?如何保证三个线程依次执行?如何保证三个线程交错执行?
在并发情况下,线程同步的辅助工具类有CountDownLatch、CyclicBarrier、Semaphore。
CountDownLatch:叫倒计时锁,有计数器原理,其构造函数中参数为int类型整数,其中最为重要的两个方法是await()和countdown()方法,await()方法会判断计数器的数值是否为0,如果是0就执行,如果不是0就挂起;countdown()方法会将计数器的数值减一;
CyclicBarrier:直面翻译为循环栅栏,举个例子:我们去玩剧本杀,老板就说了你们必须凑够6个人才能玩这个本,但是你们就只有三个人,这时候你们就需要在等三个人,满6人才能玩(当然只是为了理解,你说你有钱我给你钱我就要玩不是不可以,就缺少了乐趣,缺人叫我),等待线程数满了就去一起执行;
Semaphore:假设我们有10个信号量的资源,这时候有一个线程进来说我需要占用7个信号量,那么好,给你7个,他就去工作去了,又有一个线程进来说我需要我5个信号量,这时候Semaphore说我被别人借走了,现在只有3个了,你先等等呢,这时候线程二就无法去工作,这时候又来一个线程,他说我需要一个信号量,Semaphore说我够给你,就这样去执行,那么线程二什么时候执行呢?当我线程一执行完会吧他用完的7个信号量还回来,当线程二再次拿到资源并且型号量剩余量大于它自身所需要的信号量个数时就会去执行。
8.什么是ThreaLocal?ThredLocal的原理是什么?
ThreadLocal即线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这么一个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免线程安全问题
ThreadLocal类有一个类型为ThreadLocal.ThreadLocalMap的实例变量,每个线程都有一个属于自己的ThreadLocalMap,ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值,每个线程往ThreadLocal里面设置值的时候,都是往自己的ThreadLocalMap里面存,读也是找对应的key。
ThreadLocal的key是弱引用,在调用gc垃圾回收的时候很可能出现一种情况就是key被回收了,但是value还在。出现内存泄漏,处理办法就是使用完ThreadLocal的时候调用remove()方法,释放内存。
9.线程有几种状态?
- 新建:New一个新的线程
- 就绪:已创建线程且调用该线程的start()方法,等待CPU的调用
- 阻塞:线程因特殊原因,放弃CPU的使用权
- 运行:就绪的线程得到CPU使用权
- 死亡:线程执行完了或者因异常退出了run()方法,该线程结束生命周期
10.sleep方法和wait方法有什么区别?yield()有什么作用?
- 线程调用sleep方法,该线程会进入休眠状态,并且不释放锁,让其他线程抢占CPU执行,因为sleep方法不会释放锁,所以该线程对象也无法被其他线程使用,等睡眠时间过去,该线程状态处于就绪状态,同其他线程一起抢占CPU资源,拿到CPU资源继续执行。
- 线程调用wait方法时,该线程会释放锁,等待线程调用notify或者notifyAll方法,调用后线程是处于就绪还是运行状态的话,是由操作系统决定的。
- 线程调用yield(),会使线程放弃当前的CPU资源,从运行状态变为就绪状态