目录
面试:进程和线程的区别
Java进程与线程的关系
面试: Thread中start与run方法的区别
面试: Thread和Runnable是什么关系?
面试: 如何给run方法传递参数?
面试: 如何处理线程的返回值?
线程的状态
New
Runnable
Waiting
Timed Waiting
Blocked
Terminated
面试: sleep和wait的区别
面试: notify和notifyAll的区别
yield
面试: 如何中断线程?
面试:进程和线程的区别
- 进程是资源分配的最小单位, 线程是CPU调度的最小单位
- 所有与进程有关的内容都被记录在PCB(进程控制块)中
- 进程独占内存空间, 保存各自的运行状态, 相互间不干扰且可相互切换, 为并发处理任务提供了可能
- 线程共享进程的内存资源, 相互间切换更快速, 支持更细粒度的任务控制, 使进程内的子任务得以并发执行
- 进程切换比线程切换开销大
Java进程与线程的关系
- 每运行一个Java程序就会产生一个进程, 一个进程至少包含一个线程
- 一个进程对应一个jvm实例, 每一个jvm实例都有一个自己的堆, 多个线程共享进程的堆, 每个线程又都有自己的栈空间
- Java采用单线程编程模式, 程序会自动创建主线程
- 耗时的操作可以放到子线程中, 以避免阻塞主线程执行
- 主线程可以创建子线程, 原则上要后于子线程完成执行
- jvm实例除了创建主线程之外还会创建其他的线程, 例如垃圾回收线程
- 进程是资源分配的最小单位, 线程是CPU调度的最小单位
面试: Thread中start与run方法的区别
- 调用start方法会创建一个新的子线程并启动, 然后通过子线程去执行run方法
- 而run方法仅仅是Thread的一个普通方法调用而已, 里面放的是子线程任务的方法体, 如果你在main方法直接调用run方法, 那他也是会执行的, 但调用他的是主线程而不是子线程
面试: Thread和Runnable是什么关系?
- Runnable是一个接口, 里面只有一个run抽象方法
- Thread是一个类, 它实现了Runnable接口, 使run方法支持多线程
- 因为Java的单一继承原则, 为了提高程序的可扩展性, 推荐多使用Runnable接口
- 一个类只能由一个父类, 继承了Thread就没办法继承别的类了
- 使用Thread创建线程: 继承Thread类, 实现里面的run方法, 调用start方法即可.
- 使用Runnable创建线程: 创建Runnable接口的实现类, 重写里面的run方法, 创建Thread对象, 并把前面的实现类对象作为参数传递给Thread的构造方法, 调用Thread对象的start方法即可.
面试: 如何给run方法传递参数?
- 实现的方法主要有三种
- 使用构造函数传参
- 使用成员变量传参
- get/set
- 使用回调函数传参
- 实现的方法主要有三种
面试: 如何处理线程的返回值?
- 实现的方法有三种
主线程等待法
- 让主线程使用while循环不断的去查询成员变量, 直到成员变量符合条件为止
- 缺点
- 当需要等待的线程多起来之后, 代码就会臃肿
- 循环一次的时间不好确定
- 使用Thread的join方法阻塞当前线程, 直到子线程执行完毕
package demo08_join;/*** @author LQ* @create 2020-07-25 17:57*/ public class Demo01 implements Runnable {private static String value;@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}value = "qweqwe";}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new Demo01());thread.start();thread.join();System.out.println("value = " + value);} }- 优点: 实现起来简单, 不用自己去确定等待时间了
- 通过实现Callable接口
- 使用这种方法时依旧有两种子方法来获取返回值
- 通过使用FutureTesk对象获取返回值
- FutureTask继承了Future接口
- FutureTask有两个get方法
- 无参的方法是, 当子线程执行完毕后获取其返回值
- 带参的方法是, 等待指定的时间来获取其返回值, 超时即放弃
- 代码
package demo09_callable;import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;/*** @author LQ* @create 2020-07-25 18:11*/ public class Test01 {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<String> task = new FutureTask<>(new MyCallable());Thread thread = new Thread(task);thread.start();if (!task.isDone()){System.out.println("工作还没有完成!");}System.out.println("完成了"+task.get());System.out.println("111");} }
- 通过线程池获取返回值
- 方法和FutureTask差不多
- 线程池的submit方法可以返回一个Future对象, 对象里面有get和isDone方法
- 代码
package demo09_callable;import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future;/*** @author LQ* @create 2020-07-25 18:28*/ public class Test02_ThreadPool {public static void main(String[] args) {ExecutorService threadPool = Executors.newCachedThreadPool();// 创建线程池Future<String> stringFuture = threadPool.submit(new MyCallable());// 提交线程任务, 返回的是一个Futureif (!stringFuture.isDone()) {// 线程还没有执行完毕System.out.println("任务还怎么完成~");}try {System.out.println("任务完成了 " + stringFuture.get());} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();} finally {threadPool.shutdown();// 关闭线程池}} }
- 通过使用FutureTesk对象获取返回值
- Callable接口
- 这个接口只有一个方法call, 它其实和run方法差不多, 都是给线程设置任务
- 实现了Callable接口的类
package demo09_callable;import java.util.concurrent.Callable;/*** @author LQ* @create 2020-07-25 18:09*/ public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {System.out.println("开始工作");Thread.sleep(2000);System.out.println("工作完成");return "hello";} }
- 使用这种方法时依旧有两种子方法来获取返回值
- 实现的方法有三种
线程的状态
New
- 线程创建好了还没有启动时就是这个状态
Runnable
- 线程创建好了调用了start方法会进入这个状态
- 这个状态有两个子状态Running Ready
- Running: 线程正在使用cpu
- Ready: 线程可以运行, 正在等待cpu
Waiting
- 不会被分配cpu资源, 需要其他线程去唤醒它
- 调用锁对象的wait方法和子线程的join方法都可使当前线程进入Waiting状态
Timed Waiting
- 在等待一段时间后可以自动唤醒
- 使用sleep方法或者带参数的wait和join方法可以使线程进入这个状态
Blocked
- 等待获取排它锁时会进入这个状态
- 这个线程是可以运行的, 但是当轮到他使用cpu时发现锁对象被别的线程占用着, 这个线程就会被阻塞住
Terminated
- 线程执行完毕
- 线程执行完毕后再次调用start方法会报错
面试: sleep和wait的区别
- sleep是Thread类的方法, wait是Object类的方法
- sleep()方法可以在任何地方使用, wait()方法只能在同步代码块或同步方法中使用
- 因为wait()会释放锁, 既然你要释放锁, 那么你就要在同步代码块或同步方法中, 因为只有他俩才有锁
- sleep()方法只会让出cpu资源, 不会改变锁的状态, wait()不仅会让出cpu也会释放锁
- 如果在同步代码块中调用了sleep方法, 那么在sleep的时间还没有结束的时候是不会释放锁的
面试: notify和notifyAll的区别
- 先来了解一下锁池(EntryList)和等待池(WaitSet)
- 锁池: 没有获取到锁被阻塞的线程会进入锁池
- 线程获取锁是根据线程的优先级, 所以使用EntryList
- 等待池: 线程A调用了锁对象的wait方法, 线程A释放该锁对象, 并进入该锁对象的等待池中
- 唤醒等待池中的线程是随机的, 所以使用WaitSet
- 锁池: 没有获取到锁被阻塞的线程会进入锁池
- 唤醒的线程会进入到锁池中去竞争锁
- notifyAll会让所有在等待池中的线程进入锁池中去竞争锁
- notify会随机选取一个等待池中的线程进入锁池
- wait notify notifyAll 只能在同步方法or同步代码块中使用, 否则会报IllegalMonitorStateException(非法监测状态异常)
- 先来了解一下锁池(EntryList)和等待池(WaitSet)
yield
- 调用静态方法Thread.yield()即声明当前线程已经完成了其生命周期中的重要部分了, 可以将CPU让出切换到其他的线程了
- 该方法只是一个建议, 具体是否切换还是要看线程调度器, 而且也只是建议其他相同优先级的线程可以执行
面试: 如何中断线程?
- 已经被抛弃的方法
- 通过线程对象的stop()中断线程
- 这种方法是不安全的, 一个线程使用stop方法去终止另外一个线程, 它并不知道另一个线程是什么状态, 有可能一些清理工作还没有完成
- 目前使用的方法
- interrupt(): 通知线程应该中断了
- 调用interrupt方法后, 被中断的线程会有下面两种情况
- 如果线程正处在阻塞状态(join) or 计时等待(sleep) or 无限等待(wait), 那么线程会立即退出被阻塞状态, 并抛出一个InterruptedException异常, 从而提前结束该进程. 但是不能中断I/O阻塞和synchronized锁阻塞
- 线程正常运行完毕会结束, 线程抛出异常也会结束
- 如果线程处在正常运行状态, 那么会将线程的中断标志位置为true. 被中断的线程正常运行, 如果需要中断的话只需要不断的监听当前线程的中断标志位即可
- 使用线程对象的isInterrupted()方法获取线程的中断标志位
- 使用线程对象的interrupted()方法也行
- 如果线程正处在阻塞状态(join) or 计时等待(sleep) or 无限等待(wait), 那么线程会立即退出被阻塞状态, 并抛出一个InterruptedException异常, 从而提前结束该进程. 但是不能中断I/O阻塞和synchronized锁阻塞
- Executor的中断操作
- 调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法
- 已经被抛弃的方法