不想当将军的士兵不是好士兵,不想成为架构师的Java程序员不是好程序员!为什么要成为架构师,为什么要进大厂?
那年十八 母校舞会
站着如喽啰
那时候 我含泪
发誓各位 必须看到我
建议使用”异常法”来终止线程的继续运行。在想要被中断执行的线程中, 调用 interrupted()方法,该方法用来检验当前线程是否已经被中断,即该线程 是否被打上了中断的标记,并不会使得线程立即停止运行,如果返回 true,则 抛出异常,停止线程的运行。在线程外,调用 interrupt()方法,使得该线程打 上中断的标记。
①. 继承 Thread 类创建线程类
②. 通过 Runnable 接口创建线程类
③. 通过 Callable 和 Future 创建线程
④. 通过线程池创建线程
利用线程池不用 new 就可以创建线程,线程可复用,利用 Executors 创 建线程池。
扩展1:Java 中 Runnable 和 Callable 有什么不同?
扩展2:一个类是否可以同时继承 Thread 和实现 Runnable接口?<
可以。比如下面的程序可以通过编译。因为 Test 类从 Thread 类中继承了 run()方法,这个 run()方法可以被当作对 Runnable 接口的实现。
public class Test extends Thread implements Runnable {
public static void main(String[] args) {
Thread t = new Thread(new Test());
t.start();
}
}
在多线程的环境中,经常会遇到数据的共享问题,即当多个线程需要访问同 一资源时,他们需要以某种顺序来确保该资源在某一时刻只能被一个线程使用, 否则,程序的运行结果将会是不可预料的,在这种情况下,就必须对数据进行 同步。
在 Java 中,提供了四种方式来实现同步互斥访问: synchronized 和 Lock 和 wait()/notify()/notifyAll()方法和 CAS。
①. synchronized 的用法。
A . 同步代码块
synchronized 块写法:
synchronized(object)
{
}
表示线程在执行的时候会将 object 对象上锁。(注意这个对象可以是任意 类的对象,也可以使用 this 关键字或者是 class 对象)。
可能一个方法中只有几行代码会涉及到线程同步问题,所以 synchronized 块 比 synchronized 方法更加细粒度地控制了多个线程的访问, 只有 synchronized 块中的内容不能同时被多个线程所访问,方法中的其他语句仍然 可以同时被多个线程所访问(包括 synchronized 块之前的和之后的)。
B . 修饰非静态的方法
当 synchronized 关键字修饰一个方法的时候,该方法叫做同步方法。
Java 中的每个对象都有一个锁(lock),或者叫做监视器(monitor), 当一个线程访问某个对象的 synchronized 方法时,将该对象上锁,其他任何 线程都无法再去访问该对象的 synchronized 方法了(这里是指所有的同步方 法,而不仅仅是同一个方法),直到之前的那个线程执行方法完毕后(或者是 抛出了异常),才将该对象的锁释放掉,其他线程才有可能再去访问该对象的 synchronized 方法。
注意这时候是给对象上锁,如果是不同的对象,则各个对象之间没有限制 关系。
注意,如果一个对象有多个 synchronized 方法,某一时刻某个线程已经进入 到了某个 synchronized 方法,那么在该方法没有执行完毕前,其他线程是无法访 问该对象的任何 synchronized 方法的。
C . 修饰静态的方法
当一个 synchronized 关键字修饰的方法同时又被 static 修饰,之前说过, 非静态的同步方法会将对象上锁,但是静态方法不属于对象,而是属于类,它 会将这个方法所在的类的 Class 对象上锁。一个类不管生成多少个对象,它们 所对应的是同一个 Class 对象。
因此,当线程分别访问同一个类的两个对象的两个 static,synchronized 方法时,它们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行 完毕后另一个线程才开始。
结论:
②.Lock 的用法。
使用 Lock 必须在 try-catch-finally 块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被释放,防止死锁的发生。通常使用 Lock 来 进行同步的话,是以下面这种形式去使用的:
Lock lock = ...;
lock.lock();
try{
//处理任务
}
catch(Exception ex){
}
finally{
lock.unlock();
//释放锁
}
Lock 和 synchronized 的区别和 Lock 的优势。你需要实现 一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此 来保持它的完整性,你会怎样去实现它?
扩展1: volatile 和 synchronized 区别。
扩展 2:什么场景下可以使用 volatile 替换 synchronized?
只需要保证共享资源的可见性的时候可以使用 volatile 替代, synchronized 保证可操作的原子性,一致性和可见性。
③.wait()\notify()\notifyAll()的用法(Java 中怎样唤醒一个阻塞的线程?)。
在 Java 发展史上曾经使用 suspend()、resume()方法对于线程进行阻塞唤醒,但随之出 现很多问题,比较典型的还是死锁问题。
解决方案可以使用以对象为目标的阻塞,即利用 Object 类的 wait()和 notify()方法实现 线程阻塞。
首先,wait、notify 方法是针对对象的,调用任意对象的 wait()方法都将导致线程阻塞, 阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对 象阻塞的线程,但它需要重新获取改对象的锁,直到获取成功才能往下执行;其次,wait、 notify 方法必须在 synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调 用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获取 某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。
扩展 1: 为什么 wait(),notify(),notifyAll()等方法都定义在 Object 类中?
因为这三个方法都需要定义在同步代码块或同步方法中,这些方法的调用是依赖锁对 象的,而同步代码块或同步方法中的锁对象可以是任意对象,那么能被任意对象调用的方 法一定定义在 Object 类中。
扩展 2: notify()和 notifyAll()有什么区别?
notify()和 notifyAll()都是 Object 对象用于通知处在等待该对象的线程的方法。
void notify(): 唤醒一个正在等待该对象的线程,进入就绪队列等待 CPU 的调度。
void notifyAll(): 唤醒所有正在等待该对象的线程,进入就绪队列等待 CPU 的调度。
两者的最大区别在于:
notifyAll 使所有原来在该对象上等待被 notify 的线程统统退出 wait 的状态,变成等待该对 象上的锁,一旦该对象被解锁,他们就会去竞争。 notify 他只是选择一个 wait 状态线程进行通知,并使它获得该对象上的锁,但不惊动其他 同样在等待被该对象 notify 的线程们,当第一个线程运行完毕以后释放对象上的锁,此时 如果该对象没有再次使用 notify 语句,即便该对象已经空闲,其他 wait 状态等待的线程由 于没有得到该对象的通知,继续处在 wait 状态,直到这个对象发出一个 notify 或 notifyAll, 它们等待的是被 notify 或 notifyAll,而不是锁。
④.CAS
它是一种非阻塞的同步方式。具体参见上面的部分。
扩展一:同步锁的分类?
扩展二:锁的分类?
扩展三:java 中的悲观锁和乐观锁?
当线程间是可以共享资源时,线程间通信是协调它们的重要的手段。
1. Object 类中 wait()\notify()\notifyAll()方法。
2. 用 Condition 接口。
3. 管道实现线程间的通信。
4. 使用 volatile 关键字
见上面部分。
如果多个线程同时运行某段代码,如果每次运行结果和单线程运行的结果 是一样的,而且其他变量的值也和预期的是一样的,就是线程安全的。
Synchronized,Lock,原子类(如 atomicinteger 等),同步容器、并 发容器、 阻塞队列 、 同步辅助类(比 如 CountDownLatch, Semaphore, CyclicBarrier)。
不一定,要看具体的任务以及计算机的配置。比如说:
ReentrantLock 和 synchronized 都是可重入锁。
如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法 中调用另外一个同步方法也同样持有该锁。
比如:
public sychrnozied void test() {
xxxxxx;
test2();
}
public sychronized void test2() {
yyyyy;
}
在上面代码段中,执行 test 方法需要获得当前对象作为监视器的对象锁, 但方法中又调用了 test2 的同步方法。
如果锁是具有可重入性的话,那么该线程在调用 test2 时并不需要再次获 得当前对象的锁,可以直接进入 test2 方法进行操作。
可重入锁最大的作用是避免死锁。如果锁是不具有可重入性的话,那么该 线程在调用 test2 前会等待当前对象锁的释放,实际上该对象锁已被当前线程 所持有,不可能再次获得,那么线程在调用同步方法、含有锁的方法时就会产 生死锁。
顾名思义,就是可以响应中断的锁。
在 Java 中,synchronized 不是可中断锁,而 Lock 是可中断锁。 lockInterruptibly()的用法已经体现了 Lock 的可中断性。如果某一线程 A 正 在执行锁中的代码,另一线程 B 正在等待获取该锁,可能由于等待时间过长, 线程 B 不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线 程中断它,这种就是可中断锁。
在 Java 中,synchronized 就是非公平锁,它无法保证等待的线程获取锁 的顺序。而对于 ReentrantLock 和 ReentrantReadWriteLock,它默认情况 下是非公平锁,但是可以设置为公平锁。
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个 锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁, 这种就是公平锁。
正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。 ReadWriteLock 就是读写锁,它是一个接口,ReentrantReadWriteLock 实 现了这个接口。可以通过 readLock()获取读锁,通过 writeLock()获取写锁。
如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把加锁同 步的范围扩展到整个操作序列的外部。
HotSpot 虚拟机的对象的内存布局:对象头(Object Header)分为两部分信息吗,第 一部分(Mark Word)用于存储对象自身的运行时数据,另一个部分用于存储指向方法区 对象数据类型的指针,如果是数组的话,还会由一个额外的部分用于存储数组的长度。
32 位 HotSpot 虚拟机中对象未被锁定的状态下, Mark Word 的 32 个 Bits 空间中 25 位 用于存储对象哈希码,4 位存储对象分代年龄,2 位存储锁标志位,1 位固定为 0。
HotSpot 虚拟机对象头 Mark Word
存储内容 | 标志位 | 状态 |
---|---|---|
对象哈希码、对象分代年龄 | 01 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 |
指向重量级锁的指针 | 10 | 膨胀(重量级锁) |
空,不记录信息 | 11 | GC 标记 |
偏向线程 ID,偏向时间戳、对象分代年龄 | 01 | 可偏向 |
synchronized(x){
x.notify()
//或者 wait()
}
特别注意: sleep 和 wait 必须捕获异常(Thread.sleep()和 Object.wait() 都会抛出 InterruptedException), notify 和 notifyAll 不需要捕获异常。
二个方法都是判断线程是否停止的方法。
对于单核 CPU,CPU 在一个时刻只能运行一个线程,当在运行一个线程的 过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。
线程上下文切换过程中会记录程序计数器、CPU 寄存器的状态等数据。
虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同 样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在 进行多线程编程时要注意这些因素。
在 java.lang.Thread 中有一个方法叫 holdsLock(Object obj),它返回 true,如果当且仅当当前线程拥有某个具体对象的锁。
当我们在 Java 程序中创建一个线程,它就被称为用户线程。将一个用户线 程设置为守护线程的方法就是在调用start()方法之前, 调用对象的 setDamon(true)方法。一个守护线程是在后台执行并且不会阻止 JVM 终止的 线程,守护线程的作用是为其他线程的运行提供便利服务。当没有用户线程在 运行的时候, JVM 关闭程序并且退出。一个守护线程创建的子线程依然是守护 线程。
守护线程的一个典型例子就是垃圾回收器。
线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的 实现。
在 Java 当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable)、运 行(running)、阻塞(blocked)、timed_waiting、waiting、消亡(dead)。
join()方法。
t1.start();
t2.start();
t3.start();
t1.join(); //不会导致 t1 和 t2 和 t3 的顺序执行
t2.join();
t3.join();
System.out.println("Main finished");
public class WithLatch {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new ChildThead(i, latch).start();
}
try {
latch.await();
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main finished");
}
static class ChildThead extends Thread {
private int id = -1;
private CountDownLatch latch = null;
public ChildThead(int id, CountDownLatch latch) {
this.id = id;
this.latch = latch;
}
public void run() {
try {
Thread.sleep(Math.abs(new Random().nextint(5000)));
System.out.println(String.format("Child Thread %dfinished", id));
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
latch.countDown();
}
}
}
}
public class WithExecutor {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(3);
List> list = new ArrayList>();
for (int i = 0; i < 3; i++) {
list.add(new ChildThead(i));
}
try {
pool.invokeAll(list);
}
finally {
pool.shutdown();
}
System.out.println("Main finished");
}
static class ChildThead implements Callable {
private int id = -1;
public ChildThead(int id) {
this.id = id;
}
public Void call() throws Exception {
try {
Thread.sleep(Math.abs(new Random().nextint(5000)));
System.out.println(String.format("Child Thread %dfinished", id));
}
catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
}
作为一名Java程序员,想进BAT只学多线程还远远不够!
想进BAT,像Kafka、Mysql、Tomcat、Docker、Spring、MyBatis、Nginx、Netty、Dubbo、Redis、Netty、Spring cloud、分布式、高并发、性能调优、微服务等架构技术你都要学习!
当然以上技术能够掌握百分之八九十的话,进阿里P7还是没什么大问题的!
需要的朋友,关注下方公众号领取!
以下是部分面试题截图