java

位置:IT落伍者 >> java >> 浏览文章

java 多线程 完全版


发布日期:2024年04月13日
 
java 多线程 完全版

尽管线程对象的常用方法可以通过API文档来了解但是有很多方法仅仅从API说明是无法详细了解的我们先来说一下线程对象的几个重要的方法

首先我们来说明start()方法

一个线程对象生成后如果要产生一个执行的线程就一定要调用它的start()方法在介绍这个方法时不得不同时说明run方法其实线程对 象的run方法完全是一个接口回调方法它是你这个线程对象要完成的具体逻辑简单说你要做什么就你在run中完成而如何做什么时候做就不需要你控制 了你只要调用start()方法JVM就会管理这个线程对象让它产生一个线程并注册到线程处理系统中

从表面上看start()方法调用了run()方法事实上start()方法并没有直接调用run方法在JDK以前 start()方法是本地方法它如何最终调用run方法已经不是JAVA程序员所能了解的而在JDK原来的那个本地start()方法被 start()代替另个一个纯JAVA的start()中调用本地方法start()而在start()方法中做了一个验证就是对一个全局变量 (对象变量)started做检验如果为true则start()抛出异常不会调用本地方法start()否则先将该变量设有true然后 调用start()

从中我们可以看到这个为了控制一个线程对象只能运行成功一次start()方法这是因为线程的运行要获取当前环境包括安全父线程的权限 优先级等条件如果一个线程对象可以运行多次那么定义一个static 的线程在一个环境中获取相应权限和优先级运行完成后它在另一个环境中利用原来的权限和优先级等属性在当前环境中运行这样就造成无法预知的结果简单说 来让一个线程对象只能成功运行一次是基于对线程管理的需要

start()方法最本质的功能是从CPU中申请另一个线程空间来执行 run()方法中的代码它和当前的线程是两条线在相对独立的线程空间运行也就是说如果你直接调用线程对象的run()方法当然也会执行但那是 在当前线程中执行run()方法执行完成后继续执行下面的代码而调用start()方法后run()方法的代码会和当前线程并发(单CPU)或并行 (多CPU)执行

所以请记住一句话[调用线程对象的run方法不会产生一个新的线程]虽然可以达到相同的执行结果但执行过程和执行效率不同

[线程的interrupt()方法interrupted()和isInterrupted()]

这三个方法是关系非常密切而且又比较复杂的虽然它们各自的功能很清楚但它们之间的关系有大多数人不是真正的了解

先说interrupt()方法它是实例方法而它也是最奇怪的方法在java语言中线程最初被设计为隐晦难懂的东西直到现在它的 语义不没有象它的名字那样准确大多数人以为一个线程象调用了interrupt()方法那它对应的线程就应该被中断而抛出异常事实中当一个线程 对象调用interrupt()方法它对应的线程并没有被中断只是改变了它的中断状态

使当前线程的状态变以中断状态如果没有其它影响线程还会自己继续执行

只有当线程执行到sleepwaitjoin等方法时或者自己检查中断状态而抛出异常的情况下线程才会抛出异常

如果线程对象调用interrupt()后它对应的线程就立即中断那么interrupted()方法就不可能执行

因为interrupted()方法是一个static方法就是说只能在当前线程上调用而如果一个线程interrupt()后它已经中断了那它又如何让自己interrupted()?

正因为一个线程调用interrupt()后只是改变了中断状态它可以继续执行下去在没有调用sleepwaitjoin等法或自己抛 出异常之前它就可以调用interrupted()来清除中断状态(还会原状)interrupted()方法会检查当前线程的中断状态如果为 被中断状态则改变当前线程为非中断状态并返回true如果为非中断状态则返回false它不仅检查当前线程是否为中断状态而且在保证当 前线程回来非中断状态所以它叫interrupted是说中断的状态已经结束(到非中断状态了)isInterrupted()方法则仅仅检查线 程对象对应的线程是否是中断状态并不改变它的状态

目前大家只能先记住这三个方法的功能只有真正深入到多线程编程实践中才会体会到它们为什么是对象方法为什么是类方法

线程到底什么时候才被中断抛出InterruptedException异常我们将在提高篇中详细讨论

[sleep()join()yield()方法]

在现在的环节中我只能先说明这些方法的作用和调用原则至于为什么在基础篇中无法深入只能在提高篇中详细说明

sleep()方法中是类方法也就是对当前线程而言的程序员不能指定某个线程去sleep只能是当前线程执行到sleep()方法时睡 眠指定的时间(让其它线程运行)事实上也只能是类方法在当前线程上调用试想如果你调用一个线程对象的sleep()方法那么这个对象对应的线程如 果不是正在运行它如何sleep()?所以只有当前线程因为它正在执行你才能保证它可以调用sleep()方法

原则:[在同步方法中尽量不要调用线程的sleep()方法]或者简单说对于一般水平的程序员你基本不应该调用sleep()方法

join()方法正如第一节所言在一个线程对象上调用join方法是当前线程等待这个线程对象对应的线程结束比如有两个工作工作A要耗时秒钟工作B要耗时秒或更多我们在程序中先生成一个线程去做工作B然后做工作A

new?B()start();//做工作B

A();//做工作A

工作A完成后下面要等待工作B的结果来进行处理如果工作B还没有完成我就不能进行下面的工作C所以

B?b?=?new?B();

bstart();//做工作B

A();//做工作A

bjoin();//等工作B完成

C();//继续工作C

原则:[join是测试其它工作状态的唯一正确方法]我见过很多人甚至有的是博士生在处理一项工作时如果另一项工作没有完成说让当前工 作线程sleep(x)我问他你这个x是如何指定的你怎么知道是毫秒而不是毫秒或是毫秒?其实这就是OnXXX事件的实质我们不 是要等多长时间才去做什么事而是当等待的工作正好完成的时候去做

yield()方法也是类方法只在当前线程上调用理由同上它主是让当前线程放弃本次分配到的时间片原则:[不是非常必要的情况下没有理 由调用它]调用这个方法不会提高任何效率只是降低了CPU的总周期上面介绍的线程一些方法基于(基础篇)而言只能简单提及以后具体应用中我会结合 实例详细论述

线程本身的其它方法请参看API文档下一节介绍非线程的方法但和线程密切相关的两[三]个对象方法:

[wait()notify()/notifyAll()]

这是在多线程中非常重要的方法

关于这两个方法有很多的内容需要说明在下面的说明中可能会有很多地方不能一下子明白但在看完本节后即使不能完全明白你也一定要回过头来记住下面的两句话:

[wait()notify()/notityAll()方法是普通对象的方法(Object超类中实现)而不是线程对象的方法]

[wait()notify()/notityAll()方法只能在同步方法中调用]

[线程的互斥控制]

多个线程同时操作某一对象时一个线程对该对象的操作可能会改变其状态而该状态会影响另一线程对该对象的真正结果

这个例子我们在太多的文档中可以看到就象两个操售票员同时售出同一张票一样

线程A 线程B

线程A在数据库中查询存票发现票C可以卖出

class=left线程A接受用户订票请求准备出票

这时切换到了线程B执行

线程B在数据库中查询存票发现票C可以卖出

线程B将票卖了出去

切换到线程A执行线程A卖了一张已经卖出的票

所以需要一种机制来管理这类问题的发生当某个线程正在执行一个不可分割的部分时其它线程不能不能同时执行这一部分

象这种控制某一时刻只能有一个线程执行某个执行单元的机制就叫互斥控制或共享互斥(mutual exclusion)

在JAVA中用synchornized关键字来实现互斥控制(暂时这样认为JDK已经发展了新的机制)

[synchornized关键字]

把一个单元声明为synchornized就可以让在同一时间只有一个线程操作该方法

有人说synchornized就是一把锁事实上它确实存在锁但是是谁的锁锁谁这是一个非常复杂的问题

每个对象只有一把监视锁(monitor lock)一次只能被一个线程获取当一个线程获取了这一个锁后其它线程就只能等待这个线程释放锁才能再获取

那么synchornized关键字到底锁什么?得到了谁的锁?

对于同步块synchornized获取的是参数中的对象锁:

synchornized(obj){ // }线程执行到这里时首先要获取obj这个实例的锁如果没有获取到线程只能等待如果多个线程执行到这里只能有一个线程获取obj的锁然后执行{}中的语句所以obj对象的作用范围不同控制程序不同

假如:

public void test(){ Object o = new Object(); synchornized(obj){ // } }这段程序控制不了任何多个线程之间执行到Object o = new Object();时会各自产生一个对象然后获取这个对象有监视锁各自皆大欢喜地执行

而如果是类的属性:

class Test{ Object o = new Object(); public void test(){ synchornized(o){ // } }}所有执行到Test实例的synchornized(o)的线程只有一个线程可以获取到监视锁

有时我们会这样:

public void test(){ synchornized(this){ // } }那么所有执行Test实例的线程只能有一个线程执行而synchornized(o)和synchornized(this)的范围是不同 的因为执行到Test实例的synchornized(o)的线程等待时其它线程可以执行Test实例的synchornized(o)部分但多 个线程同时只有一个可以执行Test实例的synchornized(this)]

而对于

synchornized(Testclass){ // }这样的同步块而言所有调用Test多个实例的线程赐教只能有一个线程可以执行

[synchornized方法]

如果一个方法声明为synchornized的则等同于把在为个方法上调用synchornized(this)

如果一个静态方法被声明为synchornized则等同于把在为个方法上调用synchornized(类class)

现在进入wait方法和notify/notifyAll方法这两个(或叫三个)方法都是Object对象的方法而不是线程对象的方法如同锁一样它们是在线程中调用某一对象上执行的

class Test{ public synchornized void test(){ //获取条件int x 要求大于; if(x < ) wait(); } }这里为了说明方法没有加在try{}catch(){}中如果没有明确在哪个对象上调用wait()方法则为thiswait();

假如:

Test t = new Test();

现在有两个线程都执行到ttest();方法其中线程A获取了t的对象锁进入test()方法内

这时x小于所以线程A进入等待

当一个线程调用了wait方法后这个线程就进入了这个对象的休息室(waitset)这是一个虚拟的对象但JVM中一定存在这样的一个数据结构用来记录当前对象中有哪些程线程在等待

当一个线程进入等待时它就会释放锁让其它线程来获取这个锁

所以线程B有机会获得了线程A释放的锁进入test()方法如果这时x还是小于线程B也进入了t的休息室

这两个线程只能等待其它线程调用notity[All]来唤醒

但是如果调用的是有参数的wait(time)方法则线程AB都会在休息室中等待这个时间后自动唤醒

[为什么真正的应用都是用while(条件)而不用if(条件)]

在实际的编程中我们看到大量的例子都是用?

while(x < )

wait();go();而不是用if为什么呢?

在多个线程同时执行时if(x <)是不安全的因为如果线程A和线程B都在t的休息室中等待这时另一个线程使x==并调用notifyAll方法线程A继续 执行下面的go()而它执行完成后x有可能又小于比如下面的程序中调用了x这时切换到线程B线程B没有继续判断直接执行go(); 就产生一个错误的条件只有while才能保证线程B又继续检查一次

[notify/notifyAll方法]

这两个方法都是把某个对象上休息区内的线程唤醒notify只能唤醒一个但究竟是哪一个不能确定而notifyAll则唤醒这个对象上的休息室中所有的线程

一般有为了安全性我们在绝对多数时候应该使用notifiAll()除非你明确知道只唤醒其中的一个线程

那么是否是只要调用一个对象的wait()方法当前线程就进入了这个对象的休息室呢?事实中要调用一个对象的wait()方法只有当前线程获取了这个对象的锁换句话说一定要在这个对象的同步方法或以这个对象为参数的同步块中

class MyThread extends Thread{ Test t = new Test(); public void run(){ ttest(); Systemoutprintln(Thread say:HelloWorld!); } } public class Test { int x = ; public void test(){ if(x==) try{ wait(); }catch(Exception e){} } public static void main(String[] args) throws Exception{ new MyThread()start(); } }这个线程就不会进入t的wait方法而直接打印出Thread say:HelloWorld!

而如果改成:

public class Test { int x = ; public synchornized void test(){ if(x==) try{ wait(); }catch(Exception e){} } public static void main(String[] args) throws Exception{ new MyThread()start(); } }

               

上一篇:java设计模式之Visitor

下一篇:Java中Runnable和Thread的区别