Fork me on GitHub

Java线程学习

  Java创建一个线程,最简单的方法就是从Thread类继承。这个类包含了创建和运行线程所需的一切东西。Tread最重要的方法是run()。但为了使用run(),必须对其进行重载或者覆盖,使其能充分按自己的吩咐形式。因此,run()属于那些会与程序中的其他线程“并发”或“同时”执行代码。

1
2
3
4
5
6
7
//simpleThread.java
public class SimpleThread extends Thread {
......
public void run(){
// do something ......
}
}

  Thread包含一个特殊的方法 start(),它的作用是对线程进行特殊的初始化,然后调用 run() 。所以整个步骤包括:调用构建器来构建对象,然后用 start() 配置线程,再调用 run() 。如果不调用 start(),线程便永远不会启动。

  Java操作共享资源时,为了保证共享资源信息数据一致性,在调用共享资源的方法使用,需要采用“同步”方式操作,即调用”synchronized”方法。调用任何synchronized方法时,对象就会被锁定,不可再调用那个对象的其它任何synchronized方法,除非第一个方法完成了自己的工作,并解除锁定。下面列出简单的synchronized的方法:

1
2
3
4
5
6
7
synchronized void f(){
// do something ......
}
synchronized void g(){
// do something ......
}

重要规则:对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized,否则就不能正常地工作。

一个线程的四个状态:

  • 新(New):线程对象已经创建,但尚未启动,所以不可运行。
  • 可运行(Runnable):意味着一旦时机分片机制有空闲的CPU周期提供给一个线程,那个线程便立即开始运行。因此,线程可能在、也可能不在运行当中,但一旦条件许可,没有什么能阻止他的运行——它既没有“死”掉,也未被“堵塞”。
  • 死(Dead):从自己的run()方法中返回后,一个线程便已“死”掉。亦可调用stop()令其死掉,但会产生一个违例——属于Error的一个子类(也就是说,我们通常不捕获它)。记住一个违例的“掷”出应当是一个特殊事件,而不是正常程序运行的一部分。所以不建议你使用stop()(在Java 1.2 则是坚决反对)。另外还有一个destroy()方法(它永远不会实现),应该尽可能地避免调用它,因为它非常武断,根本不会解除对象的锁定。
  • 堵塞(Blocked):线程可以运行,但有某种东西阻碍了它。若线程处于堵塞状态,调度机制可以简单地跳过它,不给它分配任何CPU 时间。除非线程再次进入“可运行”状态,否则不会采取任何操作。

线程为何会堵塞?
堵塞状态是前述四种状态中最有趣的,值得我们作进一步的探讨。线程被堵塞可能是由下述五方面的原因造成的:
1.调用sleep(毫秒数),使线程进入“睡眠”状态。在规定的时间内,这个线程是不会运行的。
2.用suspend()暂停了线程的执行。除非线程收到resume()消息,否则不会返回“可运行”状态。
3.用wait()暂停了线程的执行。除非线程收到nofify()或者notifyAll()消息,否则不会变成“可运行”(是的,这看起来同原因2 非常相象,但有一个明显的区别是我们马上要揭示的)。
4.线程正在等候一些IO(输入输出)操作完成。
5.线程试图调用另一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。

亦可调用yield()(Thread 类的一个方法)自动放弃CPU,以便其他线程能够运行。

死锁
由于线程可能进入堵塞状态,而且由于对象可能拥有“同步”方法——除非同步锁定被解除,否则线程不能访问那个对象——所以一个线程完全可能等候另一个对象,而另一个对象又在等候下一个对象,以此类推。这个“等候”链最可怕的情形就是进入封闭状态——最后那个对象等候的是第一个对象!此时,所有线程都会陷入无休止的相互等待状态,大家都动弹不得。我们将这种情况称为“死锁”。

为减少出现死锁的可能,Java 1.2 作出的一项贡献是“反对”使用Thread 的stop(),suspend(),resume() 以及destroy()方法。
suspend()和resume() 方法天生容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被“挂起”的线程恢复运行。

推荐:使用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个
notify()重新启动线程。

线程安全性:当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的对于线程安全类的实例进行顺序或并发的一系列操作,都不会导致实例处于无效状态。

无状态对象永远是现场安全的。

原子变量
线程安全性定义要求无论是多线程中的时序或交替操作,都要保证不破坏那些不变约束。当一个不变约束涉及多个变量时,变量间不是彼此独立的:某个变量的值会制约其他几个变量的值。因此,更新一个变量的时候,要在同一原子操作中更新其他几个。

为了保护状态的一致性,要在单一的原子操作中更新相互关联的状态变量。

加锁


-------------本文结束 感谢您的阅读-------------

本文标题:Java线程学习

文章作者:ElwinHe

发布时间:2017年10月08日 - 11:10

最后更新:2017年11月15日 - 23:11

原始链接:http://www.elwinhe.xyz/blog/b44589cd/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!
0%