1:什么是线程,和进程的区别联系
线程:
线程就是一个执行流,每个线程之间都按照顺序执行代码,多个线程间可以同时执行多份代码。
为什么需要线程(线程优势):
- 线程可以充分利用CPU资源,提高执行效率。
- 等待IO的时间里可以进行别的操作。
- 线程相比进程更加轻量级,资源消耗小于进程。
线程劣势:
增加编码复杂度,需要考虑线程安全相关。
线程状态:
- NEW:创建态
- RUNNABLE:可运行态(包含就绪和运行态)程序无法判断就绪态或者运行态
- WAITING:等待
- TIMED_WAITING:超时等待
- BLOCKED:阻塞
- TERMINATED:销毁
线程的实现方式:
- 内核线程:Java的多线程是内核线程的轻量级实现(相比进程,是一种轻量级进程,创建,调度,销毁的效率高)
- 用户线程:由程序实现,包括线程状态转移,调度等
面试题:线程和进程
- 进程的内存独立,相互隔离,一个进程的多个线程共享内存。
- 进程是分配资源的最小单位,而线程是CPU调度执行的最小单位。
- 相比进程,线程的创建,销毁的代价小,执行效率高。
- 进程之间独立运行,线程之间如果出错可能造成整个进程崩溃。
2:线程的创建,中断
面试题:线程创建的方式
-
继承Thread类
class MyThread extends Thread{ @Override public void run() { System.out.println("这是继承Thread的实现方式"); } }
-
实现Runnable接口
Thread thread=new Thread(new Runnable() { @Override public void run() { System.out.println("这是实现Runnable接口的实现方式"); } });
-
实现Callable接口
FutureTask<Object> futureTask=new FutureTask<>(new Callable<Object>() { @Override public Object call() throws Exception { return "这是实现Callable借助于FutureTask的实现方式"; } }); Thread thread=new Thread(futureTask); thread.start(); //获取返回值 System.out.println(futureTask.get());
面试题:start()和run()方法
使用Start()方法会创建线程,而使用Run()方法仅仅是在main线程中执行Run()方法的调用,而不是创建新的线程。Start为启动线程的方式,Run()属于线程任务的描述。
方法 | 说明 |
---|---|
getId() | 获取线程ID |
getName() | 获取线程名 |
getState() | 获取线程状态 |
getPriority() | 获取线程优先级 |
isDaemon() | 是否为后台线程 |
isAlive() | 是否存活 |
join() | 等待线程死亡 |
Thread.currentThread() | 获取当前线程的引用 |
sleep(long millis) | 使当前执行的线程以指定的毫秒数暂停 |
后台线程:
Java进程中至少有一个非后台线程存活,进程才不会结束。(后台线程为程序启动而自启动的线程)
线程中断API:
什么是线程中断:
线程运行的过程中,被其它线程打断,但是否结束或者继续执行由目标线程本身逻辑代码决定。
模拟线程中断----自定义线程中断标志位:
自定义线程中断标志位不能中断 处于阻塞/等待/超时等待状态
的线程
public class 自定义标志位中断 {
//是否中断标志位
private static boolean flag=false;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
//当标志位为true后则退出循环,线程执行结束
for(int i=0 ;i <10 && !flag; i ++){
System.out.println("打印:"+i);
try {
//为了让线程执行慢一些保证能在执行完毕前能感知到中断标志位的改变
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
//为了让main线程执行慢一点,能够让thread线程执行一段时间后再进行改变中断标志位的操作
Thread.sleep(5);
//main线程修改中断标志位
flag=true;
}
}
线程中断API | 说明 |
---|---|
interrupt() | 修改线程中断标志位为true |
interrupted() | 返回当前中断标志位,且重置标志位为false |
isInterrupted() | 返回当前中断标志位 |
利用中断API进程线程中断:
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//isInterrupted()获取中断标志位但不重置
for(int i=0;i<100 && !Thread.currentThread().isInterrupted();i++){
System.out.println("打印"+i);
}
}
});
thread.start();
Thread.sleep(1);
//修改中断标志位
thread.interrupt();
}
如果让目标线程Sleep(),则当睡眠时,中断信号到来会抛出InterruptedException
异常,且抛出异常后,目标线程继续执行直到完毕。(因为当抛出InterruptedException
异常后,会重置进程标志位)
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10 && !Thread.currentThread().isInterrupted();i++){
System.out.println("打印"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
Thread.sleep(3000);
thread.interrupt();
}
使用interrupt()方法后不一定能中断线程:
interrupt()方法并不是中断线程,只是修改中断标志位,更像是一种通知,通知目标线程中断,然而是否中断取决于目标线程的逻辑代码。
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//将上述代码 !Thread.currentThread().isInterrupted()去掉
//此时即使收到中断信号,目标线程仍执行完毕
for(int i=0;i<100 ;i++){
System.out.println("打印"+i);
}
}
});
thread.start();
Thread.sleep(1);
//修改中断标志位
thread.interrupt();
}