多线程_多线程理论

来源:互联网 发布:苹果mac 镜像下载 编辑:程序博客网 时间:2024/06/06 05:03


线程的五种状态及其转换:

 ():线程对象已经创建,还没有在其上调用start()方法。

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

        a:start()方法调用时,线程首先进入可运行状态。

        b:在线程运行之后或者从阻塞、等待或睡眠状态回来后也返回到可运行状态。

 运行:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

等待/阻塞/睡眠 :这是线程有资格运行时它所处的状态。实际上这  三个状态组合为一种,其共同点是:线程仍旧是活 的,但是当前没有条件运行。换句话说,它是可运行               的,但是如果某件事件出现,他可能返回到可运行态。

死亡: 当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

2、阻止线程执行:

对于线程的阻止,考虑一下三个方面,不考虑IO阻塞的情况

 睡眠:Thread.sleep(long millis)Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以减慢线程。当线程睡眠时,它入睡在某个地方,在苏醒之前不会返回到可运行状态。当睡眠时间到期,则返回到可运行状态。

 注意:

a、线程睡眠是帮助所有线程获得运行机会的最好方法。

b、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。

c、sleep()是静态方法,只能控制当前正在运行的线程。

 等待:

          a:线程的优先级和线程让步yield()yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。yield()应该做的是让当前运行线程回到可运行状

      b:调用join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。

注意:当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作

 因为需要一个对象的锁定而被阻塞

除了以上三种方式外,还有下面几种特殊情况可能使线程离开运行状态:

1、线程的run()方法完成。

2、在对象上调用wait()方法(不是在线程上调用)。

3、线程不能在对象上获得锁定,它正试图运行该对象的方法代码。

4、线程调度程序可以决定将当前运行状态移动到可运行状态,以便让另一个线程获得运行机会,而不需要任何理由。

 

4同步和锁定

同步问题提出

 

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

②a:锁的原理

  Java中每个对象都有一个内置锁

  当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。

   当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。

 

一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

 

释放锁是指持锁线程退出了synchronized同步方法或代码块。

关于锁和同步,有一下几个要点:

1)、只能同步方法,而不能同步变量和类

2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?

3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。

4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。

6)、线程睡眠时,它所持的任何锁都不会释放。

7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。

9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:

    public int fix(int y) {        synchronized (this) {            x = x - y;        }        return x;    } 


当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:

    public int fix(int y) {        synchronized (this) {            x = x - y;        }        return x;    } 

    public int fix(int y) {        synchronized (this) {            x = x - y;        }        return x;    } 

效果是完全一样的。

 

10静态方法同步

要同步静态方法,需要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)


5如果线程不能获得锁会怎么样:

如果线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止。

当考虑阻塞时,一定要注意哪个对象正被用于锁定:

、调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。

 

、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。

 

、静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。

 

、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。


6何时需要同步

①在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。

 

②对于非静态字段中可更改的数据,通常使用非静态方法访问。

③对于静态字段中可更改的数据,通常使用静态方法访问。

 

④如果需要在非静态方法中使用静态字段,或者在静态字段中调用非静态方法,问题将变得非常复杂。


7线程安全类

当一个类已经很好的同步以保护它的数据时,这个类就称为线程安全的


8线程死锁

    死锁对Java程序来说,是很复杂的,也很难发现问题。当两个线程被阻塞,每个线程在等待另一个线程时就发生死锁。

   假设read()方法由一个线程启动,write()方法由另外一个线程启动。读线程将拥有resourceA锁,写线程将拥有resourceB锁,两者都坚持等待的话就出现死锁。

 

    实际上,上面这个例子发生死锁的概率很小。因为在代码内的某个点,CPU必须从读线程切换到写线程,所以,死锁基本上不能发生。

 

   但是,无论代码中发生死锁的概率有多小,一旦发生死锁,程序就死掉。有一些设计方法能帮助避免死锁,包括始终按照预定义的顺序获取锁这一策略。



9线程同步小结:

1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。

2)、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。

3)、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。

4)、对于同步,要时刻清醒在哪个对象上同步,这是关键。

5)、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对原子操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。

6)、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。

7)、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,但是,一旦程序发生死锁,程序将死掉。

即使是线程安全类,也应该特别小心,因为操作的线程是间仍然不一定安全。