今天为你们带来了关于17道线程、多线程和线程池面试知识,对于这些东西我还专门整理了一份983页的完整PDF核心笔记,须要的小伙伴能够联系我获取~我不休息我还能学 ⊂(‘ω’⊂ )))Σ≡=─༄༅༄༅༄༅༄༅༄༅
核心学习笔记的获取方式:VX:mm14525201314java
1)继承Thread类,重写run()
方法,在run()
方法体中编写要完成的任务new Thread().start();
2)实现Runnable接口,实现run()
方法new Thread(new MyRunnable()).start();
3)实现Callable接口MyCallable类,实现call()
方法,使用FutureTask类来包装Callable对象,使用FutureTask
对象做为Thread对象的target建立并启动线程;调用FutureTask对象的get()
方法来得到子线程执行结束后的返回值。
FutureTask<Integer> ft = new FutureTask<Integer>(new MyCallable()); new Thread(ft).start();
run()
和start()
方法区别run()
方法只是线程的主体方法,和普通方法同样,不会建立新的线程。只有调用start()
方法,才会启动一个新的线程,新线程才会调用run()
方法,线程才会开始执行。git
建立Semaphore
变量,Semaphore semaphore = new Semaphore(5, true);
当方法进入时,请求一个信号,若是信号被用完则等待,方法运行完,释放一个信号,释放的信号新的线程就可使用。github
wait()
方法属于Object类,调用该方法时,线程会放弃对象锁,只有该对象调用notify()
方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。面试
sleep()
方法属于Thread类,sleep()
致使程序暂停执行指定的时间,让出CPU,但它的监控状态依然保存着,当指定时间到了又会回到运行状态,sleep()
方法中线程不会释放对象锁。编程
notify
: 唤醒在此对象监视器上等待的单个线程缓存
notifyAll()
: 通知全部等待该竞争资源的线程安全
wait
: 释放obj的锁,致使当前的线程等待,直接其余线程调用此对象的notify()或notifyAll()方法多线程
当要调用wait()
或notify()/notifyAll()
方法时,必定要对竞争资源进行加锁,通常放到synchronized(obj)
代码中。并发
当调用obj.notify/notifyAll
后,调用线程依旧持有obj锁,所以等待线程虽被唤醒,但仍没法得到obj锁,直到调用线程退出synchronized块,释放obj锁后,其余等待线程才有机会得到锁继续执行。函数
通常线程阻塞
1)线程执行了Thread.sleep(int millsecond)
方法,放弃CPU,睡眠一段时间,一段时间事后恢复执行;
2)线程执行一段同步代码,但没法得到相关的同步锁,只能进入阻塞状态,等到获取到同步锁,才能恢复执行;
3)线程执行了一个对象的wait()
方法,直接进入阻塞态,等待其余线程执行notify()/notifyAll()
操做;
4)线程执行某些IO操做,由于等待相关资源而进入了阻塞态,如System.in
,但没有收到键盘的输入,则进入阻塞态。
5)线程礼让,Thread.yield()
方法,暂停当前正在执行的线程对象,把执行机会让给相同或更高优先级的线程,但并不会使线程进入阻塞态,线程仍处于可执行态,随时可能再次分得CPU时间。
线程自闭,join()
方法,在当前线程调用另外一个线程的join()
方法,则当前线程进入阻塞态,直到另外一个线程运行结束,当前线程再由阻塞转为就绪态。
6)线程执行suspend()
使线程进入阻塞态,必须resume()
方法被调用,才能使线程从新进入可执行状态
1 ) 使用标志位
2)使用stop()
方法,但该方法就像关掉电脑电源同样,可能会发生预料不到的问题
3)使用中断interrupt()
public class Thread { // 中断当前线程 public void interrupt(); // 判断当前线程是否被中断 public boolen isInterrupt(); // 清除当前线程的中断状态,并返回以前的值 public static boolen interrupted(); }
但调用interrupt()
方法只是传递中断请求消息,并不表明要立马中止目标线程。
之因此须要同步,由于在多线程并发控制,当多个线程同时操做一个可共享的资源时,若是没有采起同步机制,将会致使数据不许确,所以须要加入同步锁,确保在该线程没有完成操做前被其余线程调用,从而保证该变量的惟一一性和准确性。
1) synchronized修饰同步代码块或方法
因为java的每一个对象都有一个内置锁,用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需得到内置锁,不然就处于阴塞状态。
2) volatile修饰变量
保证变量在线程间的可见性,每次线程要访问volatile修饰的变量时都从内存中读取,而不缓存中,这样每一个线程访问到的变量都是同样的。且使用内存屏障。
3) ReentrantLock重入锁,它经常使用的方法有ReentrantLock()
:建立一个ReentrantLock实例lock()
得到锁 unlock()
释放锁
4) 使用局部变量ThreadLocal实现线程同步,每一个线程都会保存一份该变量的副本,副本之间相互独立,这样每一个线程均可以随意修改本身的副本,而不影响其余线程。
经常使用方法ThreadLocal()
建立一个线程本地变量;get()
返回此线程局部的当前线程副本变量;initialValue()
返回此线程局部变量的当前线程的初始值;set(Tvalue)
将此线程变量的当前线程副本中的值设置为value
5) 使用原子变量,如AtomicInteger
,经常使用方法AtomicInteger(int value)
建立个有给定初始值的AtomicInteger
整数;addAndGet(int data)
以原子方式将给定值与当前值相加
6) 使用阻塞队列实现线程同步LinkedBlockingQueue<E>
线程安全性体如今三方法:
1)原子性: 提供互斥访问,同一时刻只能有一个线和至数据进行操做。
JDK中提供了不少atomic类,如AtomicInteger\AtomicBoolean\AtomicLong
,它们是经过CAS完成原子性。
JDK提供锁分为两种: synchronized依赖JVM实现锁,该关键字做用对象的做用范围内同一时刻只能有一个线程进行操做。另外一种是LOCK,是JDK提供的
代码层面的锁,依赖CPU指令,表明性是ReentrantLock。
2)可见性 :一个线程对主内存的修改及时被其余线程看到。
JVM提供了synchronized和volatile,volatile的可见性是经过内存屏障和禁止重排序实现的,volatile会在写操做时,在写操做后加一条store屏障指令,将本地内存中的共享变量值刷新到主内存;会在读操做时,在读操做前加一条load指令,从内存中读取共享变量。
3)有序性:指令没有被编译器重排序。
可经过volatile、synchronized、Lock保证有序性。
我认为能够实现,好比两个进程都读取日历进程数据是没有问题,但同时写,应该会有冲突。
可使用共享内存实现进程间数据共享。
1)建立阶段(Created): 为对象分配存储空间,开始构造对象,从超类到子类对static成员初始化;超类成员变量按顺序初始化,递归调用超类的构造方法,子类成员变量按顺序初始化,子类构造方法调用。
2)应用阶段(In Use): 对象至少被一个强引用持有着。
3)不可见阶段(Invisible): 程序运行已超出对象做用域
4)不可达阶段(Unreachable): 该对象再也不被强引用所持有
5)收集阶段(Collected): 假设该对象重写了finalize()方法且未执行过,会去执行该方法。
6)终结阶段(Finalized): 对象运行完finalize()方法仍处于不可达状态,等待垃圾回收器对该对象空间进行回收。
7)对象空间从新分配阶段(De-allocated): 垃圾回收器对该对象所占用的内存空间进行回收或再分配,该对象完全消失。
static synchronized
控制的是类的全部实例访问,无论new了多少对象,只有一份,因此对该类的全部对象都加了锁。限制多线程中该类的全部实例同时访问JVM中该类对应的代码。
若是synchronized修饰的是静态方法,锁的是当前类的class对象,进入同步代码前要得到当前类对象的锁;
普通方法,锁的是当前实例对象,进入同步代码前要得到的是当前实例的锁;
同步代码块,锁的是括号里面的对象,对给定的对象加锁,进入同步代码块库前要得到给定对象锁;
若是两个线程访问同一个对象的synchronized方法,会出现竞争,若是是不一样对象,则不会相互影响。
有volatile变量修饰的共享变量进行写操做的时候会多一条汇编代码,lock addl $0x0,lock前缀的指令在多核处理器下会将当前处理器缓存行的数据会写回到系统内存,这个写回内存的操做会引发在其余CPU里缓存了该内存地址的数据无效。同时lock前缀也至关于一个内存屏障,对内存操做顺序进行了限制。
synchronized经过对象的对象头(markword)来实现锁机制,java每一个对象都有对象头,均可觉得synchronized实现提供基础,均可以做为锁对象,在字节码层面synchronized块是经过插入monitorenter monitorexit
完成同步的。持有monitor对象,经过进入、退出这个Monitor对象来实现锁机制。
NIO( New Input/ Output) 引入了一种基于通道和缓冲区的 I/O 方式,它可使用 Native 函数库直接分配堆外内存,而后经过一个存储在 Java 堆的 DirectByteBuffer 对象做为这块内存的引用进行操做,避免了在 Java 堆和 Native 堆中来回复制数据。
NIO 是一种同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,能够同时作其余任务。
同步的核心就是 Selector,Selector 代替了线程自己轮询 IO 事件,避免了阻塞同时减小了没必要要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,能够经过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。
1)volatile: 解决变量在多个线程间的可见性,但不能保证原子性,只能用于修饰变量,不会发生阻塞。volatile能屏蔽编译指令重排,不会把其后面的指令排到内存屏障以前的位置,也不会把前面的指令排到内存屏障的后面。多用于并行计算的单例模式。volatile规定CPU每次都必须从内存读取数据,不能从CPU缓存中读取,保证了多线程在多CPU计算中永远拿到的都是最新的值。
2)synchronized: 互斥锁,操做互斥,并发线程过来,串行得到锁,串行执行代码。解决的是多个线程间访问共享资源的同步性,可保证原子性,也可间接保证可见性,由于它会将私有内存和公有内存中的数据作同步。可用来修饰方法、代码块。会出现阻塞。synchronized发生异常时,会自动释放线程占有的锁,所以不会致使死锁现象发生。非公平锁,每次都是相互争抢资源。
3) lock是一个接口,而synchronized是java中的关键字,synchronized是内置语言的实现。lock可让等待锁的线程响应中断。在发生异常时,若是没有主动经过unLock()
去释放锁,则可能形成死锁现象,所以使用Lock时须要在finally块中释放锁。
4) ReentrantLock可重入锁,锁的分配机制是基于线程的分配,而不是基于方法调用的分配。ReentrantLock
有tryLock
方法,若是锁被其余线程持有,返回false,可避免造成死锁。对代码加锁的颗粒会更小,更节省资源,提升代码性能。ReentrantLock
可实现公平锁和非公平锁,公平锁就是先来的先获取资源。ReentrantReadWriteLock
用于读多写少的场合,且读不须要互斥场景。
5)并发编程有关知识点(这个是通常Android开发用的少的,因此建议多去看看):
平时Android开发中对并发编程能够作得比较少,Thread这个类常常会用到,可是咱们想提高本身的话,必定不能停留在表面,,咱们也应该去了解一下java的关于线程相关的源码级别的东西。
更多面试内容,面试专题,flutter视频 全套,音视频从0到高手开发。
关注GitHub:https://github.com/xiangjiana/Android-MS
免费获取面试PDF合集