欢迎您的光临,本博所发布之文章皆为作者亲测通过,如有错误,欢迎通过各种方式指正。

教程  Java语言学习—第十二章 线程[Java基础]

Java 原创 1123 0评论

内容概要:

1.Runtime类和Process类

2.创建线程 

3.多线程

4.线程的优先级 

5.线程的休眠与唤醒 

6.线程让步 

7.线程同步 

8.stop()suspend()resume()


我们将正在执行的程序成为进程(progress),现在计算机的运行平台大部分都支持多进程体制。实际上,在只有一块CPU的电脑上,无法同时运行多个进程,CPU在交替轮流执行多个程序,看上去就像多个进程在同时执行。


在一个进程内部也可以同时运行多个任务,我们将在一个进程内部运行的每个任务都称为一个线程(thread)。虽然看上去这多个线程在同时运行,实际上,在某个时间上,CPU运行的线程也只有一个。

线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存数据,以及一个供程序执行时使用的堆栈。所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小得多,所以,线程也被称为轻负荷进程。一个进程在执行过程中可以产生多个线程,形成多条执行线索。每个线索,即每个线程也有它自身的产生、存在和消亡的过程。


线程与进程最重要的区别在于在一个应用程序中,有多个线程可以同时执行,但系统并没有将多个线程看做多个独立的应用来实现进程的调度和管理以及分配资源。

线程的应用:

(1)游戏程序设计:移动游戏中的出场角色的线程;多个角色同时移动

(2)网络程序设计:负责程序流的线程;等待来自网路数据的线程

 

1.Runtime类与Process类

在Java中,有一个叫做runtime object的对象。这个对象提供JVM启动系统接口,同时负责运行平台的基本程序(非Java类),也提供有关的平台信息。

如果调用Runtime类的静态方法getRuntime()方法,则可以获取runtime object对象,代码如下:

Runtime rt=Runtime.getRuntime();

runtime object 对象是一个当Java程序运行时产生的对象,所以我们无法使用new关键字创建它,只能调用Runtime类的getRuntime()方法来获取它。

Runtime类的常用方法:

public static Runtime getRuntime()—获取对runtime object对象的引用。

public Process exec(String command) throws IOException—执行指定的命令,并返回执行的进程的引用。

public void exit(int status)—终止JVM的执行。

public native void gc();—运行garbage collector,增加可用内存。

public native long freeMemory();—返回JVM可用的内存大小(bytes)。

public native long maxMemory();—返回JVM可用的最大内存。

public native long totalMemory();—返回JVM正在使用的全部内存。


下面是一个使用了runtime object对象,执行Windows的记事本程序,并输出内存的大小。Runtime类与Process类都存在于java.lang包中,我们无需另外导入它们。

public Class RunAndPro1{
public static void main(String[] args)throws Exception{
Runtime rt=Runtime.getRuntime();.......//x1,获取runtime object对象,rt为该对象的引用。
Process pr=rt.exec("c:\\windows\\notepad.exe");......//x2,使用rt启动Windows的记事本程序,随后输出内存的状态
System.out.println("最大内存:"+rt.maxMemory()+"bytes");
System.out.println("总内存:"+rt.totalMemory()+"bytes");
System.out.println("空闲内存:"+rt.freeMemory()+"bytes");
}}

Process类是一个跟当前执行的进程相关的类,Process类的常用方法如下:

abstract public void destroy();—强制终止进程。

abstract public InputStream getInputStream();—获取进程的InputStream。

abstract public OutputStream getOutputStream();—获取进程的OutputStream。

abstract public int waitFor() throws InterruptedException;—等待进程终止,进程终止,返回终止值。如果终止值为0,则表明进程正常终止。


下面实例会执行记事本程序,并在程序执行完毕后输出终止值。

public Class RunAndPro2{
public static void main(String[] args)throws Exception{
Runtime rt=Runtime.getRuntime();
System.out.println(“运行记事本程序。”);
Process pr=rt.exec("c:\\windows\\notepad.exe");
//等待进程终止
int exitValue=pr.waitFor();
System.out.println(“记事本程序终止。”);//关闭记事本程序时,执行下面命令。
System.out.println(“终止值:”+exitValue);

 

2.创建线程

用Runnable接口创建线程:

在Java中,线程也是一种对象,但并非任何对象都可以称为线程,只有实现了Runnable接口的类的对象才能成为线程,即实现Runnable接口是对象转变为线程的必要条件。

public interface Runnable{
public abstract void run(){
}

//正如你看到的,Runnable接口是一个非常简单的接口,它仅包含一个抽象的run()方法。但要创建线程必须实现此接口。


创建线程的方法:Thread t=new Thread(runnable对象);//runnable对象是指实现了Running接口的对象,线程执行时,runnable对象的run()方法会被调用执行。

如果想运行上面创建的线程t,还需要调用Thread的start()方法来启动它:t.start();

创建线程并启动它:

class MyRunnable implements Runnable{............//实现了Runnable接口的类
String name;
MyRunnable(String name){.........................//构造函数
this.name=name;
}
public void run(){...............................//x2,实现Runnable接口的唯一抽象run()方法
for(int i=1;i<=10;i++)
System.out.println(name+":"+i);
}}
public class Thread{
public static void main(String[] args) {
MyRunnable myr=new MyRunnable("myrunnable");...............//创建MyRunnable类的对象myr
Thread t=new Thread(myr);..................................//使用对象myr创建一个线程t
t.start();.................................................//启动创建的线程t
}
}

//x2行的run()方法执行完毕后,线程也就执行完毕,即线程死亡了。线程处于活动状态中,称为线程存活(alive),若想知道线程的状态,请调用它的isAlive()方法。

public final native boolean isAlive();—如果线程处于活动状态中,返回true,否则返回false.

t.isAlive() //死亡了?存活着。


创建线程的另一种方法:Thread类本身也实现了Runnable接口。所以我们可以构造一个类,让它继承Thread类,并覆盖继承而来的run()方法,通过创建这个类的对象而创建一个线程。

请看实例:

class MyThread extends Thread{.........................//线程类
public void run(){.....................................//覆盖run()方法
for(int i=10;i<=10;i++)
System.out.println(super.getName()+":"+i);.............//x1,super.getName()返回线程的名称,线程的名称形式:Thread-号码。
}}
public class Thread{
public static void main(String[] args) {
MyThread mt=new MyThread();
mt.start();
}

上面的方法虽然比用Runnable对象的方法更方便创建线程,但如果你的类继承了Thread类,就无法再继承其他的类了。

public final void join() throws InterruptedException—无限期等待线程死亡

public final synchronized void join(long millis)—等待线程死亡,可以使用millis指定等待时间(以毫秒为单位)

public final synchronized void join(long millis,int nanos)—等待线程死亡,可以给出millis等待的时间和nanos附加时间。

使用join方法等待至线程死亡。

public class Thread{
public static void main(String[] args){
SumThread st=new SumThread(1,1000);
st.start();
try{
st.join();................//x1,等待至线程死亡,等待的是main线程,main一直处于待机状态,直到st线程死亡,才再次苏醒过来
}catch(InterruptedException ie){}
System.out.println(st.getSum());//x2,输出线程st求得的和
}}

注意:join()方法可能抛出InterruptedException异常。


Thread类

方法描述
String getName()获得线程的名称
int getPriority()获得线程优先级
boolean isAlive()判断线程是否仍在运行
void join()等待一个线程终止
void run()线程的入口点
void sleep()在一段时间内挂起线程
void start()通过调用运行方法来启动线程

线程生命的控制:

当一个线程创建之后,就开始了线程的生命周期过程,在线程的整个生命周期中有不同的状态。

(1)新建状态(New Thread):当线程已经被应用程序用构造方法创建之后但是还没有调用start方法启动,就处于这种状态。

(2)执行状态(Runnable):当线程对象(Thread的子类或者使用Runnable接口创建的对象)被调用了线程的start方法之后,线程就可以执行。线程执行由JVM控制,并且和底层的系统平台有关,任何时候都不能保证线程一定能或者不能执行。

(3)堵塞状态(Block):当线程没有分配CPU的执行时间,无法做到执行时就处于这个状态,线程被堵塞可能是由下述五个方面原因造成的。

1) 调用sleep(毫秒数),是线程进入“睡眠”状态。在规定的时间内,这个线程是不会运行的。

2) 调用suspend()暂停了线程的执行。除非线程收到resume()消息,否则不会返回“可运行”状态。

3) 调用wait()暂停了线程的执行。除非线程收到notify()或notifyAll()消息,否则不会变成“可执行”状态。

4) 线程正在等候一些IO(输入输出)操作完成。

5) 线程试图调用另一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。也可以调用yield()(Thread类的一个方法)自动放弃CPU,以便其他线程能够运行。

(4)死亡状态(Dead):正常情况run方法的正常或者异常终止会使线程死亡。当对线程对象调用stop()或destroy()方法也可以获得同样的效果,但是不推荐,前者会引发异常,后者会强制终止线程,不会十分线程所占用的对象资源锁。


线程的执行流程:

1_101222023326_1.jpg


3.多线程

如果我们同时启动多个线程,就会在CPU上按照启动的顺在给定的时间内轮流执行。

class MyThread extends Thread{
public void run(){
for(int i=1;i<=10;i++)
System.out.println(super.getName()+":"+i);
}}
public class Thread2{
public static void main(String[] args) {
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
mt1.start();....................//x1,Thread-1
mt2.start();....................//x2,Thread-2
}}
Thread-2:1
Thread-1:1
Thread-2:2
Thread-1:2
..........
Thread-2:10
Thread-1:10

//x1与x2分别启动了两个线程,线程mt1与线程mt2方法会轮换执行。当多次运行上面的程序发型结果是不行同的,因为线程的执行时间与顺序由Thread Scheduler负责管理。


下列实例求1—1000之和,分别用两个线程,一个用于求1—500之和,另一个用于求501—1000之和。

public static void main(String[] args) {
MyThread mt1=new MyThread(1,500);..................//求1-500的和
MyThread mt2=new MyThread(501,1000);...............//求501-1000的和
mt1.start();....................//x1,启动线程mt1
mt2.start();....................//x2,启动线程mt2
try{
mt1.join();....................//x1,等待线程mt1执行完毕
mt2.join();....................//x2,等待线程mt2执行完毕
}catch(InterruptedException ie){}
long sum=mt1.getSum()+mt2.getSum();
System.out.println("1-1000之间的和:"+sum);
}}

使用多线程机制将任务分解并不意味着程序的执行速度会比使用单个线程完成相同的任务块。

线程的状态:

Startable处于该状态的线程可以通过调用start()方法,进入就绪状态。

Runnable:Thread Scheduler已经为其分配了执行顺序,但尚未分配CPU,一旦获得了CPU资源,线程就进入运行状态。

Not Runnable暂时处于等待状态(未获得运行顺序的状态)。

Deadrun()方法执行完毕或强制终止线程执行时所处的状态。


4.线程的优先级

优先级(priority)高的线程通常会比优先级低的线程获得更多的执行时间。如果想让某个线程执行的时间比其他线程长一些,只需将其优先级设高一些就行了。对应优先级相同的线程,JVM在处理它们时时随机的。一个线程创建之后,可以通过在线程中调用setPriority()方法来设置它的优先级,调用getPriority()来获取优先级。

public final void setPriority(int newPriority)

newPriority是一个在1—10之间的正整数,数值越大,表面线程的优先级越高。


在Thread类中,与线程优先级有关的常量数声明如下:

public final static int MIN_PRIORTY=1; //最低优先级 
public final static int NORM_PRIORTY=5; //缺省优先级 
public final static int MAX_PRIORTY=10; //最高优先级
class MyThread extends Thread{
public void run(){
for(int i=1;i<=10;i++)
System.out.println(super.getName()+":"+i);
} 
public static void main(String[] args) {
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
mt1.setPriorty(Thread.MIN_PRIORITY);...........//设置最低优先级 
mt2.setPriorty(Thread.MAX_PRIORITY);...........//设置最高优先级
mt1.start();....................//x1,Thread-1
mt2.start();....................//x2,Thread-2
}}

如果你不给新创建的线程设置优先级,那么它拥有缺省的优先级(NORM_PRIORITY)

 

5.线程的休眠与唤醒

线程休眠指线程暂时处于等待的一种状态。调用Thread类的sleep()方法可以使线程在指定的时间内处于休眠状态。线程在休眠结束后,会重新执行未尽的任务。

public static native void sleep(long millis) throws InterruptedException;—使当前正在执行的线程休眠millis毫秒。

public static void sleep(long millis,int nanos) throws InterruptedException;—使当前正在执行的线程休眠millis毫秒和nanos毫秒。

public class Thread3 extends Thread{
public void run(){
for(int i=1;i<=10;i++){
System.out.println(getName()+":"+i);.............//x1
try{
sleep(1000);.....................................//等待1秒钟,每隔1秒钟,执行x1行的语句
}catch(InterruptedException ie){}
}}
public static void main(String[] args) {
Thread3 t1=new Thread3();
t1.start();
}}

线程的唤醒:

线程的interrupt()方法会唤醒线程处于休眠状态中的线程。唤醒是指使线程从休眠状态进入可执行状态。

public void interrupt(); //使线程从休眠状态进入就绪状态。

调用sleep()方法与join()方法使线程进入休眠等待状态,而在调用线程的interrupt()方法,那么在sleep()方法与join()方法中就会抛出InterruptedException异常。

public class Thread4 extends Thread{
public void run(){
System.out.println("休眠,勿扰");
try{
sleep(1000000);............................//x1,线程休眠
}catch(InterruptedException ie){..........................//x2
System.out.println("啊,谁叫醒我的");..........................//x3
}}
public static void main(String[] args) {
Thread4 t=new Thread4();
t.start();..........................//x4
t.interrput();..........................//x5,唤醒线程
}}

//x4创建了t的线程,在x1处使它长时间休眠。在x5处又唤醒它,所以受到唤醒的线程t在x1行的sleep方法中会抛出InterruptedException异常。


线程的等待与唤醒

wait()方法使线程进入等待状态,而notify()方法用于通知处于等待中的线程。特别指出:wait()方法与notify()方法并非Thread类的方法,而是Object类的成员方法。因为不论是“等待”还是“通知”,都需要使用对象monitor。


下面是调用wait()方法代码:

synchronized(key){.....................//获得了key的monitor并开始运行同步块中的代码,x2中的条件满足了,可以的monitor才能被返还。因为对象的monitor已经被返还,所以线程在x2行处于等待状态。

............

try{

if(条件)key.wait();....................//x2

}catch(InterruptedException ie){}

....................

}

wait()方法只有在同步块或同步方法中,才能被调用。因为必须首先获取对象monitor,才能释放它。

notify()方法拥有通知处于等待的线程,将其唤醒。下面是调用notify()的方法

synchronized(key){....................//x1

................

key.notify();..........................//x2

...............

}

与wait()方法一样,notify()方法也仅在同步块中使用。在x1行的获取了key的monitor而执行同步方法,当执行到x2处,notify()会通知调用了wait()方法二处于等待的线程,将其唤醒。

加入调用wait()方法而处于等待的线程有多个,那么调用notify()方法会唤醒哪一个呢,事实上,处于等待中的线程都有可能被唤醒。当然也可能出现某个线程一次也没有被唤醒。调用notifyAll()方法可以将所有的线程都唤醒

 

6.线程让步

调用线程类的yield()方法,会使当前正在执行的线程对象退出运行状态,使得其他的线程得以运行,这通常称为线程让步

public static native void yield();—向其他线程过度运行权

调用线程类的yield()方法并不能将运行权过度给指定的线程,处于存活状态中的线程都有机会获得这项权利。

public class MyThread extends Thread{
public void run(){
for(int i=1;i<=10;i++)
System.out.println(super.getName()+":"+i);
yield();...................................//线程让步
try{
sleep(100);
}catch(InterruptedException ie){} 
}} 
public static void main(String[] args) {
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
mt1.start();
mt2.start();
}}

 

7.线程同步

Java语言支持多个线程,并具有并发能力。

只有获得共享资源权限的线程才能执行某种任务的情况,我们称之为线程的同步化(synchronization)。

例:如果同时启动两个线程共用一台打印机打印内容,会使输出结果变得混乱。

public class Synchronization1{
public static void main(String[] args) {
PrintThread pt1=new PrintThread('A');
PrintThread pt2=new PrintThread('B');
pt1.start();
pt2.start();
}}

//结果:AAAAAAAAA......BABABABABABABABA.......BABABABABABABBA......BBBBBBBBBB

使用线程同步化可以解决上面的打印混乱问题。


Java语言的同步化方式:同步块(synchronized block)与同步方法(synchronized method)

同步块:同步块是使具有某个对象的monitor的线程获取运行权限的一种方法。monitor是所有Java对象都具有的同步保障对象,将其想象成对象的权限即可。

同步块的形式如下:

synchronized(someObject){

代码段

}

执行同步块内代码的线程必须是获取了someObject的monitor的线程。

下面是修改上面混乱的打印结果,实现线程同步化。

class PrintThread extends Thread{
char ch;
static Object printer=new Object();...........................//x1
public PrintThread2(char ch){
this.ch=ch;
}
void printCh10(){
for(int i=1;i<=10;i++)......................//输出字符ch10次
System.out.print(ch);......................//不打印换行符
}
public void run(){.......................................//x2
synchronized(printer){................................//x3,同步块
for(int i=1;i<=5;i++){
printCh10();
System.out.println();...................................//x4,同步块结束
}}}}
public class Synchronization1{
public static void main(String[] args) {
PrintThread pt1=new PrintThread('A');.................//x5
PrintThread pt2=new PrintThread('B');.................//x6
PrintThread pt3=new PrintThread('C');.................//x7
pt1.start();
pt2.start();
pt3.start();
}}//结果:
AAAAAAAAAAA
AAAAAAAAAAA
...........
CCCCCCCCCCC
...........
BBBBBBBBBBB
BBBBBBBBBBB

printer对象是一个静态变量,它为所有线程共有。运行x2行的run()方法会执行x3行时碰到的同步块,获取printer对象的monitor,从而获得执行同步块的代码。此时,其他对象没有获得monitor对象不得不等待。拥有monitor的线程执行完同步块中的代码会退出,将会返还printer对象的monitor。此时,其他等待的线程将获取monitor进入同步块开始执行相应的代码。从x5行处开始,pt1,pt2,pt3将被顺序启动,这三个线程的执行顺序由线程调度器确定。


同步化方法:

同步块只对大括号内的代码段进行同步,相反,同步化方法会对整个进程同步。同步化形式如下:

synchronized void f(){

代码段

}

与同步块一样,同步化方法也是一种运行权赋给拥有monitor线程的一种方法,但这里提及的monitor对象是其自身的monitor,即this的monitor。

假设某个线程正在执行this的f()方法,则其他线程执行到f()方法时将必须进行等待。换言之,只有获取了this的monitor的线程才能执行f()方法。

我们将同步方法转换成同步块,格式如下:

void f(){

synchronized(this){

代码段

}

}

同步方法实例:

class Printer{
synchronized void printChar(char ch){.....................//x1,同步化方法,输出10次ch
for(int i=10;i<=10;i++)
System.out.println(ch);
}}
class PrinterThread extends Thread{
Printer ptr;.................................//共有对象
char ch;..................................//待输出字符
PrinterThread(Printer ptr,char ch){..........//构造函数
this.ptr=ptr;
this.ch=ch;
}
public void run(){
for(int i=1;i<=10;i++){
ptr.printChar(ch);.............................//x2
System.out.println();
}}
public class Synchronization2{
public static void main(String[] args) {
Printer ptr=new Printer();.................................//共有对象
PrinterThread pt1=new PrinterThread(ptr,'A');.......................//x3
PrinterThread pt2=new PrinterThread(ptr,'B');........................//x4
pt1.start();
pt2.start();
}}
结果:AAAAAAAAAAA
...............
BBBBBBBBBBBBBB
AAAAAAAAAAAAAA
BBBBBBBBBBBBBB
..............
BBBBBBBBBBBBBB

能够运行x1行同步化的方法必定拥有printChar()方法的对象的monitor。但是线程只有共享Printer类的对象时才使用同步化。在x3,x4可以看到Printer对象ptr被共享了。能够运行x2行的线程必定拥有了ptr的对象monitor。

 

8.stop()、suspend()和resume()

stop()可以强制线程终止,但会出现安全问题。suspend()和resume()方法可以是线程停止。


问题思考

(1)线程在游戏编程时如何使用,请举例说明。

(2)以银行的账号管理程序为例,说明线程同步的重要性。

账户管理员的主要业务:存款、取款、查询余额、转账。


转载请注明: ITTXX.CN--分享互联网 » Java语言学习—第十二章 线程[Java基础]

最后更新:2018-09-28 15:30:46

赞 (2) or 分享 ()
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽