一、 填空题
- 处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入____阻塞_____状态。
- 处于新建状态的线程被启动后,将进入线程队列排队等待CPU,此时它已具备了运行条件,一旦轮到享用CPU资源就可以获得执行机会。上述线程是处于 就绪 状态。
解析: 线程的生命周期—五个阶段
新建状态---- new关键字创建线程对象
就绪状态---- start()方法运行之前
运行状态---- run()方法的运行
堵塞状态--- interrupt() sleep() join() yield() wait() 等方法
死亡状态--- stop()方法(已过时) /或run()方法运行结束
- 一个正在执行的线程可能被人为地中断,让出CPU的使用权,暂时中止自己的执行,进入 阻塞 状态。
解析:人为的中断正在执行的线程---- 执行wait() 、interrupt() 、sleep()方法等
- 在Java中编写实现多线程应用有两种途径:一种是继承Thread类创建线程,另一种是实现 Runnable 接口创建线程。
解析: 继承Thread类来创建线程,不能实现资源的共享,除非资源使用static修饰。
实现Runnable接口创建线程,此时还需要借助Thread类,因为Runnable接口
没有start()方法,同时Thread类是Runnable接口的子类….
- 在线程控制中,可以调用_____join()____方法,阻塞当前正在执行的线程,等插队线程执行完后后再执行阻塞线程。
- 多线程访问某个共享资源可能出现线程安全问题,此时可以使用______synchronized_____关键字来实现线程同步,从而避免安全问题出现,但会影响性能,甚至出现死锁。
- 在线程通信中,调用wait( )可以是当前线程处于等待状态,而为了唤醒一个等待的线程,需要调用的方法是____notify()/notifyAll()__________。
解析:线程等待使用wait()方法,此方法最先是存在于Object类,Thread类继承Object类,可以获取此方法,同时notify()方法和notifyAll()方法---线程唤醒,最先也是存在于
Object类当中。
- 在线程通信中,可以调用wait()、notify()、notifyAll()三个方法实现线程通信,这三个方法都是____Object____类提供的public方法,所以任何类都具有这三个方法。
二、 选择题
1. |
下列关于Java线程的说法正确的是( A )。(选择一项) |
|
|
|
|
|
A |
每一个Java线程可以看成由代码、一个真实的CPU以及数据三部分组成 |
|
B. |
创建线程的两种方法中,从Thread类中继承方式可以防止出现多父类的问题 |
|
C. |
Thread类属于java.util程序包 |
|
D. |
使用new Thread(new X()).run();方法启动一个线程 解析: A、语句正确。 B、创建线程两种方法:一是继承Thread类,一是实现Runnable接口 根据Java语言类的单继承特性,防止出现多继承,但是可以实现多 重继承,也可以出现多父类。 C、Thread类是存在于java.lang包下 D、启动一个线程是调用start()方法,之后JVM会默认的调用run()方法 run()方法是线程的主体,核心代码放到此方法当中。 |
2. |
以下选项中可以填写到横线处,让代码正确编译和运行的是( AD )。(选择一项) |
|
|
public class Test implements Runnable { public static void main(String[] args) { ___________________________________ t.start(); System.out.println("main"); } public void run() { System.out.println("thread1!"); } } |
|
|
|
|
|
A. |
Thread t = new Thread(new Test()); |
|
B. |
Test t = new Test(); |
|
C. |
Thread t = new Test(); |
|
D. |
Thread t = new Thread(); 解析: 线程的创建有两种方式,其中之一就是实现Runnable接口。 此接口只有一个抽象方法---run()方法,启动线程又需要start()方法 此时还需要借助Thread类,根据Thread类的构造方法: Public Thread(Runnable run) 需要传入Runnable接口对象 此题还有一个方法: 直接创建Thread类对象,调用start()方法 所以D选项也是正确的,但是此题重点还是考的子类实现Runnable接口 根据Thread类的构造方法启动线程。 |
3. |
如下代码创建一个新线程并启动线程,问:四个选项中可以保证正确代码创建target对象,并能编译正确的是( C )?(选择一项) |
|
|
public static void main(String[] args) { Runnable target=new MyRunnable( ); Thread myThread=new Thread(target); } |
|
|
|
|
|
A |
public class MyRunnable extends Runnable { public void run( ) { } } |
|
B. |
public class MyRunnable extends Runnable { void run( ) { } } |
|
C. |
public class MyRunnable implements Runnable { public void run( ) { } } |
|
D. |
public class MyRunnable implements Runnable { void run( ) { } 解析:接口是用来实现的,不是继承,所以AB两个选项错误。 根据Java多态机制,子类可以为父类(接口)实例化对象 根据子类实现接口的规则,子类必须重写接口当中的抽象方法 此时又复习到了方法重写的规则:子类重写的方法它的权限必须 大于等于父类方法权限(除去private外)。根据以上得出C是对的
|
4. |
当线程调用start( )后,其所处状态为( C )。(选择一项) |
|
|
|
|
|
A |
阻塞状态 |
|
B. |
运行状态 |
|
C. |
就绪状态 |
|
D. |
新建状态 解析:线程的五个状态也就是它的生命周期: 新建状态----通过new关键字来创建线程对象 就绪状态----调用start()方法 运行状态----调用run()方法 阻塞状态----调用sleep()、wait()、join() 、yield()、interrupt ()等方法 消亡状态----调用stop()方法,但是此方法已经过时。
|
5. |
下列关于Thread类提供的线程控制方法的说法中,错误的是( C )。(选择一项) |
|
|
|
|
|
A |
线程A中执行线程B的join()方法,则线程A等待直到B执行完成 |
|
B. |
线程A通过调用interrupt()方法来中断其阻塞状态 |
|
C. |
若线程A调用方法isAlive()返回值为false,则说明A正在执行中,也可能是可运行状态 |
|
D. |
currentThread()方法返回当前线程的引用 解析: A: join()方法—强制加入,加入的线程执行完毕之后才能执行其他线程 B:interrupt()方法---线程中断,题中的说法有些牵强 C:isAlive()方法---判断一个线程是否在活动,如果在活动返回真,反之假 D:currentThread()方法返回正在执行的线程 从以上得知,C明显错误。 |
6. |
下列关于线程的优先级说法中,正确的是( BD )。(选择两项) |
|
|
|
|
|
A |
线程的优先级是不能改变的 |
|
B. |
线程的优先级是在创建线程时设置的 |
|
C. |
在创建线程后的任何时候都可以重新设置 |
|
D. |
线程的优先级的范围在1-10之间 |
解析:线程的优先级可以通过setPriority(int newPriority)
的方法进行设置
线程一共有三个优先级,分别是:最低优先级(1) 中等优先级(5)
最高优先级(10),咱们经常写的main方法就是中等优先级线程。
A:线程的优先级是可以更改也可以获取,但是有一点,即使设置为最高
优先级也不一定先执行,只是它优先执行的几率比较高。
B:线程的优先级是在创建时进行设置,通过setPriority()
方法设置
C:正在执行的线程是不允许重新设置线程优先级的。
D:线程的优先级范围是1—10 符合要求
7. |
以下选项中关于Java中线程控制方法的说法正确的是( AD )。(选择二项) |
|
|
|
|
|
A. |
join ( ) 的作用是阻塞指定线程等到另一个线程完成以后再继续执行 |
|
B. |
sleep ( ) 的作用是让当前正在执行线程暂停,线程将转入就绪状态 |
|
C. |
yield ( ) 的作用是使线程停止运行一段时间,将处于阻塞状态 |
|
D. |
setDaemon( )的作用是将指定的线程设置成后台线程 解析: A:join()方法—线程的强制加入,加入的线程执行完毕之后再执行其他线程 B:sleep()方法—线程休眠,等时间过时,线程处于运行状态 C:yield()方法—线程礼让,让出CUP资源,其他线程先执行,有些与join() 方法类似 D:setDaemon()方法--将指定的线程设置成后台线程,对的。 题的答案是:AD,但是个人感觉C选项也是对的…… |
8. |
在多个线程访问同一个资源时,可以使用( A )关键字来实现线程同步,保证对资源安全访问。(选择一项) |
|
|
|
|
|
A. |
Synchronized |
|
B. |
Transient |
|
C. |
Static |
|
D. |
Yield |
解析:关于同步,有两种实现方式,一种是同步方法,一种是同步代码块
无论怎样,都需要使用到synchronized关键字。
同步方法:
权限修饰符 synchronized 返回值类型 方法名称(参数列表){
N行代码;
}
同步代码块:
Synchronized(对象){
N行代码;
}
9. |
Java中线程安全问题是通过关键字( C )解决的?。(选择一项) |
|
|
|
|
|
A. |
Finally |
|
B. |
wait( ) |
|
C. |
Synchronized |
|
D. |
notify( ) |
10. |
以下说法中关于线程通信的说法错误的是( D )?。(选择一项) |
|
|
|
|
|
A. |
可以调用wait()、notify()、notifyAll()三个方法实现线程通信 |
|
B. |
wait()、notify()、notifyAll()必须在synchronized方法或者代码块中使用 |
|
C. |
wait()有多个重载的方法,可以指定等待的时间 |
|
D. |
wait()、notify()、notifyAll()是Object类提供的方法,子类可以重写 解析: A选项: 在线程通信中,可以调用wait()、notify()、notifyAll()三个方法实现线程通信,这三个方法都是Object类提供的public方法,所以任何类都具有这三个方法。 B选项:在编程题的第二题当中,wait()方法、notify()方法、notifyAll()方法都是写在同步方法当中,具体可以查看此类的源码。 C选项:Object类当中的wait方法的重载如下: public final void wait() public final void wait(long timeout) public final void wait(long timeout, int nanos) 三个方法都抛出异常-- InterruptedException D选项 : 看A选项的解释 以上三个方法都使用final修饰,所以子类是不能重写的 |
三、 判断题
- 进程是线程Thread内部的一个执行单元,它是程序中一个单一顺序控制流程。( × )
解析:线程是进程的一个执行单位,进程包含线程。
- Thread类实现了Runnable接口。( √ )
解析:Thread类在JDK当中的定义如下:
Public class Thread extends Object implements Runnable
从定义可知,它是Runnable接口的子类。
- 一个进程可以包括多个线程。两者的一个主要区别是:线程是资源分配的单位,而进程CPU调度和执行的单位。( × )
解析:不管是进程还是线程,都是通过循环获得自己执行的时间片,获得CUP资源。
- 用new关键字建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start进入就绪状态。( √ )
解析:具体解析看选择题第四题的解释。
- A线程的优先级是10,B线程的优先级是1,那么当进行调度时一定会先调用A( × )
解析:线程的执行,不是谁的优先级高就先执行,只是它的概率会高。
- 线程可以用yield使低优先级的线程运行。( × )
解析:yield()方法是线程礼让,可以让出自己执行其他线程,由于线程的执行存在严重
的随机性,不能确定使低优先级的线程执行。
- Thread.sleep( )方法调用后,当等待时间未到,该线程所处状态为阻塞状态。当等待时间已到,该线程所处状态为运行状态。( √ )
解析:sleep()方法是线程休眠,处于阻塞状态,当时间一到,就会执行处于运行状态。
如果真要追究用词的严谨性,应该不是”等待时间”,而是”休眠时间”。
- 当一个线程进入一个对象的一个synchronized方法后,其它线程不可以再进入该对象同步的其它方法执行。( √ )
解析:对的,可以查看一下编程题第二题的源码。
- wait方法被调用时,所在线程是会释放所持有的锁资源, sleep方法不会释放。( √ )
解析:sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复,所以调用sleep 不会释放对象锁。
wait 是Object 类的方法,对此对象调用wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
- wait、notify、notifyAll是在Object类中定义的方法。( √ )
解析:具体可以查看API帮助文档,查看。
- notify是唤醒所在对象wait pool中的第一个线程。( × )
解析:唤醒的不一定是第一个线程。
JDK已经明确说明唤醒哪个thread是随意决定的,没有特定顺序
The choice is arbitrary and occurs at the discretion of the implementation
四、 简答题
- 简述程序、进程和线程的联系和区别。
- 创建线程的两种方式分别是什么?各有什么优缺点。
- sleep、yield、join方法的区别?
- synchronize修饰的语句块,如下面的代码。是表示该代码块运行时必须获得account对象的锁。如果没有获得,会有什么情况发生?
synchronized (account) { if(account.money-drawingNum<0){ return; } } |
- 请你简述sleep( )和wait( )有什么区别?
- 死锁是怎么造成的?用文字表达。再写一个代码示例。
- Java中实现线程通信的三个方法及其作用。
- 为什么不推荐使用stop和destroy方法来结束线程的运行?
五、 编码题
1.设计一个多线程的程序如下:设计一个火车售票模拟程序。假如火车站要有100张火车票要卖出,现在有5个售票点同时售票,用5个线程模拟这5个售票点的售票情况。
1 public class CharPrint extends Thread 2 { 3 // 定义属性
4 private Printer p; 5 //构造方法
6 public CharPrint(Printer p) 7 { 8 super(); 9 this.p = p; 10 } 11 //重写run()方法
12 public void run() 13 { 14 char c = 'A';//定义起始位置 15 //通过for循环对其进行输出
16 while (c <= 'Z') 17 { 18 p.print(c); 19 c++;//控制while循环当中的条件表达式
20 } 21 } 22 } 23 public class NumberPrint extends Thread 24 { 25 //定义属性
26 private Printer p; 27 //构造方法
28 public NumberPrint(Printer p) 29 { 30 this.p = p; 31 } 32 //重写run()方法
33 public void run() 34 { 35 int i = 1;//定义起始位置
36 while (i <= 52) 37 { 38 p.print(i); 39 i++;//控制while循环当中的条件表达式
40 } 41 } 42 } 43 public class Printer 44 { 45 //定义一个int类型变量---用于计数
46 int index = 1; 47 //同步的方法---print()
48 public synchronized void print(int i) 49 { 50 //判断是否是3的倍数
51 while (index % 3 == 0) 52 { 53 try
54 { 55 wait();//线程等待
56 } catch (InterruptedException e) 57 { 58 // TODO Auto-generated catch block
59 e.printStackTrace(); 60 } 61 } 62 //打印输出数字
63 System.out.print("" + i); 64 //控制while循环中的条件表达式
65 index++; 66 //线程唤醒
67 notifyAll(); 68 } 69 public synchronized void print(char c) 70 { 71 //判断是否是3的倍数
72 while (index % 3 != 0) 73 { 74 try
75 { 76 wait(); 77 } catch (InterruptedException e) 78 { 79 // TODO Auto-generated catch block
80 e.printStackTrace(); 81 } 82 } 83 //打印字母
84 System.out.print("" + c); 85 //控制while循环中的条件表达式
86 index++; 87 //唤醒线程
88 notifyAll(); 89 } 90 } 91 public class Test 92 { 93 public static void main(String[] args) 94 { 95 //创建Printer对象,为NumberPrint()类构造方法做准备
96 Printer p = new Printer(); 97 //创建线程---子类为父类实例化对象
98 Thread t1 = new NumberPrint(p); 99 Thread t2 = new CharPrint(p); 100 //启动线程
101 t1.start(); 102 t2.start(); 103 } 104 }
2.编写两个线程,一个线程打印1-52的整数,另一个线程打印字母A-Z。打印顺序为12A34B56C….5152Z。即按照整数和字母的顺序从小到大打印,并且每打印两个整数后,打印一个字母,交替循环打印,直到打印到整数52和字母Z结束。
要求:
1) 编写打印类Printer,声明私有属性index,初始值为1,用来表示是第几次打印。
2) 在打印类Printer中编写打印数字的方法print(int i),3的倍数就使用wait()方法等待,否则就输出i,使用notifyAll()进行唤醒其它线程。
3) 在打印类Printer中编写打印字母的方法print(char c),不是3的倍数就等待,否则就打印输出字母c,使用notifyAll()进行唤醒其它线程。
4) 编写打印数字的线程NumberPrinter继承Thread类,声明私有属性private Printer p;在构造方法中进行赋值,实现父类的run方法,调用Printer类中的输出数字的方法。
5) 编写打印字母的线程LetterPrinter继承Thread类,声明私有属性private Printer p;在构造方法中进行赋值,实现父类的run方法,调用Printer类中的输出字母的方法。
6) 编写测试类Test,创建打印类对象,创建两个线程类对象,启动线程。
1 public class TicketSalSystem implements Runnable 2 { 3 // 定义变量---票数/票号
4 public int ticket = 100; 5 public int count = 0; 6 // 重写run()方法
7 public void run() 8 { 9 // 定义while循环, 循环售票
10 while (ticket > 0) 11 { 12 // 根据题的要求,实现同步,此时定义同步代码块
13 synchronized (this) 14 {// 传入对象,使用this代替当前类对象 15 // 判断是否还有票,如果大于零说明还有票可卖
16 if (ticket > 0) 17 { 18 // 线程休眠0.5秒
19 try
20 { 21 Thread.sleep(500); 22 } catch (InterruptedException e) 23 { 24 e.printStackTrace(); 25 } 26 count++; // 票号++
27 ticket--;// 循环售票,卖一张少一张 28 // 输出当前的售票窗口和票号
29 System.out.println(Thread.currentThread().getName() 30 + "\t当前票号:" + count); 31 } 32 } 33 } 34 } 35 } 36 public class TicketTest 37 { 38 public static void main(String[] args) 39 { 40 // 创建线程类对象
41 TicketSalSystem st = new TicketSalSystem(); 42 // 启动5次线程
43 for (char i = 'A'; i <= 'F'; i++) 44 { 45 /*
46 * 创建匿名Thread类对象 47 * 1、根据Thread类构造方法,传入Runnable接口对象 48 * TicketSalSystem类是Runnable接口的子类,子类对象st可以让父类接收 49 * 2、Thread类的构造方法如下: 50 * public Thread(Runnable run,String name) 51 * 在创建线程的同时,为线程名称 52 * 3、启动线程是使用start方法,启动之后JVM会默认的区调用run()方法 53 */
54 new Thread(st, "售票口" + i).start(); 55 } 56 } 57 }
六、 可选题
1.设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。
要求:使用内部类实现线程,对j增减的时候不考虑顺序问题。
1 public class ThreadTest61 2 { 3 //声明成员变量j
4 private int j; 5 public static void main(String[] args) 6 { 7 //创建ThreadTest61的对象
8 ThreadTest61 tt = new ThreadTest61(); 9 //创建内部线程类对象
10 Inc inc = tt.new Inc(); 11 Dec dec = tt.new Dec(); 12 for (int i = 0; i < 2; i++) 13 { 14 // 创建线程对象,启动线程
15 Thread t = new Thread(inc); 16 t.start(); 17 // 创建线程对象,启动线程
18 t = new Thread(dec); 19 t.start(); 20 } 21 } 22 //实现同步对j的值加
23 private synchronized void inc() 24 { 25 //调用此方法一次,对j就增加一次
26 j++; 27 System.out.println(Thread.currentThread().getName() + "-inc:" + j); 28 } 29 //实现同步对j的值减
30 private synchronized void dec() 31 { 32 //调用此方法一次,对j就减一次
33 j--; 34 System.out.println(Thread.currentThread().getName() + "-dec:" + j); 35 } 36 //内部类实现Runnable接口,重写run()方法
37 class Inc implements Runnable 38 { 39 public void run() 40 { 41 //for循环当中调用inc()方法,实现每次对j加1
42 for (int i = 0; i < 100; i++) 43 { 44 //调用加的方法
45 inc(); 46 } 47 } 48 } 49 //内部类实现Runnable接口,重写run()方法
50 class Dec implements Runnable 51 { 52 public void run() 53 { 54 //for循环当中调用inc()方法,实现每次对j减1
55 for (int i = 0; i < 100; i++) 56 { 57 //调用减的方法
58 dec(); 59 } 60 } 61 } 62 }
2.编写多线程程序,模拟多个人通过一个山洞的模拟。这个山洞每次只能通过一个人,每个人通过山洞的时间为5秒,有10个人同时准备过此山洞,显示每次通过山洞人的姓名和顺序。
1 public class ThreadTest62 2 { 3 public static void main(String[] args) 4 { 5 /*创建一个山洞对象,为Thread类构造方法做准备 6 * public Thread(Runnable run,String name) 7 */
8 Tunnel tul = new Tunnel(); 9 //创建十个过山洞线程,并为线程命名
10 Thread p1 = new Thread(tul, "p1"); 11 Thread p2 = new Thread(tul, "p2"); 12 Thread p3 = new Thread(tul, "p3"); 13 Thread p4 = new Thread(tul, "p4"); 14 Thread p5 = new Thread(tul, "p5"); 15 Thread p6 = new Thread(tul, "p6"); 16 Thread p7 = new Thread(tul, "p7"); 17 Thread p8 = new Thread(tul, "p8"); 18 Thread p9 = new Thread(tul, "p9"); 19 Thread p10 = new Thread(tul, "p10"); 20 //启动十个线程
21 p1.start(); 22 p2.start(); 23 p3.start(); 24 p4.start(); 25 p5.start(); 26 p6.start(); 27 p7.start(); 28 p8.start(); 29 p9.start(); 30 p10.start(); 31 } 32 } 33 //创建山洞类并实现Runnable接口,重写run()方法
34 class Tunnel implements Runnable 35 { 36 //定义变量,初始化过山洞人数
37 int crossedNum = 0; 38 //重写run()方法,在此方法当中调用cross()方法
39 public void run() 40 { 41 cross(); 42 } 43 //定义过山洞的同步方法
44 public synchronized void cross() 45 { 46 //每个人通过山洞的时间为5秒
47 try
48 { 49 Thread.sleep(5000); 50 } catch (InterruptedException e) 51 { 52 e.printStackTrace(); 53 } 54 //人数统计,过一个人增加一个人
55 crossedNum++; 56 //显示每次通过山洞人的姓名
57 System.out.println(Thread.currentThread().getName() 58 + "通过了山洞,这是第" + crossedNum + "个用户"); 59 } 60 }