1.现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行?
可以用 Thread 类的 join 方法实现这一效果。
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
Join方法实现是通过wait实现的
jion的使用方式,以下是没有使用jion的情况
package com.springboot.demo.mybatis;
public class TestThread implements Runnable {
public static int a = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++){
a = a + 1;
}
}
public static void main(String[] args){
Runnable r = new TestThread();
Thread testThread = new Thread(r);
testThread.start();
System.out.println(a);
}
}
上面程序中其实有两个线程,一个是main 的主线程在运行,一个是调用的TestThread线程在运行。当testThread.start() 的时候,它回去执行testThread这个线程中的代码,但是同时main线程也在运行,main方法中的代码会继续向下执行,System.out.println(a)可能会在testThread线程还没执行完的时候就已经执行了。所以这里的结果很难得到 5。
下面使用jion()就可以让线程按照先后顺序执行。
package com.springboot.demo.mybatis;
public class TestThread implements Runnable {
public static int a = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++){
a = a + 1;
}
}
public static void main(String[] args){
Runnable r = new TestThread();
Thread testThread = new Thread(r);
testThread.start();
testThread.jion(); // 调用jion
System.out.println(a);
}
}
在调用了jion后,main线程运行到这一行时,就会停下来,让testThread线程执行完。
2. Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。
多线程和并发编程中使用 lock 接口的最大优势是它为读和写提供两个单独的锁,可以让你构建高性能数据结构,比如 ConcurrentHashMap 和条件阻塞。
3. 关于多线程中的锁
**锁的概念:**多线程在运行的时候可能会遇到这样的问题,多个线程要用到同一个资源(比如要修改同一个数据),那么可能会出现错乱,比如线程要改动资源里的数据,那么多个线程同时改就乱了套了。所以锁出现了,如果有多个线程要对某项数据进行修改,那么就在当有一个线程在运行的时候,就给程序或者变量上把锁,不让其他线程再操作它了。
4.锁的类型
**可重入锁:**广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。即在执行对象中所有同步方法不用再次获得锁。ReentrantLock和synchronized都是可重入锁。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
**可中断锁:**在等待获取锁过程中可中断。synchronized就不是可中断锁,而Lock是可中断锁。
**公平锁:**按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利。非公平锁即无法保证锁的获取是按照请求锁的顺序进行的,这样就可能导致某个或者一些线程永远获取不到锁。synchronized是非公平锁,它无法保证等待的线程获取锁的顺序。对于ReentrantLock和ReentrantReadWriteLock,默认情况下是非公平锁,但是可以设置为公平锁。
**读写锁:**对资源读取和写入的时候拆分为2部分处理,一个读锁和一个写锁。读的时候可以多线程一起读,写的时候必须同步地写。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。
5.乐观锁和悲观锁
乐观锁:很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会去判断在此期间有没有人去更新这个数据(可以使用版本号等机制)。如果因为冲突失败就重试。乐观锁适用于写比较少的情况下,即冲突比较少发生,这样可以省去了锁的开销,加大了系统的整个吞吐量
**悲观锁:**总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,因此每次拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁,效率比较低
6.实现一个死锁
什么是死锁:两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。
产生死锁的四个必要条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。