面试官必问java 并发知识总结-线程
Posted 神技圈子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试官必问java 并发知识总结-线程相关的知识,希望对你有一定的参考价值。
线程是什么
线程是针对cpu来说的一个执行独立单元,线程的资源来自进程。进程内的线程是共享资源的(如内存 文件句柄 网络等)所以就有线程的并发冲突问题,进程间的资源是隔离的。java.lang.Thread来定义一个线程,并提供对线程的操作。
进程的执行需要依赖线程(前面说了线程是CPU执行的一个最小独立单元),用户态的线程都是人为显示构建的。
分为:用户态和内核态
—>内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
—>用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取
为什么需要用户态和内核态?
—>由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 :用户态 和 内核态
怎么创建一个线程并启动线程
当前java里面有如下三种方式创建线程
- 继承Thread类创建线程类
- 实现Runnable接口
- 通过Callable和Future创建线程
先说第一种继承Thread类创建 :
thread的构建函数有9种,支持不同的实现方式,简单说下如果参数里面的有string的,是让你定义线程名,这个对问题定位是有好处的,能够知道是哪个线程。如果带runnable的,则线程在启动时候会调用 Runnable 的run方法(必须实现run方法),如果有ThreadGroup的,则创建的线程会由该ThreadGroup管理。注意 你可以直接调用 Thread的run()方法,但是这个时候你不是以多线程方式运行程序,你直接调用了一个普通的方法 , 如果以多线程方式启动 只能调用 Thread.start()
第二种通过实现 Runnable接口来实现
类似于 public class NewThread implement Runnable
@Override
public void run()
//方法实现 System.out.println("aaaa");
若要启动线程,需要new Thread(Runnable target).start(),看了Thread 类的run方法就会看到
@Override
public void run()
if (target != null)
target.run();
传进去的Runnale target对象的run()方法。但是这种方式有个好处。你可以继承其他类在实现 Runnable 接口
第三种方式 通过Callable和Future创建线程
为什么要有这种方式?通过看代码你会发现 不管是 继承 Thread 或者实现 Runnable, run()方法是没有返回值的,
public abstract void run();
而 Callable 和Future创建线程就能有返回值
@FunctionalInterface
public interface Callable<V>
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
还一个重要点的,Callable的call()方法是 可以throws Exception的, run() 方法是不可以的。
这种方式创建一个线程并启动的方式如下:
1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。 FutureTask 实现了public class FutureTask<V> implements RunnableFuture<V> ,
而 RunableFutrue 又继承了public interface RunnableFuture<V> extends Runnable, Future<V>
3. 使用FutureTask对象作为Thread对象的target,并调用thread的start()来启动
4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
补充几点: 1)Thread 类还有个setDaemon()方法,这个方法的作用是把线程设置为守护线程,在其他非守护线程退出后做一些善后工作。
2)当一个线程正常地运行结束或者抛出某种未检测的异常,线程就会终止。当线程终止之后,是不能被重新启动的。
3)同一个Thread上无法多次调用start()方法,如果调用了会抛出InvalidThreadStateException异常。
4) 如果线程已经启动但是还没有终止,那么调用isAlive方法就会返回true.如果线程已经被取消,那么调用其isAlive在什么时候返回false不太确定,可能有不同的实现。
线程优先级
Java提供一个线程调度器来监视和控制Runnable状态的线程。线程的调度策略采用抢占式,优先级高的线程比优先级低的线程优先执行。在优先级相同的情况下,按照“先到先服务”的原则。
依赖于jvm的实现,优先级不能保证线程一定会按序执行,会影响线程的调度。当前优先级会按照 Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之间(分别为1和10)
这些优先级范围跟对应的说明如下
优先级范围 | 说明 | 权重 |
10 | 应急处理 | 最大 |
7-9 | 交互相关,事件驱动 | 次大 |
4-6 | IO限制类 | 中 |
2-3 | 后台计算 | 小 |
1 | 仅在没有任何线程运行时运行的 | 最小 |
如果不做特殊设置,假如 A线程里面创建了B线程,则A与 B的线程优先级相同 。默认情况下线程的优先级是 5 ,可以通过下面例子证明 :
public static void main(String[] args)
Thread.currentThread().setPriority(4);
Thread th = new Thread();
System.out.println(th.getPriority());
输出 如下
4
public static void main(String[] args)
Thread th = new Thread();
System.out.println(th.getPriority());
输出如下 :
5
但是你可以改这个线程的优先级 通过方法 : th.setPriority(5);
但是线程的优先级在不同虚拟机的调度机制是不一样的,有些虚拟机就是把最高优先级的先执行,这样可能会导致优先级低的线程被饿死,有些是公平对待的,保证线程都能被调度到。
线程的控制
线程其实是有方法把它挂起或者让他中断的,Thread 类也提供了对应的方法,下面列几种常见下线程的控制以及对应的状态情况: 线程无非就几种状态: 等待执行 正在执行 挂起 中断死亡 ,下面从这集中状态的控制来说明下线程的控制:
1. sleep() & interrupt()
对一个线程在其上调用sleep()暂停着,那么这个线程就挂起了,例如: thread1.sleep(100) 单位是ms; 如果要取消他的等待状态,可以在正在执行的线程里(非thread1),thread1.interrupt();
这个操作产生的效果就是thread1放弃睡眠操作,当在sleep中时 线程被调用interrupt()时,就马上会放弃暂停的状态.并抛出 InterruptedException .丢出异常的,是 thread1线程.
2. wait() & interrupt()
对线程thread1调用了wait()进入了等待状态,也可以通过调用thread1.interrupt().不过这时候要小心锁定的问题.线程在进入等待区, 会把锁定解除 ,当对等待中的线程调用 interrupt()时,会先重新获取锁定,再抛出异常.在获取锁定之前,是无法抛出异常的.
3. join() & interrupt()
当线程以join()等待其他线程结束时,当它被调用interrupt(),它与sleep()时一样, 会马上跳到 catch 块里. 注意interrupt()方法,一定是调用被阻塞线程的interrupt方法.如在线程a中调用来线程t.join().
则a会等t执行完后在执行t.join后的代码,当在线程b中调用来 a.interrupt()方法,则会抛出InterruptedException,当然join()也就被取消了。
每个线程都有一个相关的Boolean类型的中断标识。在线程t上调用t.interrupt会将该线程的中断标识设为true,除非线程t正处于Object.wait,Thread.sleep,或者Thread.join,这些情况下interrupt调用会导致t上的这些操作抛出InterruptedException异常,但是t的中断标识会被设为false。任何一个线程的中断状态都可以通过调用isInterrupted方法来得到。如果线程已经通过interrupt方法被中断,这个方法将会返回true。
起初,Thread类还支持一些另外一些控制方法:suspend,resume,stop以及destroy。这几个方法已经被声明过期。其中destroy方法从来没有被实现,估计以后也不会。而通过使用等待/唤醒机制增加suspend和resume方法在安全性和可靠性上还不成熟。
静态方法
Thread类中的部分方法被设计为只能当前正在运行的线程(即调用Thread方法的线程,这些方法被搞了 public static的。
- Thread.currentThread方法会返回当前线程的引用,得到这个引用可以用来调用其他的非静态方法,比如Thread.currentThread().destroy(),销毁线程,只是该方法当前不建议用了。
- Thread.interrupted方法会清除当前线程的中断状态并返回前一个状态。(一个线程的中断状态是不允许被其他线程清除的)
- Thread.sleep(long msecs)方法会使得当前线程暂停执行至少msecs毫秒。
常用的方法就这些,其他的可以暂时不用关注。
线程组
每一个线程都是一个线程组中的成员。默认情况下,线程组是继承性的,比如新建线程和创建它的线程属于同一个线程组。但是当创建一个新的线程组,这个线程组就会变成当前线程组的子线程组,类似子节点的概念。你如果想看当前线程属于哪个线程组,调用线程的getThreadGroup方法会返回所属的线程组,ThreadGroup类也有方法可以得到哪些线程目前属于这个线程组,比如enumerate方法。
为什么要有ThreadGroup的概念?:
1)安全隔离:比如对不属于同一组的线程调用interrupt是不合法的。这是为避免某些问题(比如,一个applet线程尝试杀掉主屏幕的刷新线程)所采取的措施。T
2)可以通过ThreadGroup也可以为该组所有线程设置一个最大的线程优先级。
实际的使用中线程组往往不会直接在程序中被使用。如果想通过程序看下线程如何分组的,可以通过一个vector等类来实现,那么普通的集合类(比如java.util.Vector)应是更好的选择。但是线程组的uncaughtException方法却是非常有用的,如果线程因为运行时异常而中断的时候,调用这个方法可以打印出线程的调用栈信息。
以上是关于面试官必问java 并发知识总结-线程的主要内容,如果未能解决你的问题,请参考以下文章