1、开启线程的三种方式?
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 ft = new FutureTask(new MyCallable());
new Thread(ft).start();
2、run()
和start()
方法区别
run()
方法只是线程的主体方法,和普通方法一样,不会创建新的线程。只有调用start()
方法,才会启动一个新的线程,新线程才会调用run()
方法,线程才会开始执行。
3、如何控制某个方法允许并发访问线程的个数?
创建Semaphore
变量,Semaphore semaphore = new Semaphore(5, true);
当方法进入时,请求一个信号,如果信号被用完则等待,方法运行完,释放一个信号,释放的信号新的线程就可以使用。
4、在Java中wait和seelp方法的不同
wait()
方法属于Object类,调用该方法时,线程会放弃对象锁,只有该对象调用notify()
方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
sleep()
方法属于Thread类,sleep()
导致程序暂停执行指定的时间,让出CPU,但它的监控状态依然保存着,当指定时间到了又会回到运行状态,sleep()
方法中线程不会释放对象锁。
5、谈谈wait/notify关键字的理解
notify
: 唤醒在此对象监视器上等待的单个线程
notifyAll()
: 通知所有等待该竞争资源的线程
wait
: 释放obj的锁,导致当前的线程等待,直接其他线程调用此对象的notify()或notifyAll()方法
当要调用wait()
或notify()/notifyAll()
方法时,一定要对竞争资源进行加锁,一般放到synchronized(obj)
代码中。
当调用obj.notify/notifyAll
后,调用线程依旧持有obj锁,因此等待线程虽被唤醒,但仍无法获得obj锁,直到调用线程退出synchronized块,释放obj锁后,其他等待线程才有机会获得锁继续执行。
6、什么导致线程阻塞?
一般线程阻塞
1)线程执行了Thread.sleep(int millsecond)
方法,放弃CPU,睡眠一段时间,一段时间过后恢复执行;
2)线程执行一段同步代码,但无法获得相关的同步锁,只能进入阻塞状态,等到获取到同步锁,才能恢复执行;
3)线程执行了一个对象的wait()
方法,直接进入阻塞态,等待其他线程执行notify()/notifyAll()
操作;
4)线程执行某些IO操作,因为等待相关资源而进入了阻塞态,如System.in
,但没有收到键盘的输入,则进入阻塞态。
5)线程礼让,Thread.yield()
方法,暂停当前正在执行的线程对象,把执行机会让给相同或更高优先级的线程,但并不会使线程进入阻塞态,线程仍处于可执行态,随时可能再次分得CPU时间。
线程自闭,join()
方法,在当前线程调用另一个线程的join()
方法,则当前线程进入阻塞态,直到另一个线程运行结束,当前线程再由阻塞转为就绪态。
6)线程执行suspend()
使线程进入阻塞态,必须resume()
方法被调用,才能使线程重新进入可执行状态
7、线程如何关闭?
1 ) 使用标志位
2)使用stop()
方法,但该方法就像关掉电脑电源一样,可能会发生预料不到的问题
3)使用中断interrupt()
public class Thread {
// 中断当前线程
public void interrupt();
// 判断当前线程是否被中断
public boolen isInterrupt();
// 清除当前线程的中断状态,并返回之前的值
public static boolen interrupted();
}
但调用interrupt()
方法只是传递中断请求消息,并不代表要立马停止目标线程。
8、讲一下java中的同步的方法
之所以需要同步,因为在多线程并发控制,当多个线程同时操作一个可共享的资源时,如果没有采取同步机制,将会导致数据不准确,因此需要加入同步锁,确保在该线程没有完成操作前被其他线程调用,从而保证该变量的唯一一性和准确性。
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>
9、如何保证线程安全?
线程安全性体现在三方法:
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保证有序性。
10、两个进程同时要求写或者读,能不能实现?如何防止进程的同步?
我认为可以实现,比如两个进程都读取日历进程数据是没有问题,但同时写,应该会有冲突。
可以使用共享内存实现进程间数据共享。
11、线程间操作List
12、Java中对象的生命周期
如何做好面试突击,规划学习方向?
面试题集可以帮助你查漏补缺,有方向有针对性的学习,为之后进大厂做准备。但是如果你仅仅是看一遍,而不去学习和深究。那么这份面试题对你的帮助会很有限。最终还是要靠资深技术水平说话。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。建议先制定学习计划,根据学习计划把知识点关联起来,形成一个系统化的知识体系。
学习方向很容易规划,但是如果只通过碎片化的学习,对自己的提升是很慢的。
我搜集整理过这几年字节跳动,以及腾讯,阿里,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。
在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。