目录
Thread 类中的start() 和 run() 方法有什么区别?
2.CountDownLatch是什么?和wait,notify用法区别。
现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
在Java中Lock接口相比synchronized块的优势是什么?
什么是线程?什么是进程?
线程是操作系统能够进行运算调度的最小单位,线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。就好比我们的淘宝软件就是一个进程,而淘宝里面的一个个功能就是线程。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。
创建线程的方式和实现:
- 继承 Thread 类(优点: 代码简单 ; 缺点: 该类无法集成别的类);
- 实现 Runnable 接口(优点: 继承其他类,同一实现该接口的实例可以共享资源;缺点: 代码复杂) ;
- 实现 Callable 接口(优点: 可以获得异步任务的返回值);
- 线程池方式(实现自动化装配, 易于管理, 循环利用资源);
用Runnable还是Thread?
JAVA不支持类的多继承,但是可以实现多个接口,所以如果要继承别的类,就实现Runnable接口,其他情况可以继承Thread类,因为代码简单。
Thread 类中的start() 和 run() 方法有什么区别?
通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,Run方法运行结束,此线程随即终止。而run方法只是Thread类中的一个普通方法调用,还是在主线程里执行。
wait() 和 sleep()方法有什么不同?
Sleep()方法不会释放锁,仅仅释放cpu资源,不需要主动唤醒。
Wait()会使线程进入等待状态,在被唤醒前它会释放锁。需要主动唤醒。
线程的生命周期,线程有那些状态
新建状态(new Thread) 当创建 Thread 类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();
就绪状态(runnable) 线程已经被启动, 正在等待被分配给 CPU 时间片, 也就是说此时线程正在就绪队列中排队等候得到 CPU 资源。 例如: t1.start();
运行状态(running) 线程获得 CPU 资源正在执行任务(run()方法), 此时除非此线程自动放弃 CPU 资源或者有优先级更高的线程进入, 线程将一直运行到结束。
死亡状态(dead) 当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行 ;
- 自然终止:正常运行 run()方法后终止;
- 异常终止:调用 stop()方法让一个线程终止运行;
堵塞状态(blocked) 由于某种原因导致正在运行的线程让出 CPU 并暂停自己的执行, 即进入堵塞状态。 正在睡眠:用 sleep(long t) 方法可使线程进入睡眠方式。 一个睡眠着的线程 在指定的时间后可进入就绪状态;
正在等待:调用 wait()方法。 (调用 motify()方法回到就绪状态);
为什么要用线程池?线程池的创建
创建线程要花费昂贵的资源和时间,如果每次任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。用 Executors 来创建线程池。常见的线程池有:
newSingleThreadExecutor():线程池中每次只有一个线程工作,单线程串行执行任务。
newFixedThreadExecutor(n):固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行。
newCacheThreadExecutor():可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
newScheduledThreadPool():线程池支持定时以及周期性执行任务,创建一个corePoolSize为传入参数,最大线程数为整形的最大数的线程池。
阿里开发规范为什么不允许使用Executors
1)newFixedThreadPool和newSingleThreadExecutor:
默认队列使用了 new LinkedBlockingQueue<Runnable>()
理论上可以无限添加任务到线程池。
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
线程池-ThreadPoolExecutor
7个核心参数:
// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位
// 参数五:任务队列
// 参数六:创建线程工厂
// 参数七:任务的拒绝策略
拒绝策略:
CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大。
AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
DiscardPolicy - 直接丢弃,其他啥都没有。
DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入。
死锁的产生条件,怎样避免死锁?
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。
线程间通信的方式
1.wait(),notify().
这两个方法必须放在synchronized方法中,因为这两方法要求在调用时线程已经获得了对象的锁。
一个线程用notify方法去唤醒一个正在等待wait状态的线程。要注意notify方法不释放锁,即使唤醒了b线程,依旧会先执行完自己的全部代码,才会开始执行b线程。
2.CountDownLatch是什么?和wait,notify用法区别。
CountDownLatch是java.util.concurrent包下的工具类
它有两个核心方法
countDownLatch.countDown();
countDownLatch.await();
比如有三个线程,a线程必须等其他两个线程逻辑全部处理完后a线程才可以运行,在此之前会等待
其他线程处理完成后调用countDown()方法,a线程用await()监听到,然后就会开始运行。整个过程中没有释不释放锁这一回事。而wait,notify方法这两个方法都必须写在synchronized里,直接写会崩,因为wait是个释放锁的方法,只有先锁住了才有锁,还有notify后不是立马释放锁的,synchronized方法块要走完。
3.基于volatile关键字
大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候,线程能够感知并执行相应的业务。
static volatile boolean flag = false。
a线程把flag的值改成true
b线程while true 循环监听flag的值,发现变成true,就开始执行自己的逻辑。
线程同步(堵塞队列)的方法有哪些
1.同步方法
2.同步代码块
3.wait与notify
wait使一个线程处于等待状态,释放lock
notify唤醒一个处于等待状态的线程,如果有多个等待线程,由jvm决定唤醒哪个线程
notifyAll唤醒所有处于等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们去竞争锁资源。
4.volatile关键字
volatile不会提供任何原子操作,也不能用于修饰final变量
5.局部变量ThreadLocal管理变量
同步和异步的区别,什么情况下使用哪个
同步:a线程b线程同时对一个数据进行操作,当b线程准备操作数据时,发现这个资源正在被a使用,同步机制就会让线程b一直等待下去,直到a线程使用完毕,释放资源后,b线程才能去操作这个数据
同步机制能够保证资源的安全,最简单的实现方式是同步方法,同步代码块
异步:每一个线程都包含了运行时自身锁需要的数据,因此在处理时不必关心其他线程的状态或者行为。
比如:用户调用了一个方法,这个方法运行很久才能运行完,我们不希望方法全部运行完才给用户返回结果,就可以先讲用户请求放入消息队列,然后就返回用户结果。然后系统在慢慢去处理。
和交易,钱相关的,比如银行转账,这种就要同步处理。其他不那么重要的,处理时间较长的就可以异步处理。
volatile关键字是什么,有什么作用
一旦一个共享变量被volatile修饰之后,不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。但是不能保证原子性。
现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
thread.Join()把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
join的源码用wait方法实现的。
t.join(); //调用join方法,等待线程t执行完毕
t.join(1000); //等待 t 线程,等待时间是1000毫秒。
public static void main(String[] args) {
method01();
method02();
}
/**
* 第一种实现方式,顺序写死在线程代码的内部了,有时候不方便
*/
private static void method01() {
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
System.out.println("t1 is finished");
}
});
Thread t2 = new Thread(new Runnable() {
@Override public void run() {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 is finished");
}
});
Thread t3 = new Thread(new Runnable() {
@Override public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3 is finished");
}
});
t1.start();
t2.start();
t3.start();
}
/**
* 第二种实现方式,线程执行顺序可以在方法中调换
*/
private static void method02(){
Runnable runnable = new Runnable() {
@Override public void run() {
System.out.println(Thread.currentThread().getName() + "执行完成");
}
};
Thread t1 = new Thread(runnable, "t1");
Thread t2 = new Thread(runnable, "t2");
Thread t3 = new Thread(runnable, "t3");
try {
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
在Java中Lock接口相比synchronized块的优势是什么?
整体上来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操作。另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。
什么是自旋锁?
当线程A想要获取一把自旋锁而该锁又被其它线程锁持有时,线程A会在一个循环中自旋以检测锁是不是已经可用了。
自旋锁需要注意:
- 由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这就会浪费CPU时间。
- 持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁。