多线程面试题
多线程的创建方式
(1)继承Thread类:但Thread实质上是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。
public class ThreadTest01 extends Thread {
@Override
public void run() {
System.out.println("threadTest.run()");
}
public static void main(String[] args) {
ThreadTest01 threadTest01 = new ThreadTest01();
threadTest01.start();
}
}
(2)实现Runnable接口的方式实现多线程,并且实例化Thread,传入自己的Thread实例,调用run()方法。
public class ThreadTest02 implements Runnable {
@Override
public void run() {
System.out.println("ThreadTest02.run()");
}
public static void main(String[] args) {
ThreadTest02 threadTest02 = new ThreadTest02();
Thread thread = new Thread(threadTest02);
thread.start();
}
}
在java中wait和sleep方法的不同的
最大的不同就是在等待时wait会释放锁,而sleep一直有锁。wait通常用于线程间交互,sleep通常被用于暂停执行。
synchronized和volatile关键字的作用
一旦一个共享变量(类的成员变量,类的静态成员变量)被volatile修饰之后,就具备了两层语义:
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说就是立即可见的。
- 禁止进行指令重排序
volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问改变量,其他线程被阻塞住。
(1)volatile仅能使用在变量级别;synchronized则可以使用在变量,方法和类级别的。
(2)volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性。
(3)volatile不会造成线程阻塞;synchronized可能会造成线程阻塞
(4)volatile标记的变量不会被编译期优化;synchronized标记的变量可以被编译器优化
什么是线程池,如何使用
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new线程而是直接去线程池去取即可,节省了开辟线程的时间,提高了代码的执行效率。
在jdk的java.util.concurrent.Executors中提供了生成多种线程池的静态方法,调用它们的execute方法即可
.ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
常用的线程池有哪些
newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
newCacheThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池的大小完全依赖操作系统能够创建的最大线程大小。
newScheeduledThreadPool:创建一个大小无限的线程池,此线程支持特定时以及周期性执行任务的需求。
newSingleThreadExecutor:创建一个单线程的线程池,此线程池支持特定时以及周期性执行任务的需求。
请叙述一下你对线程池的理解
(可以从线程池如何使用,线程池的好处,线程池的启动策略)
- 好处
降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。- 启动策略
线程池创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里有任务,线程池也不会马上执行它们。
当调用execute()方法时添加一个任务时,线程会作如下判断:
a. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务。
b. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列。
c.如果队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建线程运行这个任务。
d. 如果队列满了,而且正在运行的线程的数量大于或等于maximumPoolSize,那么线程会抛出异常,告诉调用者“我不能再接受任务了”- 当一个线程完成任务时,它会从队列中取出下一个任务来执行。
- 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程会被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
三个线程a,b,c并发执行,b,c需要a线程的数据怎么实现
ThreadA,ThreadB,ThreadC三个线程,ThreadA用于初始化数据num,只有num初始化完成后再让ThreadB和ThreadC获取到初始化后的变量num。
代码实现:
public class ThreadCommunication {
//定义一个变量作为数据
private static int num;
/**
* 解决方案
* 定义一个信号量,该类内部维持了多个线程锁,可以阻塞多个线程,释放多个线程
* 线程的阻塞和释放是通过permit概念实现的
* 线程通过semaphore.acquire()方法获取permit,如果当前semaphore有permit则分配给该线程
* 如果没有则阻塞该线程直到semaphore
* 调用release()释放permit
* 构造函数中参数:permit(允许)个数
*/
private static Semaphore semaphore = new Semaphore(0);
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
//模拟耗时操作之后初始化便利num
try {
Thread.sleep(1000);
num = 1;
//初始化完参数后释放两个permit
semaphore.release(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
//获取permit,如果semaphore没有可用的permit则等待,如果有则消耗一个
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"获取到num的值为:"+num);
}
});
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"获取到num的值为:"+num);
}
});
//同时开启三个线程
threadA.start();
threadB.start();
threadC.start();
}
}
同一个类中的2个方法都加了同步锁,多个线程能同时访问同一个类中的这两个方法吗
考虑Lock与synchronized两个实现锁的不同情形,会有不同的结果。
Lock可以让等待锁的线程响应中断,Lock获取锁,之后需要释放锁,所以多个线程不可访问同一个类中的2个加了Lock锁的方法。
public class LockTest {
private int count = 0;
//设置lock锁
private Lock lock = new ReentrantLock();
//方法一
public Runnable run1 = new Runnable() {
@Override
public void run() {
lock.lock();//枷锁
while (count < 1000) {
System.out.println(Thread.currentThread().getName() + "run1:" + count++);
}
}
};
public Runnable run2 = new Runnable() {
@Override
public void run() {
lock.lock();
while (count<1000){
System.out.println(Thread.currentThread().getName() + "run2:" + count++);
}
}
};
public static void main(String[] args) {
LockTest lock = new LockTest();
new Thread(lock.run1).start();//获取该对象的方法1
new Thread(lock.run2).start();//获取该对象的方法2
}
}
而synchronized却不行,使用synchronized时,当我们访问同一个类对象的时候,是同一把锁,所以可以访问该对象的其他synchronized方法,代码如下:
public class SynchronizedTest {
private int count = 0;
//设置lock锁
private Lock lock = new ReentrantLock();
//方法一
public Runnable run1 = new Runnable() {
@Override
public void run() {
synchronized (this){//枷锁
while (count < 1000) {
System.out.println(Thread.currentThread().getName() + "run1:" + count++);
}
}
}};
public Runnable run2 = new Runnable() {
@Override
public void run() {
synchronized (this){
while (count<1000){
System.out.println(Thread.currentThread().getName() + "run2:" + count++);
}
}
}};
public static void main(String[] args) {
SynchronizedTest lock = new SynchronizedTest();
new Thread(lock.run1).start();//获取该对象的方法1
new Thread(lock.run2).start();//获取该对象的方法2
}
}
什么情况下导致线程死锁,遇到线程死锁该怎么解决
死锁的定义:所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进城将无法向前推进。
死锁产生的必要条件
互斥条件:线程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一个线程所占有。此时若有其它线程请求资源,则请求线程只能等待。
不剥夺条件:线程所获得资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)
请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求,即存在一个等到状态的线程集合(p1,p2,p3…pi)其中pi等待的资源被p(i+1)占有,pn等待的资源被p0占有。
如何避免死锁
- 枷锁顺序(线程按照一定的顺序加锁)
public class DeadLock {
public int flag = 1;
//静态资源是类的所有对象共享的
private static Object o1 = new Object(),o2 = new Object();
public void money(int flag){
this.flag = flag;
if (flag==1){
synchronized (o1){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println("当前的线程是"+
Thread.currentThread().getName()+" "+"flag 的值"+"1");
}
}
}
if (flag==0){
synchronized (o2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
System.out.println("当前的线程是"+
Thread.currentThread().getName()+" "+"flag 的值"+"0");
}
}
}
}
public static void main(String[] args) {
final DeadLock deadLock1 = new DeadLock();
final DeadLock deadLock2 = new DeadLock();
deadLock1.flag=1;
deadLock2.flag=0;
//deadLock1,deadLock2都处于执行状态,但JVM线程调度先执行哪个线程是不确定的
//deadLock2的run()可能在deadLock1的run()之前运行
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
deadLock1.flag=1;
deadLock1.money(1);
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//让deadLock2等待deadLock1执行完
try {
t1.join();//让t1执行完后t2才会执行
} catch (InterruptedException e) {
e.printStackTrace();
}
deadLock2.flag=0;
deadLock1.money(0);
}
});
t2.start();
}
}
- 加锁时限
线程尝试获取锁的时候加上一定的时限,超时则放弃对该锁的请求,并释放自己占有的锁
public class DeadLockTest {
public int flag = 1;
private static Object o1 = new Object(),o2 = new Object();
public void money(int flag){
this.flag = flag;
if (flag==1){
synchronized (o1){
try {
Thread.sleep(500);
synchronized (o2){
System.out.println("当前的线程是"+
Thread.currentThread().getName()+" "+"flag 的值"+"1");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (flag==0){
synchronized (o2){
try {
Thread.sleep(500);
synchronized (o1){
System.out.println("当前的线程是"+
Thread.currentThread().getName()+" "+"flag 的值"+"0");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
final DeadLockTest td1 = new DeadLockTest();
final DeadLockTest td2 = new DeadLockTest();
td1.flag=1;
td2.flag=0;
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
td1.flag = 1;
try {
if (lock.tryLock(5000, TimeUnit.MILLISECONDS)){
System.out.println(name+"获取到锁");
}else {
System.out.println(name+"获取不到锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
td1.money(1);
} catch (Exception e) {
System.out.println(name+"出错了!!!");
} finally {
System.out.println("当前线程是:"+Thread.currentThread().getName()+"释放锁!!");
lock.unlock();
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
td1.flag = 1;
try {
if (lock.tryLock(5000,TimeUnit.MILLISECONDS)){
System.out.println(name+"获取到锁!");
}else {
System.out.println(name+"获取不到锁!!");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
td2.money(0);
} catch (Exception e) {
System.out.println(name+"出错了!!!");
} finally {
System.out.println("当前的线程是"+Thread.currentThread().getName()+"释放锁!");
}
}
});
t2.start();
}
}