【多线程面试题 多线程】一、线程的基本概念
- 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合 。即指一段静态的代码,静态对象 。
- 进程(process):是程序的一次执行过程,或是正在运行的一个程序 。是一个动态的过程:有它自身的产生、存在和消亡的过程——生命周期 。
如:运行中的QQ、运行中的播放器 。
程序是静态的,进程是动态的 。
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域 。
- 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径 。
若一个进程同一时间并行执行多个线程,就是支持多线程 。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),线程切换的开销小 。
一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以访问相同的变量和对象 。这就使得线程间通信更简便、高效 。但多个线程操作共享的系统资源可能就会带来安全的隐患 。
1、使用多线程有什么优点:- 提高应用程序的响应,对图形化界面更有意义,可以增强用户的体验 。
- 提高计算机系统CPU的利用率 。
- 改善程序结构 。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改 。
- 程序需要同时执行两个或多个任务 。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等 。
- 需要一些后台运行的程序时 。
- 继承Thread类
- 重写Thread类的run()方法——>将此线程执行的操作声明在run()方法中
- 创建Tread类的子类对象
- 通过此对象调佣start()方法
例子:计算100以内的所有的偶数
class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i % 2 == 0){System.out.println(i);}}}}public class ThreadTest {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start(); //start()作用:①启动当前线程②调佣当前线程的run()方法}}//使用多线程计算100以内的质数class MyThread extends Thread{@Overridepublic void run() {boolean flag = true;for (int i = 2; i <= 100; i++) {for (int j = 2; j < i; j++) {// 除以这个数前面的数if (i % j == 0){//如果能被整除的话,则这个数不是质数flag = false;}}if (flag){System.out.println(i);}flag = true;}}}public class ThreadTest {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start(); //start()作用:①启动当前线程②调佣当前线程的run()方法}}
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调佣start()
class MyThread1 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i % 2 == 0){System.out.println(i);}}}}public class ThreadTest2 {public static void main(String[] args) {MyThread1 myThread1 = new MyThread1();Thread thread = new Thread(myThread1);thread.start();}}创建线程两种方式的比较:优先选择实现Runnable接口的方式- 实现的方式没有类的单继承性的局限性
- 实现的方式更适合来处理多个线程有共享数据的情况 。
- start():启动当前线程,调用当前线程的run()方法 。
- run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 。
- currentThread():静态方法,返回执行当前代码的线程 。
- getName():获取当前线程的名字 。
- setName():设置当前线程的名字 。
- yield():释放当前CPU执行权 。
- join():在线程A中调用线程B的join()方法,此时线程A会进入阻塞状态,直到线程B完全执行之后,线程A才结束阻塞状态 。
- stop():不建议使用 。当执行此方法时,强制结束当前线程 。
- sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒 。
- isAlive():判断当前线程是否存活 。
- 时间片策略:同优先级线程组成先进先出队列 。
- 抢占式策略:对高优先级,使用优先调度的抢占式策略 。
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5默认的线程优先级
- 如何获取和设置线程的优先级:
- getPriority():获取线程的优先级 。
- setPriority(int p):设置线程的优先级。
- 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态 。
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 。
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能 。
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态 。
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束 。
四、线程的同步例子:创建三个窗口卖票,总票数为100张 。实现Runnable接口的方式 。
问题:1.卖票的过程中出现了 重票、错票-------> 线程的安全问题 。
产生问题的原因:当某个线程操作车票的过程中,在没有完成的情况下,又有其他线程也参与进来 。
如何解决:当一个线程正在操作共享数据的时候,其他线程不能参与出来,直到当前线程操作完成之后,其他线程才可以开始操作 。
这种情况即使出现了阻塞,也不能被改变 。
在java开发过程中:通过同步机制,来解决线程的安全问题 。
方式一:同步代码块synchronized(同步监视器){
?//需要被同步的代码说明:操作共享数据的代码,即为需要被同步的代码 。
?//同步监视器,俗称:锁;任何一个类的对象都可以充当锁 。
?//要求:多个线程必须要公用同一把锁 。
}
class Window1 implements Runnable{private int ticket = 100;Object obj = new Object();@Overridepublic void run() {while (true){synchronized (obj){if (ticket > 0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":" + "卖票,票号为:" + ticket);ticket--;}else{break;}}}}}public class WindowTest1 {public static void main(String[] args) {Window1 window1 = new Window1();Thread thread1 = new Thread(window1);Thread thread2 = new Thread(window1);Thread thread3 = new Thread(window1);thread1.setName("窗口1");thread2.setName("窗口2");thread3.setName("窗口3");thread1.start();thread2.start();thread3.start();}}方式二:同步方法如果操作共享数据的代码完整的声明在一个方法中,我们可以把这个方法声明为同步的 。class Window2 implements Runnable {private int ticket = 100;@Overridepublic void run() {while (true) {show();}}private synchronized void show() {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":" + "卖票,票号为:" + ticket);ticket--;}}}public class WindowTest2 {public static void main(String[] args) {Window2 window2 = new Window2();Thread thread1 = new Thread(window2);Thread thread2 = new Thread(window2);Thread thread3 = new Thread(window2);thread1.setName("窗口1");thread2.setName("窗口2");thread3.setName("窗口3");thread1.start();thread2.start();thread3.start();}}线程的死锁问题死锁:- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁 。
- 出现死锁之后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续 。
- 专门的算法、原则 。
- 尽量减少同步资源的定义 。
- 尽量避免嵌套同步 。
- wait():一旦执行此方法之后,当前线程就会进入阻塞状态,并释放同步监视器 。
- notify():一旦执行此方法之后,就会唤醒被阻塞的线程 。如果有多个线程被阻塞的话,则会唤醒优先级较高的线程 。
- notifyAll():一旦执行此方法,就会唤醒所有被阻塞的线程 。
class PrintNum implements Runnable{private int number = 1;@Overridepublic void run() {while (true){//同步代码块,解决线程安全问题,this表示当前的对象,一定要唯一 。synchronized (this){notify();//唤醒单个阻塞的线程//notifyAll();//唤醒全部阻塞的线程try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if (number <= 100){System.out.println(Thread.currentThread().getName() + ":" + number);number++;//实现交替打印,当一个线程执行之后,让它进入阻塞状态 。try {wait();} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}}}}public class CommunicationTest {public static void main(String[] args) {PrintNum printNum = new PrintNum();Thread thread1 = new Thread(printNum);Thread thread2 = new Thread(printNum);thread1.setName("线程1");thread2.setName("线程2");thread1.start();thread2.start();}}面试题:1. sleep() 和 wait() 的异同:- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态 。
- 不同点:Thread类中声明sleep()方法,Object类中声明wait()方法 。
?sleep()方法可以在任何需要的场景下调用,wait()方法必须在同步代码块或同步方法中调用 。
?如果是两个方法都是用在同步代码块或同步方法中,sleep()方法不会释放同步监视器,wait()方法会释放同步监视器 。
- 重写call()方法,可以有返回值 。
- 方法可以抛出异常 。
- 支持泛型的返回值 。
- 需要借助FutureTask类,比如获取返回结果 。
Future接口:
可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等 。
FutrueTask是Futrue接口的唯一的实现类 。
FutureTask 同时实现了Runnable, Future接口 。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值 。
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池 。
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池 。
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池 。
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池 。
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行 。
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
