1 编程题
1.1 打印数字,读取通知
(1)在main方法中启动两个线程;
(2)在1个线程循环打印100以内的整数;
(3)直到第2个线程从键盘读取了“Q”命令。
🐦 思路解析:
需要使用一个线程取控制另外一个线程;
main线程启动两个线程,一个线程打印100以内整数,另一个线程可以终止打印整数线程,可以考虑采用通知的方式终止打印线程;
记打印数字线程为A,监听键盘线程为B。要想让线程B能够通知线程A,需要B线程持有A线程对象,通过改变A线程的某个参数,达到通知A线程终止的目的。
参考代码
/** * @author 兴趣使然黄小黄 * @version 1.0 */ public class ThreadTest01 { public static void main(String[] args) { PrintNum printNum = new PrintNum(); Thread printThread = new Thread(printNum); Thread readThread = new Thread(new ReadKeyboard(printNum)); printThread.start(); readThread.start(); } } /** * 打印数字的线程 */ class PrintNum implements Runnable{ private boolean loop = true; @Override public void run() { while (loop) { System.out.println((int)(Math.random() * 100 + 1)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public void setLoop(boolean loop) { this.loop = loop; } } /** * 读取键盘的线程 */ class ReadKeyboard implements Runnable{ private PrintNum printNum; private Scanner scanner = new Scanner(System.in); /** * 利用构造器,传入PrintNum线程对象,保证对象为同一个 * @param printNum */ public ReadKeyboard(PrintNum printNum) { this.printNum = printNum; } @Override public void run() { //接收用户输入 while (true){ System.out.println("输入Q退出程序: "); char key = scanner.next().toUpperCase().charAt(0); if (key == 'Q'){ //通知PrintNum线程退出 printNum.setLoop(false); break; } } } }
🐱 扩展:
也 可以考虑将loop设置为静态成员, 这样另一个线程就无需再传入一个打印线程的对象了,具体见下图:
同时,也可以采用守护线程的做法, 将打印数字作为读取Q线程的守护线程,当读取Q线程消亡时,作为守护线程的打印数字线程,也会停止,参考图如下:
1.2 取钱问题
(1)有2个用户分别从一个卡上取钱(总额:5200元);
(2)每次都取500块钱;
(3)当余额不足时,不能再取钱;
(4)不能出现超取现象
🐦 思路解析:
考虑采用线程的同步机制来解决取钱问题;
两个用户取钱分别记为t1与t2两个线程;
当这两个线程进行取钱操作前,需要获得锁;
锁被一个线程占用时,另一个线程进入blocked阻塞状态,等待获得锁,才能进行取钱。
参考代码
/** * @author 兴趣使然黄小黄 * @version 1.0 * 取钱问题 */ public class ThreadTest02 { public static void main(String[] args) { WithDrawMoney withDrawMoney = new WithDrawMoney(); Thread thread1 = new Thread(withDrawMoney); Thread thread2 = new Thread(withDrawMoney); thread1.setName("路人甲"); thread2.setName("路人乙"); thread1.start(); thread2.start(); } } /** * 取钱的线程 */ class WithDrawMoney implements Runnable{ private int money = 5200; private int draw = 500; @Override public void run() { synchronized (this){ while (true){ //余额是否足够 if (money < draw){ System.out.println("余额不足... ..."); break; } //取钱 money -= draw; System.out.println(Thread.currentThread().getName() + "取走了 " + draw + "元, 当前余额: " + money + "元"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void setMoney(int money) { this.money = money; } public void setDraw(int draw) { this.draw = draw; } }
🐰 结果如下:
🐯 为什么都是路人甲,路人乙去哪里了?
答: 在该程序中,使用到的是this对象锁,该锁为非公平锁。
比如:t1、t2、t3三个线程都进行获取锁的操作,而每次都是t1获取到锁,就会导致t2、t3两个线程都被阻塞(Blocked),看起来就像没有执行一样。
2 判断题
如果线程死亡,它便不能运行。(T)
在Java中,高优先级的可运行线程会抢占低优先级线程。(T )
线程可以用yield方法使低优先级的线程运行。(F)
程序开发者必须创建一个线程去管理内存的分配。(T)
一个线程在调用它的start方法之前,该线程将一直处于出生期。( T)
当调用一个正在进行线程的stop( )方法时,该线程便会进入休眠状态。(F)
一个线程可以调用yield方法使其他线程有机会运行。(T)
多线程没有安全问题(F)
多线程安全问题的解决方案可以使用Lock提供的具体的锁对象操作(T)
Stop()方法是终止当前线程的一种状态(T)
3 简答题
1️⃣ 简述程序、进程和线程之间的关系?什么是多线程程序?
答:程序: 程序就是一段代码,一组指令的集合,不能单独运行,需要将其加载到内存中,系统为他分配资源后才能执行,运行时就相当于一个进程。
进程: 进程就是系统分配资源调用的一个独立单位。是程序的一次动态执行,从加载到执行到执行完毕是一个完整的过程,并且有自己的生命周期。
多线程: 一个程序运行时(进程)产生了不止一个线程,执行的路径有多条,就叫多线程。
2️⃣ 什么是线程调度?Java的线程调度采用什么策略?
答:线程调度:
对处于可运行状态的多个线程对象进行系统级的协调,防止多个线程争用有限资源而导致系统死机或者崩溃
java的线程调度采用的策略:
java 的调度策略是基于线程优先级的抢先式调度。意思就是,谁的优先级高那我就先给谁使用系统资源。
3️⃣ 在Java中wait()和sleep()方法的不同?
答:(1)wait方法是在Object类中,而sleep方法是Thread类中
(2)sleep方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是它的监控状态依然保持,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
4️⃣ 如何在Java程序中实现多线程?
答:1)定义一个类继承自Thread类,重写run方法,然后创建这个类的对象,然后通过对象调用start方法启动线程。
2)定义一个类实现Runnable接口,重写run方法,然后创建一个这个类的子类对象,然后建Thread类的对象,将子类对象作为参数进行传递,然后通过start方法启动线程。
3)线程池,使用ExecutorService、Callable、Future实现有返回结果的多线程。
4)JDK5以后新增了一个Executors工厂类来产生线程池,利用工厂类调用newFixedThreadPool方法,创建一个线程池对象,然后用线程池对象调用submit方法,传入的参数是一个实现了Callable接口的子类,重写了里面的call方法,submit方法相当于start方法,是用于启动线程的。