您的位置:首页 > 编程语言 > Java开发

java多线程系列----------- 基本概念释疑

2015-05-30 16:06 323 查看

一、线程和线程对象

线程,是指正在执行的一个指令序列.在java平台上是指从一个线程对象的start()开始.运行run方法体中的那一段相对独立的过程.

线程对象是可以产生线程的对象.比如在java平台中Thread对象,Runnable对象.

二、并发与并行

在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent).

而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel).

三、线程类的几个常用方法

1、interrupt()、interrupted()和isInterrupted()方法

这三个方法是关系非常密切而且又比较复杂的,虽然它们各自的功能很清楚,但它们之间的关系有大多数人不是真正的了解。
先说interrupt()方法,它是实例方法,而它也是最奇怪的方法,直到现在它的语义也不像它的名字那样准确。大多数人以为,一个线程象调用了interrupt()方法,那它对应的线程就应该被中断而抛出异常,事实中,当一个线程对象调用interrupt()方法,它对应的线程并没有被中断,只是改变了它的中断状态,使当前线程的状态变为中断状态,如果没有其它影响,线程还会自己继续执行,只有当线程执行到sleep,wait,join等方法时,或者自己检查中断状态而抛出异常的情况下,线程才会抛出异常。

如果线程对象调用interrupt()后它对应的线程就立即中断,那么interrupted()方法就不可能执行。因为interrupted()方法是一个static方法,就是说只能在当前线程上调用,而如果一个线程interrupt()后它已经中断了,那它又如何让自己interrupted()?正因为一个线程调用interrupt()后只是改变了中断状态,它可以继续执行下去,在没有调用sleep,wait,join等方法或自己抛出异常之前,它就可以调用interrupted()来清除中断状态(还会原状)。

interrupted()方法会检查当前线程的中断状态,如果为"被中断状态"则改变当前线程为"非中断状态"并返回true,如果为"非中断状态"则返回false,它不仅检查当前线程是否为中断状态,而且在保证当前线程回到非中断状态,所以它叫"interrupted",是说中断的状态已经结束(到非中断状态了)。

isInterrupted()方法则仅仅检查线程对象对应的线程是否是中断状态,并不改变它的状态。

2、sleep()、join()和yield()方法

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

原则:在同步方法中尽量不要调用线程的sleep()方法,或者简单说,对于一般水平的程序员你基本不应该调用sleep()方法。
join()方法:在线程对象a上调用join()方法,就是让当前正在执行的线程等待线程对象a对应的线程运行完成后才继续运行。

yield()方法也是类方法,只在当前线程上调用,理由同上,它主是让当前线程放弃本次分配到的时间片。

原则:不是非常必要的情况下,没有理由调用它].调用这个方法不会提高任何效率,只是降低了CPU的总周期。

3、wait()、notify()/notityAll()方法

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

wait(),notify()/notityAll()方法只能在同步方法中调用。
class Test{
public synchronized void test(){
//获取条件,int x 要求大于100;

if(x < 100)
wait();
}
}


这里为了说明方法没有加在try{}catch(){}中,如果没有明确在哪个对象上调用wait()方法,则为this.wait();

假如:Test t = new Test();

现在有两个线程都执行到t.test();方法.其中线程A获取了t的对象锁,进入test()方法内,这时x小于100,所以线程A进入等待.

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

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

所以线程B有机会获得了线程A释放的锁,进入test()方法,如果这时x还是小于100,线程B也进入了t的休息室。这两个线程只能等待其它线程调用notify()/notityAll()来唤醒. 但是如果调用的是有参数的wait(time)方法,则线程A,B都会在休息室中等待这个时间后自动唤醒.

为什么真正的应用都是用while(条件)而不用if(条件),在实际的编程中我们看到大量的例子都是用

while(x < 100)
wait();go();

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

notify/notifyAll 这两个方法都是把某个对象上休息区内的线程唤醒,notify只能唤醒一个,但究竟是哪一个不能确定,而notifyAll则唤醒这个对象上的休息室中所有的线程。一般为了安全性,我们在绝对多数时候应该使用notifiAll(),除非你明确知道只唤醒其中的一个线程。

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

class MyThread extends Thread{
Test t = new Test();
public void run(){
t.test();
System.out.println("Thread say:Hello,World!");
}
}

public class Test {

int x = 0;
public  void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
这个线程就不会进入t的wait方法而直接打印出Thread say:Hello,World!.而如果改成:

public class Test {

int x = 0;
public synchronized void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
我们就可以看到线程一直等待,注意这个线程进入等待后没有其它线程唤醒,除非强行退出JVM环境,否则它一直等待。

所以请记住:线程要想调用一个对象的wait()方法就要先获得该对象的监视锁,而一旦调用wait()后又立即释放该锁。

四、线程的互斥控制

多个线程同时操作某一对象时,一个线程对该对象的操作可能会改变其状态,而该状态会影响另一线程对该对象的真正结果。所以需要一种机制来管理这类问题的发生,当某个线程正在执行一个不可分割的部分时,其它线程不能不能同时执行这一部分。像这种控制某一时刻只能有一个线程执行某个执行单元的机制就叫互斥控制或共享互斥(mutual exclusion)

在JAVA中,用synchronized关键字来实现互斥控制(JDK1.5已经发展了新的机制), 把一个单元声明为synchronized,就可以让在同一时间只有一个线程操作该方法。

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

那么synchronized关键字到底锁什么?得到了谁的锁? 对于同步块,synchronized获取的是参数中的对象锁:

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

    public void test(){
Object o = new Object();

synchronized(obj){ //............... }
}
这段程序控制不了任何,多个线程之间执行到Object o = new Object();时会各自产生一个对象然后获取这个对象有监视锁,各自皆大欢喜地执行。 而如果是类的属性:

class Test{
Object o = new Object();
public void test(){

synchronized(o){
//...............
}
}
}
所有执行到Test实例的synchronized(o)的线程,只有一个线程可以获取到监视锁.

有时我们会这样:

public void test(){
synchronized(this){
//...............
}
}
那么所有执行Test实例的线程只能有一个线程执行。而synchornized(o)和synchronized(this)的范围是不同的,因为执行到Test实例的synchornized(o)的线程等待时,其它线程可以执行Test实例的synchronized(o1)部分,但多个线程同时只有一个可以执行Test实例的synchornized(this)。 而对于
synchronized(Test.class){
//...............
}
这样的同步块而言,所有调用Test多个实例的线程只能有一个线程可以执行。

如果一个方法声明为synchronized的,则等同于在整个方法上调用synchronized(this)。

如果一个静态方法被声明为synchronized,则等同于在整个方法上调用synchronized(类.class)。

四、线程中断的理解

设想这样的情景:我们的应用在某一个时间段内,需要一个子线程不停的在后台运行,这可能是一个下载过程,是一个对服务端socket的监听,也可能是一个绘图的计算过程。当我们想要终止线程的时候,我们会怎样做呢?是设定一个标志变量来控制跳出循环?还是使用thread.stop()?又或者是设置thread = null?

有的时候我们需要一种规范的思路,使用规范的方法来解决一类问题。

我们首先要明白,线程终止的条件,有三种情况:

1.当线程的run方法执行方法体中最后一条语句后。

2.当执行retutrn语句返回时。

3.当出现了在方法中没有捕获的异常时。

在Java的早期版本中,还有一个stop方法,其他线程可以调用它终止线程,但是这个方法已经被弃用了,所以还在用的同学就不要继续用了。

我们的正确思路是,使用interrupt方法来终止我们的线程。

首先要理解interrupt方法做了什么:每一个线程都有一个中断状态,这是一个boolean标志,当线程调用了interrupt方法时,这个中断状态就会被置位。如果我们要检查中断状态,可以使用Thread.currentThread().isInterrupted()来获得是否中断。

但是如果线程被阻塞了(sleep or wait),当我们调用了interrupt方法的时候,就会产生InterruptedException异常。这是我们可以利用的地方。

同样的,如果中断状态先被置位了,然后我们调用了sleep方法,线程不会休眠,相反,它将清除中断状态,然后抛出InterruptedException。

我们调用了interrupt并不意味着线程会终止,线程是否会终止,以及会如何继续,是程序员来控制的。

在本文中我们将会讨论终止线程的规范用法,首先我们来看两种情形的后台线程写法:

public void run()
{
try{
...
while(!Thread.currentThread.isInterrupted() && more work to do)
{
do more work
}
}
catch(InterruptedException)
{
//thread was interrupted during sleep or wait
}
finally
{
cleanup, if required
}
//exiting the run method terminates the thread
}

public void run()
{
try{
...
while( more work to do)
{
do more work
Thread.sleep(delay);
}
}
catch(InterruptedException)
{
//thread was interrupted during sleep or wait
}
finally
{
cleanup, if required
}
//exiting the run method terminates the thread
}
第一种写法适用于后台下载,文件拷贝以及类似情形,第二种写法适合游戏画面刷新或者类似情形。

第一种写法利用了interrupt方法,作为终止的请求,使得循环跳出,run方法执行完毕。而第二种方法则是利用当线程sleep的时候调用interrupt会抛出InterruptedException从而跳出了循环进而线程执行到结束。

事实上这两种写法的区别就在于第二种使用了sleep。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: