第十章 多线程

1.程序、进程、线程的区别是什么? 举个现实的例子说明。(网上查资料,跟老师的
不一样)


程序(Program):是一个指令的集合。程序不能独立执行,只有被加载到内存中,系统为它分配资源后才能执行。
进程(Process):如上所述,一个执行中的程序称为进程。
进程是系统分配资源的独立单位,每个进程占有特定的地址空间。
程序是进程的静态文本描述,进程是程序在系统内顺序执行的动态活动。
线程(Thread):是进程的“单一的连续控制流程“。
线程是CPU调度和分配的基本单位,是比进程更小的能独立运行的基本单位,也被称为轻量级的进程。
线程不能独立存在,必须依附于某个进程。一个进程可以包括多个并行的线程,一个线程肯定属于一个进程。Java虚拟机允许应用程序并发地执行多个线程。

举例:如一个车间是一个程序,一个正在进行生产任务的车间是一个进程,车间内每个从事不同工作的工人是一个线程。


2.【上机】Java中通过哪些方式创建多线程类? 分别使用代码说明。并调用之。


(1)自定义线程类继承Thread:
public class MyDefinedThread extends Thread{
//重写run()方法;
public void run(){
//把线程需要执行的任务写在run()方法里;
}

public static void main(String[] args){
MyDefinedThread mdt=new MyDefinedThread();
mdt.start(); //启动线程。
}
}
(2) 自定义类实现Runnable接口;
public class MyRunnable implements Runnable{
//实现run()方法;
public void run(){
//把线程需要执行的任务写在run()方法里;
}

public static void main(String[] args){
MyRunnable mr=new MyRunnable();
Thread th=new Thread(mr);//”mr”并不是一个线程对象,而是要作为参数传递到Thread的构造方法中;“th“才是一个线程对象。
th.start;//启动线程。
}
}


3.Thread类有没有实现Runnable接口?


有实现。


4.当调用一个线程对象的start方法后,线程马上进入运行状态吗?


不是,只是进入就绪(可运行)状态,等待分配CPU时间片。一旦得到CPU时间片,即进入运行状态。

5.下面的代码,实际上有几个线程在运行:


两个:线程t和main()方法(主线程)。
public static void main(String[] argc) throws Exception {
Runnable r = new Thread6();
Thread t = new Thread(r, "Name test");
t.start();
}

6.说说:sleep、yield、join方法的区别。


sleep():在指定时间内让线程暂停执行,进入阻塞状态。
在指定时间到达后进入就绪状态。线程调用sleep()方法时,释放CPU当不释放对象锁(如果持有某个对象的锁的话)。
join(): 当前线程等待调用此方法的线程执行结束再继续执行。如:在main方法中调用t.join(),那main方法在此时进入阻塞状态,一直等t线程执行完,main方法再恢复到就绪状态,准备继续执行。
yield(): 调用该方法的线程暂停一下,回到就绪状态。所以调用该方法的线程很可能进入就绪状态后马上又被执行。


7.为什么不推荐使用stop和destroy方法来结束线程的运行?


stop():此方法可以强行中止一个正在运行或挂起的线程。但stop方法不安全,就像强行切断计算机电源,而不是按正常程序关机。可能会产生不可预料的结果。举例来说:
当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,并抛出特殊的ThreadDeath()异常。这里的“立即”因为太“立即”了,
假如一个线程正在执行:
synchronized void {
x = 3;
y = 4;
}
由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记 线程的stop方法,以后我们再也不要说“停止线程”了。

destroy():该方法最初用于破坏该线程,但不作任何资源释放。它所保持的任何监视器都会保持锁定状态。不过,该方法决不会被实现。即使要实现,它也极有可能以 suspend() 方式被死锁。如果目标线程被破坏时保持一个保护关键系统资源的锁,则任何线程在任何时候都无法再次访问该资源。如果另一个线程曾试图锁定该资源,则会出现死锁。


8.【上机】写个代码说明,终止线程的典型方式。


(1)当run()方法执行完后,线程就自动终止了。
(2)但有些时候run()方法不会结束(如服务器端监听程序),或者其它需要用循环来处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。下面通过例子来说明:

public class ThreadFlag extends Thread 

    public volatile boolean exit = false; 

    public void run() 
    { 
        while (!exit); 
    } 
    public static void main(String[] args) throws Exception 
    { 
        ThreadFlag thread = new ThreadFlag(); 
        thread.start(); 
        sleep(5000); // 主线程延迟5秒 
        thread.exit = true;  // 终止线程thread 
        thread.join(); 
        System.out.println("线程退出!"); 
    } 

在上面代码中定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值。


9.A线程的优先级是10,B线程的优先级是1,那么当进行调度时一定会调用A吗?


不一定。线程优先级对于不同的线程调度器可能有不同的含义,可能并不是用户直观的推测。


10.【上机】模仿老师课堂例子,完成账户取钱的模拟操作代码。


11.synchronize修饰在方法前是什么意思?


一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入.


12.synchronize修饰的语句块,如下面的代码。是表示该代码块运行时必须获得
account对象的锁。如果没有获得,会有什么情况发生?


如果没有获得account对象的锁,就不能执行synchronize修饰的语句块,包括此语句块,还有其他需要获得此account对象锁才能执行的同步代码块和同步方法。
synchronized (account) {
if(account.money-drawingNum<0){
return;
}
}

13.【上机】死锁是怎么造成的?用文字表达。再写一个代码示例。


过多的线程同步会引起死锁。如两个线程都在等待对方释放锁才能继续执行,有时会出现僵持局面:两个线程都持有对方需要的锁,而两个线程都需要对方释放锁了才能继续运行。举例来说:

我们先看看这样一个生活中的例子:在一条河上有一座桥,桥面较窄,只能容纳一辆汽车通过,无法让两辆汽车并行。如果有两辆汽车A和B分别由桥的两端驶上该桥,则对于A车来说,它走过桥面左面的一段路(即占有了桥的一部分资源),要想过桥还须等待B车让出右边的桥面,此时A车不能前进;对于B车来说,它走过桥面右边的一段路(即占有了桥的一部分资源),要想过桥还须等待A车让出左边的桥面,此时B车也不能前进。两边的车都不倒车,结果造成互相等待对方让出桥面,但是谁也不让路,就会无休止地等下去。这种现象就是死锁。死锁是程序运行时出现的一种问题,是需要避免的。

代码示例:
publicclass DeadLock{
Object A=new Object();
Object B=new Object();

publicvoid toEast(){
synchronized(A){
synchronized(B){
System.out.println("向东行驶。");
}
}
}
publicvoid toWest(){
synchronized(B){
synchronized(A){
System.out.println("向西行驶。");
}
}
}
}
publicclass ToEastThread extends Thread {
DeadLock d=new DeadLock();
public ToEastThread(DeadLock d) {
super();
this.d = d;
}
publicvoid run(){
d.toEast();
}
}
publicclass ToWestThread extends Thread {
DeadLock d=new DeadLock();
public ToWestThread(DeadLock d) {
super();
this.d = d;
}
publicvoid run(){
d.toWest();
}
}
publicclass Test1 {
publicstaticvoid main(String[] args) {
DeadLock d=new DeadLock();
ToEastThread et=new ToEastThread(d);
ToWestThread wt=new ToWestThread(d);
et.start();
wt.start();
}
}


14.使用Timer和TimerTask实现定时执行,定时在每天下午17:00执行。

知识点简介:
(1)Timer:定时器,实际上是个线程,定时调度所拥有的TimerTasks。
(2)TimerTask:一个拥有run方法的类,需要定时执行的代码放到run方法体内。 TimerTask一般是以匿名类的方式创建。
语法简介:
java.util.Timer timer = new java.util.Timer(true);   
// true 说明这个timer以daemon方式运行(优先级低,   
// 程序结束timer也自动结束),注意,javax.swing   
// 包中也有一个Timer类,如果import中用到swing包,   
// 要注意名字的冲突。   
TimerTask task = new TimerTask() {   
public void run() {   
... //每次需要执行的代码放到这里面。   
}   
};  
用法简介:
//以下是几种调度task的方法:   
 timer.schedule(task, time);   
// time为Date类型:在指定时间执行一次。   
 timer.schedule(task, firstTime, period);   
// firstTime为Date类型,period为long   
// 从firstTime时刻开始,每隔period毫秒执行一次。   
timer.schedule(task, delay)   
// delay 为long类型:从现在起过delay毫秒执行一次   
timer.schedule(task, delay, period)   
// delay为long,period为long:从现在起过delay毫秒以后,每隔period   
// 毫秒执行一次。  
举例:
import java.util.TimerTask;
publicclass TimePrintTask extends TimerTask {
int i=1;
publicvoid run(){
System.out.println(i);
i++;
}
}
publicclass Test {
publicstaticvoid main(String[] args) {
Timer timer=new Timer();
//timer.schedule(new TimePrintTask(), 1000, 500);
DateFormat df=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SS");
Date firstDate=null;
try {
firstDate=df.parse("2016/12/09 17:00:00 00");
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
timer.schedule(new TimePrintTask(),firstDate , 1000);
}
}

15.wait方法被调用时,所在线程是否会释放所持有的锁资源? sleep方法呢?


wait:释放CPU,释放锁;
sleep:释放CPU,不释放锁。


16.wait、notify、notifyAll是在Object类中定义的方法吗?作用分别是什么?wait(),notify(),notifyAll()不属于Thread类,而是属于Object类,也就是说每个对象都有wait(),notify(),notifyAll()的功能。因为每个对像都有锁,锁是每个对像的基础,而wait(),notify(),notifyAll()都是跟锁有关的方法。

三个方法的作用分别是:
wait:导致当前线程等待,进入阻塞状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。当前线程必须拥有此对象监视器(对象锁)。该线程释放对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行.
notify:唤醒在此对象监视器(对象锁)上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。此方法只应由作为此对象监视器的所有者的线程来调用.
"当前线程必须拥有此对象监视器"与"此方法只应由作为此对象监视器的所有者的线程来调用"说明wait方法与notify方法必须在同步块内执行,即synchronized(obj之内).
notifyAll: 唤醒在此对象监视器(对象锁)上等待的所有线程。


17.notify是唤醒所在对象wait pool中的第一个线程吗?


不是。调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择。

18.【上机】使用线程通信实现生产者-消费者问题。

详见课堂实例。

猜你喜欢

转载自www.cnblogs.com/ren549047861/p/11294158.html