线程已阅.docx

上传人:b****7 文档编号:9025857 上传时间:2023-02-02 格式:DOCX 页数:19 大小:23.36KB
下载 相关 举报
线程已阅.docx_第1页
第1页 / 共19页
线程已阅.docx_第2页
第2页 / 共19页
线程已阅.docx_第3页
第3页 / 共19页
线程已阅.docx_第4页
第4页 / 共19页
线程已阅.docx_第5页
第5页 / 共19页
点击查看更多>>
下载资源
资源描述

线程已阅.docx

《线程已阅.docx》由会员分享,可在线阅读,更多相关《线程已阅.docx(19页珍藏版)》请在冰豆网上搜索。

线程已阅.docx

线程已阅

线程

1、线程的状态

线程的状态表示线程在某段时间内进行的活动和将要进行的任务。

线程有创建、就绪、运行、阻塞、死亡5种状态。

1创建状态

实例化Thread对象,但没有调用start()方法时的状态。

例如:

ThreadTesttt=newThreadTest();

或者

Threadt=newThread(tt);

此时虽然创建了Thread对象,但是他们暂时不能通过isAlive()测试。

2就绪状态

程序有资格运行,当调度程序还没有把它选为线程运行时所处的状态。

此时,线程具备了运行的条件,一旦被选中马上就能运行。

线程创建后,调用了start()方法,线程不处于运行状态,但能通过isAlive()测试。

在线程运行后,或者从被阻塞、等待或者睡眠状态回来之后,线程首先进入就绪状态。

3运行状态

从就绪状态池(是池,不是队列)中被选择为当前执行的线程所处的状态。

4等待、阻塞或者睡眠状态

线程依然是活的,但是缺少运行的条件,一旦具备了条件就可以转为就绪状态(不能直接转为运行状态)。

另外,suspend()和stop()方法已经被废弃了,比较危险,不要再使用了。

5死亡状态

一个线程的run()方法运行结束,那么该线程就完成了它的使命,它的栈结构将解散,也就是死亡了。

但是该线程仍然是一个Thread对象,仍可以被引用这一点与其他对象一样,而且被引用的对象也不会被垃圾回收器回收。

一旦线程死去,他就永远不能重新启动了,也就是说,不能再用start()方法让它运行。

创建线程有两种方式。

⑴继承java.lang.Thread类

publicclassThreadTestextendsThread{

publicvoidrun(){

System.out.println("somethingrunhere!

");

}

publicvoidrun(Strings){

System.out.println("stringinrunis"+s);

}

publicstaticvoidmain(String[]args){

ThreadTesttt=newThreadTest();

tt.start();

tt.run("itwon'tautorun");

}

}

运行结果如下:

stringinrunisitwon'tautorun

somethingrunhere!

注意:

是否与想象的顺序相反了?

为什么呢?

一旦调用了start()方法,必须给JVM留足时间,让它配置进程。

而在JVM配置完成之前,重载的run(Strings)方法被调用了,结果反而先输出了“stringinrunisitwon’tautorun”。

之后,tt线程完成了配置,输出了“somethingrunhere!

这个结论是比较容易验证。

修改上面的程序,在“tt.start();”语句后面加上语句“for(inti=0;i<10000;i++);”.主线程必须执行运算量比较大的for循环,只有执行完for循环才能运行后面的“tt.run(“itwon’tautorun!

”);”语句。

此时,因为有足够的时间完成线程的配置,所以修改后的程序运行结果如下:

somethingrunhere

stirnginrunisitwon’tautorun!

注意:

这种输出结果的顺序是没有保障!

不要依赖循环耗时!

Thread类有许多管理线程的方法,包括创建、启动和暂停他们,所有的操作都是从run()方法开始,并且在run()方法内编写需要在独立线程内执行的代码。

run()方法可以调用其他方法,但是执行的线程总是通过调用run()开始。

没有参数的run()方法是自动被调用的,而带参数的run()是被重载的,必须显示调用。

上面示例中创建线程的方式很简单,但不是最好的方案。

Java是单继承结构的,如果继承了Thread类,那么就不能继承其它的类了,应该把继承的机会留给别的类。

⑵实现java.lang.Runnable接口

publicclassThreadTestimplementsRunnable{

publicvoidrun(){

System.out.println("somethingrunhere!

");

}

publicstaticvoidmain(String[]args){

ThreadTesttt=newThreadTest();

Threadt1=newThread(tt);

Threadt2=newThread(tt);

t1.start();

t2.start();

//newThread(tt).start();

}

}

这种方式把线程相关的代码和线程要执行的代码分离出来。

另一种常见的方式是参数形式的匿名内部类创建方式,示例代码如下

publicclassThreadTest{

publicstaticvoidmain(String[]args){

Threadt=newThread(newRunnable(){

publicvoidrun(){

System.out.println("anonymousthread");

}

}

);

t.start();

}

}

在调用start()方法开始执行线程之前,线程的状态还不是活的。

测试程序如下:

publicclassThreadTestimplementsRunnable{

publicvoidrun(){

System.out.println("somethingrunhere!

");

}

publicstaticvoidmain(String[]args){

ThreadTesttt=newThreadTest();

Threadt1=newThread(tt);

System.out.println(t1.isAlive());

t1.start();

System.out.println(t1.isAlive());

}

}

结果输出:

false

true

isAlivve()方法用于确定一个线程是否已经启动,而且还没完成run()方法。

注意:

线程的启动要调用start()方法,只有这样才能创建新的调用栈。

而直接调用run()方法的话,就不会调用新调用栈,也就不会创建新的线程,run方法就与普通的方法没什么两样了。

2、线程同步

线程同步指多个线程同时访问某资源时,采用一系列的机制以保证同时最多只能有一个线程访问该资源。

线程共享了相同的资源。

但是在某些重要的情况下,一次只能让一个线程来访问共享资源,例如,作为银行账户这样的共享资源,如果多个线程可以同时使用该资源,就会出现银行账户数据安全上的问题。

1共享变量

要使多个线程在同一个程序中有用,必须在某种方法实现线程间互相通信或共享结果,

最简单方法是使用共享变量。

确保变量从一个线程正确传播到另一个线程,以防止当一个线程正在更新一些相关数据项时,另一个线程看到不一致的中间结果,需要采取线程同步。

2存在于同一个内存空间中的所有线程

线程与进程有许多共同点,不同的是线程与统一进程中的其他线程共享相同的进程上下文,包括内存空间。

只要访问共享变量(静态变量或实例变量),线程就可以方便地互相交换数据,但必须确保线程以受控的方式访问共享变量,以免它们互相干扰对方的更改。

3受控访问的同步

为确保可以在线程之间以受控方式共享数据,Java语言提供了两个关键字:

synchronized

和volatile。

Synchronized有两个重要含义:

■一次只有一个线程可以执行代码的受保护部分

■一个线程更改的数据对于其他线程是可见的。

4确保共享数据更改的可见性

同步可以让用户确保线程看到一致的内存视图。

如果没有同步,数据很容易处于不一致的状态。

例如,一个线程正在更新两

个相关值,而另一个线程正在读取这两个值,有可能在第1个线程只更新了一个值,还没有来得及更新另一个值的时候,调度第2个线程运行,第2个线程就会看到一个旧值和一个新值。

Volatile比同步更简单,只适合于控制对基本变量(整数、布尔变量等)的单

个实例的访问。

当一个变量被声明为volatile,任何对该变量的写操作都会绕过告诉缓存,直接写入主内存,而任何对该变量的读取也都绕过告诉缓存,直接取自主内存。

这表示所有线程在任何时候看到的volatile变量值都相同。

5用锁保护的原子代码块

Volatile对于确保每个线程看到最新的变量值非常有用,但实际上经常需要保

护代码片段,同步使用监控器(monitor)或锁的概念,以协调对特定代码块的访问。

每个Java对象都有一个相关的锁,同一时间只能有一个线程持有Java锁。

线程进入synchronized代码块时,线程会阻塞并等待,直到锁可用。

当线程处于就绪状态时,并且获得锁后,将执行代码块,当控制退出受保护的代码块,即到达了代码块的末尾或者抛出了没有在synchronized块中捕获的异常时,它就会释放该锁。

这样,每次只有一个线程可以执行受给定监控器保护的代码块。

从其他线程

的角度看,该代码块可以看做是原子的,它要么全部执行,要么根本不执行。

6简单的同步示例

使用synchronized块可以将一组相关更新作为一个集合来执行,而不必担心

其他线程中断或得到意外的结果。

以下示例将打印“10”或者“01”,如果没有同步,他还会打印“11”或“00”。

publicclassSynExample{

privatestaticObjectlockObject=newObject();

privatestaticclassThread1extendsThread{

publicvoidrun(){

synchronized(lockObject){

intx,y;

x=y=0;

System.out.println(x);

}

}

}

privatestaticclassThread2extendsThread{

publicvoidrun(){

synchronized(lockObject){

intx,y;

x=y=1;

System.out.println(y);

}

}

}

publicstaticvoidmain(String[]args){

newThread1().run();

newThread2().run();

}

}

在这两个线程中都必须使用同步,以便使程序正确工作。

7Java锁定

Java锁定可以保护许多代码块或方法,每次只有一个线程可以持有锁。

代码块由锁保护并不表示两个线程不能同时执行该代码块,只表示如果两

个线程正在等待相同的锁,则他们不能同时执行该代码。

在以下示例中,两个线程可以同时不受限制地执行setLastAccess()方法中的

synchronized块,因为每个线程有一个不同的thingie值。

因此,synchronized代码块受到两个正在执行的线程中不同锁的保护。

publicclassSynExample{

publicstaticclassThingie{

privateDatelaseAccess;

publicsynchronizedvoidsetLastAccess(Datedate){

this.laseAccess=date;

}

}

publicstaticclassMyThreadextendsThread{

privateThingiethingie;

publicMyThread(Thingiethingie){

this.thingie=thingie;

}

publicvoidrun(){

thingie.setLastAccess(newDate());

}

}

publicstaticvoidmain(String[]args){

Thingiethingie1=newThingie();

Thingiethingie2=newThingie();

newMyThread(thingie1).start();

newMyThread(thingie2).start();

}

}

8同步的方法

创建synchronized块的最简单方法是将方法声明为synchronized。

这表示致在进入方法主体之前,调用者必须获得锁,示例代码如下:

publicclassPoint{

publicsynchronizedvoidsetXY(intx,inty){

this.x=x;

this.y=y;

}

}

对于普通的synchronized方法,这个锁是一个对象将针对它调用方法。

对于

静态synchronized方法,这个锁与Class对象相关的监控器,在该对象中声明了该方法。

setXY()方法被声明为synchronized并不表示两个不同的线程不能同时执行

setXY()方法,只要他们调用不同的Point实例的setXY()方法就可以同时执行。

对于同一个Point实例一次只能有一个线程执行setXY()方法或Point的任何其他synchronized方法。

9同步的块

Synchronized块的语法比synchronized方法稍微复杂一点,因为还需要显示地指定锁要保护哪个块。

publicclassPoint{

publicvoidsetXY(intx,inty){

synchronized(this){

this.x=x;

this.y=y;

}

}

}

使用this引用锁很常见,但这并不是必需的。

使用this引用作为锁表示该代

码块将与这个类中的synchronized方法使用同一个锁。

由于同步防止了多个线程同时执行一个代码块,因此性能上就有问题,即使

在单处理器系统上,最好在尽可能最小的需要保护的代码块上使用同步。

访问基于堆栈的局部变量从来不需要受到保护,因为他们只能被自己所属的

线程访问。

10大多数类并没有同步

因为同步会带来小小的性能损失,大多数通用类,如java.util中的Collection

类,不在内部使用同步。

这表示在没有附加同步的情况下,不能在多个线程中使用诸如HashMap这样的类。

3、线程转换

线程存在着创建状态、就绪状态、运行状态、阻塞状态和死亡状态。

那么各个状态之间是如何转换的呢?

如何利用线程的状态转换来完成相应的任务?

深入理解线程转换是灵活使用Java线程的基础。

在Java线程中,正在运行的线程可以转换为不可运行状态,但是线程不可从运行状态直接转换为运行状态,而是首先转换为运行状态,如下图所示。

Thread类的常用方法如下所示。

■finalBooleanisAlive():

用来的确定线程是活动线程还是死亡线程。

如果线程已经启动并还没有终止,那么就是活动状态,返回true。

■finalintgetPriority():

返回当前状态的优先级

■finalvoidsetPriority(intnewPriority):

改变线程的优先级

■staticvoidyield():

使线程暂时停止运行

■staticvoidsleep(longmillisec):

指定当前线程的休眠时间,然后继续运行。

■finalvoidjoin():

调用该方法的某线程,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行。

■voidinterrupt():

中断调用此方法的线程。

在等待通知、休眠或者阻塞以等待挂起完成的状态,线程会抛出InterruptedException异常。

1线程的调度和优先级

线程调用的意义在于JVM应对运行的多个线程进行系统级的协调,以避免多个线程争用有限资源而导致应用系统死机或者崩溃。

Java定义了线程的优先级策略,Java线程的优先级分为10个等级,分别用1~10之间的数字表示。

数字越大表示线程的优先级越高。

相应地,在Thread类中定义了表示线程最低、最高和普通优先级的成员变量MIN-PRIORITY,MAX-PRIORITY和NOMAL-PRIORITY,代表的优先级分别为1、10、5。

当一个线程对象被创建时,其默认的线程优先级是5.

为了控制线程的运行策略,Java定义了线程调度器来监控系统中处于就绪状态的所有线程。

线程调度器按照线程的优先级决定哪个线程投入处理器运行。

在多个线程处于就绪状态的条件下,具有高优先级的线程会在低优先级线程之前得到执行。

线程调度器同样采用“抢占式”策略来调度线程执行,即当前线程执行过程中有较高优先级的线程进入就绪状态,则高优先级的线程立即被调度执行。

具有相同优先级的线程采用轮转的方式来共同分配CPU时间片。

在应用程序中设置线程优先级的方法很简单,在创建线程对象之后可以调用线程对象的setPriority()方法改变该线程的运行优先级,同样可以调用getPriority()方法获取当前线程的优先级。

在Java中比较特殊的线程是被称为守护线程(Daemon)的低级别线程。

这个线程具有最低的优先级,用于为系统中的其他对象和线程提供服务。

将一个用户线程设置为守护进程的方式是线程对象创建之前调用线程对象的setDaemon()方法。

典型的守护线程例子是JVM中的系统资源自动回收线程,它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

2运行和挂起

通过调用strat()方法后,线程就进入了准备运行状态。

进入准备运行阶段后,

线程才能够获得运行的资格,即获得CPU的时间。

注意:

系统线程调度器来决定哪个线程可以运行并确定其运行的时间,也就

是说,线程具有不可预知性。

■yield()方法

调用线程Thread类的yield()方法将会导致当前线程从运行状态转换为准备

运行状态。

使用yield()方法的好处是可以让出CPU的时间,供其他线程使用。

典型的例子就是用户需要进行长时间计算的线程(例如计算PI值),通过“取消”操作来获得快速的系统反应,从而预防假的死机现象。

一个使用yield()方法的示例如下:

publicclassTestYield{

publicstaticvoidmain(String[]args){

MyThreadt1=newMyThread("t1");

MyThreadt2=newMyThread("t12");

t1.start();

t2.start();

}

}

classMyThreadextendsThread{

MyThread(Strings){

super(s);

}

publicvoidrun(){

for(inti=0;i<100;i++){

System.out.println(getName()+":

"+i);

if(i%10==0){

yield();

}

}

}

}

■sleep()方法

sleep()使当前线程(即调用该方法的线程)暂时执行一段时间,让其他线程

有机会继续执行,但它并不释放对象锁,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据

注意:

该方法要捕获异常。

例如有两个线程同时执行(没有synchronized),一个线程优先级为

MAX_PRIORITY,另一个为MIN_PRIORITY。

如果没有sleep()方法,只有高优先级的线程执行完成后,低优先级的线程才能执行。

但当高优先级的线程调用sleep(5000)后,低优先级就有机会执行了。

总之,sleep()可以使低优先级的线程得到执行的机会,也可以让同优先级、

高优先级的线程有执行的机会。

3wait()和notify()

通常,多线程之间需要协调工作,例如两个人共用一个卫生间(每次只能一个人用),一个人必须等待另一人用完,得知没有人使用的时候才能使用卫生间。

以上逻辑简单的说就是:

如果条件不满足,则等待。

当条件满足时,等待该条件的线程将被唤醒。

在Java中,这个机制的实现依赖于wait()和notify()。

等待机制与锁机制是密切相关的。

例如:

synchronized(obj){

while(!

condition){

obj.wait();

}

obj.dosomeThing();

}

当线程A获得了obj锁后,发现条件condition不满足,无法继续下移处理,于是线程A就等待(调用wait()方法)。

在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:

synchronized(obj){

condition=true;

obj.notify();

}

如果以使用卫生间作为示例,加入他们都要刷牙和洗脸。

他们是这样约定的:

第一个人先刷牙,然后第二个人刷牙;第一个人洗脸,然后第二个人洗脸。

程序代码如下:

publicclassSyn{

publicstaticvoidmain(String[]args){

TwoPeople.ONE.start();

TwoPeople.TWO.start();

}

}

classTwoPeopleextendsThread{

privateinti;

staticThreadONE=newTwoPeople

(1);

staticThreadTWO=newTwoPeople

(2);

staticObjectwashroom=newObject();

privateTwoPeople(inti){

this.i=i;

}

privatevoidbrush(){

System.out.println("People"+i+"isbrushing!

");

try{

Thread.sleep(2000);

}catch(Exceptione){

e.printStackTrace();

}

//延迟两秒看结果

System.out.println("People"+i+"hasbrushed!

");

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1