二、可运行状态:当线程有资格运行,但调度程序尚未把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行以后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
四、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,可是当前没有条件运行。换句话说,它是可运行的,可是若是某件事件出现,他可能返回到可运行状态。
五、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,可是,它已经不是一个单独执行的线程。线程一旦死亡,就不能 复生。 若是在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
2、阻止线程执行
对于线程的阻止,考虑一下三个方面,不考虑IO阻塞的状况:
睡眠;
等待;
由于须要一个对象的锁定而被阻塞。
一、睡眠
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。当线程睡眠时,它入睡在某个地方,在苏醒以前不会返回到可运行状态。当睡 眠时间到期,则返回到可运行状态。
线程睡眠的缘由:线程执行太快,或者须要强制进入下一轮,由于Java规范不保证合理的轮换。
睡眠的实现:调用静态方法。
try {
Thread.sleep(123);
} catch (InterruptedException e) {
e.printStackTrace();
}
睡眠的位置:为了让其余线程有机会执行,能够将Thread.sleep()的调用放线程run()以内。这样才能保证该线程执行过程当中会睡眠。
注意:
一、线程睡眠是帮助全部线程得到运行机会的最好方法。
二、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短期。所以,sleep()方法不能保证该线程睡眠到期后就开始执行。
三、sleep()是静态方法,只能控制当前正在运行的线程。
二、线程的优先级和线程让步yield()
线程的让步是经过Thread.
yield()来实现的。yield()方法的做用是:暂停当前正在执行的线程对象,并执行其余线程。
要理解yield(),必须了解线程的优先级的概念。线程老是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数状况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数状况。
注意:当设计多线程应用程序的时候,必定不要依赖于线程的优先级。由于线程调度优先级操做是没有保障的,只能把线程优先级做用做为一种提升程序效率的方法,可是要保证程序不依赖这种操做。
当线程池中线程都具备相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操做有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每一个线程提供均等的运行机会。
设置线程的优先级:线程默认的优先级是建立它的执行线程的优先级。能够经过setPriority(int newPriority)更改线程的优先级。例如:
Thread t = new MyThread();
t.setPriority(8);
t.start();
线程优先级为1~10之间的正整数,JVM从不会改变一个线程的优先级。然而,1~10之间的值是没有保证的。一些JVM可能不能识别10个不一样的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。
线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:
static int MAX_PRIORITY
线程能够具备的最高优先级。
static int MIN_PRIORITY
线程能够具备的最低优先级。
static int NORM_PRIORITY
分配给线程的默认优先级。
三、Thread.yield()方法
Thread.yield()方法做用是:暂停当前正在执行的线程对象,并执行其余线程。
yield()应该作的是让当前运行线程回到可运行状态,以容许具备相同优先级的其余线程得到运行机会。所以,使用yield()的目的是让相 同优先级的线程之间能适当的轮转执行。可是,实际中没法保证yield()达到让步目的,由于让步的线程还有可能被线程调度程序再次选中。
结论:yield()从未致使线程转到等待/睡眠/阻塞状态。在大多数状况下,yield()将致使线程从运行状态转到可运行状态,但有可能没有效果。
四、join()方法
Thread的非静态方法join()让一个线程B“加入”到另一个线程A的尾部。在A执行完毕以前,B不能工做。例如:
Thread t = new MyThread();
t.start();
t.join();
另外,join()方法还有带超时限制的重载版本。 例如t.join(5000);则让线程等待5000毫秒,若是超过这个时间,则中止等待,变为可运行状态。
线程的加入join()对线程栈致使的结果是线程栈发生了变化,固然这些变化都是瞬时的。下面给示意图:
小结
到目前位置,介绍了线程离开运行状态的3种方法:
一、调用Thread.sleep():使当前线程睡眠至少多少毫秒(尽管它可能在指定的时间以前被中断)。
二、调用Thread.yield():不能保障太多事情,尽管一般它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行。
三、调用join()方法:保证当前线程中止执行,直到该线程所加入的线程完成为止。然而,若是它加入的线程没有存活,则当前线程不须要中止。
除了以上三种方式外,还有下面几种特殊状况可能使线程离开运行状态:
一、线程的run()方法完成。
二、在对象上调用wait()方法(不是在线程上调用)。
三、线程不能在对象上得到锁定,它正试图运行该对象的方法代码。
四、线程调度程序能够决定将当前运行状态移动到可运行状态,以便让另外一个线程得到运行机会,而不须要任何理由。
Java线程:线程的同步与锁
1、同步问题提出
线程的同步是为了防止多个线程访问一个数据对象时,对数据形成的破坏。
例如:两个线程ThreadA、ThreadB都操做同一个对象Foo对象,并修改Foo对象上的数据。
public
class Foo {
private
int x = 100;
public
int getX() {
return x;
}
public
int fix(
int y) {
x = x - y;
return x;
}
}
public
class MyRunnable
implements Runnable {
private Foo foo =
new Foo();
public
static
void main(String[] args) {
MyRunnable r =
new MyRunnable();
Thread ta =
new Thread(r,
"Thread-A");
Thread tb =
new Thread(r,
"Thread-B");
ta.start();
tb.start();
}
public
void run() {
for (
int i = 0; i < 3; i++) {
this.fix(30);
try {
Thread.sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
" : 当前foo对象的x值= " + foo.getX());
}
}
public
int fix(
int y) {
return foo.fix(y);
}
}
运行结果:
Thread-A : 当前foo对象的x值= 40
Thread-B : 当前foo对象的x值= 40
Thread-B : 当前foo对象的x值= -20
Thread-A : 当前foo对象的x值= -50
Thread-A : 当前foo对象的x值= -80
Thread-B : 当前foo对象的x值= -80
Process finished with exit code 0
从结果发现,这样的输出值明显是不合理的。缘由是两个线程不加控制的访问Foo对象并修改其数据所致。
若是要保持结果的合理性,只须要达到一个目的,就是将对Foo的访问加以限制,每次只能有一个线程在访问。这样就能保证Foo对象中数据的合理性了。
在具体的Java代码中须要完成一下两个操做:
把竞争访问的资源类Foo变量x标识为private;
同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。
2、同步和锁定
一、锁的原理
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 synchronized int getX() {
return x++;
}
与
public int getX() {
synchronized (this) {
return x;
}
}
效果是彻底同样的。
3、静态方法同步
要同步静态方法,须要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)。
例如:
public static synchronized int setName(String name){
Xxx.name = name;
}
等价于
public static int setName(String name){
synchronized(Xxx.class){
Xxx.name = name;
}
}
4、若是线程不能不能得到锁会怎么样
若是线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止。
当考虑阻塞时,必定要注意哪一个对象正被用于锁定:
一、调用同一个对象中非静态同步方法的线程将彼此阻塞。若是是不一样对象,则每一个线程有本身的对象的锁,线程间彼此互不干预。
二、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。
三、静态同步方法和非静态同步方法将永远不会彼此阻塞,由于静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
四、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不一样对象上锁定的线程将永远不会彼此阻塞。
5、什么时候须要同步
在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。
对于非静态字段中可更改的数据,一般使用非静态方法访问。
对于静态字段中可更改的数据,一般使用静态方法访问。
若是须要在非静态方法中使用静态字段,或者在静态字段中调用非静态方法,问题将变得很是复杂。已经超出SJCP考试范围了。
6、线程安全类
当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”。
即便是线程安全类,也应该特别当心,由于操做的线程是间仍然不必定安全。
举个形象的例子,好比一个集合是线程安全的,有两个线程在操做同一个集合对象,当第一个线程查询集合非空后,删除集合中全部元素的时候。第二个 线程也来执行与第一个线程相同的操做,也许在第一个线程查询后,第二个线程也查询出集合非空,可是当第一个执行清除后,第二个再执行删除显然是不对的,因 为此时集合已经为空了。
看个代码:
public
class NameList {
private List nameList = Collections.synchronizedList(
new LinkedList());
public
void add(String name) {
nameList.add(name);
}
public String removeFirst() {
if (nameList.size() > 0) {
return (String) nameList.remove(0);
}
else {
return
null;
}
}
}
public
class Test {
public
static
void main(String[] args) {
final NameList nl =
new NameList();
nl.add(
"aaa");
class NameDropper
extends Thread{
public
void run(){
String name = nl.removeFirst();
System.out.println(name);
}
}
Thread t1 =
new NameDropper();
Thread t2 =
new NameDropper();
t1.start();
t2.start();
}
}
虽然集合对象
private List nameList = Collections.synchronizedList(new LinkedList());
是同步的,可是程序还不是线程安全的。
出现这种事件的缘由是,上例中一个线程操做列表过程当中没法阻止另一个线程对列表的其余操做。
解决上面问题的办法是,在操做集合对象的NameList上面作一个同步。改写后的代码以下:
public
class NameList {
private List nameList = Collections.synchronizedList(
new LinkedList());
public
synchronized
void add(String name) {
nameList.add(name);
}
public
synchronized String removeFirst() {
if (nameList.size() > 0) {
return (String) nameList.remove(0);
}
else {
return
null;
}
}
}
这样,当一个线程访问其中一个同步方法时,其余线程只有等待。
7、线程死锁
死锁对Java程序来讲,是很复杂的,也很难发现问题。当两个线程被阻塞,每一个线程在等待另外一个线程时就发生死锁。
仍是看一个比较直观的死锁例子:
public
class DeadlockRisk {
private
static
class Resource {
public
int value;
}
private Resource resourceA =
new Resource();
private Resource resourceB =
new Resource();
public
int read() {
synchronized (resourceA) {
synchronized (resourceB) {
return resourceB.value + resourceA.value;
}
}
}
public
void write(
int a,
int b) {
synchronized (resourceB) {
synchronized (resourceA) {
resourceA.value = a;
resourceB.value = b;
}
}
}
}
假设read()方法由一个线程启动,write()方法由另一个线程启动。读线程将拥有resourceA锁,写线程将拥有resourceB锁,二者都坚持等待的话就出现死锁。
实际上,上面这个例子发生死锁的几率很小。由于在代码内的某个点,CPU必须从读线程切换到写线程,因此,死锁基本上不能发生。
可是,不管代码中发生死锁的几率有多小,一旦发生死锁,程序就死掉。有一些设计方法能帮助避免死锁,包括始终按照预约义的顺序获取锁这一策略。已经超出SCJP的考试范围。
8、线程同步小结
一、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
二、线程同步方法是经过锁来实现,每一个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其余访问该对象的线程就没法再访问该对象的其余同步方法。
三、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程得到锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
四、对于同步,要时刻清醒在哪一个对象上同步,这是关键。
五、编写线程安全的类,须要时刻注意对多个线程竞争访问资源的逻辑和安全作出正确的判断,对“原子”操做作出分析,并保证原子操做期间别的线程没法访问竞争资源。
六、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
七、死锁是线程间相互等待锁锁形成的,在实际中发生的几率很是的小。真让你写个死锁程序,不必定好使,呵呵。可是,一旦程序发生死锁,程序将死掉。
Java线程:线程的交互
线程交互是比较复杂的问题,SCJP要求不很基础:给定一个场景,编写代码来恰当使用等待、通知和通知全部线程。
1、线程交互的基础知识
SCJP所要求的线程交互知识点须要从
java.lang.Object的类的三个方法来学习:
void notify()
唤醒在此对象监视器上等待的单个线程。
void notifyAll()
唤醒在此对象监视器上等待的全部线程。
void wait()
致使当前的线程等待,直到其余线程调用此对象的 notify() 方法或 notifyAll() 方法。
固然,wait()还有另外两个重载方法:
void wait(long timeout)
致使当前的线程等待,直到其余线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
void wait(long timeout, int nanos)
致使当前的线程等待,直到其余线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其余某个线程中断当前线程,或者已超过某个实际时间量。
以上这些方法是帮助线程传递线程关心的时间状态。
关于等待/通知,要记住的关键点是:
必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
wait()、notify()、notifyAll()都是Object的实例方法。与每一个对象具备锁同样,每一个对象能够有一个线程列表,他 们等待来自该信号(通知)。线程经过执行对象上的wait()方法得到这个等待列表。从那时候起,它再也不执行任何其余指令,直到调用对象的 notify()方法为止。若是多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。若是没有线程等待,则不采起任何特殊操 做。
下面看个例子就明白了:
public
class ThreadA {
public
static
void main(String[] args) {
ThreadB b =
new ThreadB();
//启动计算线程
b.start();
//线程A拥有b对象上的锁。线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者
synchronized (b) {
try {
System.out.println(
"等待对象b完成计算。。。");
//当前线程A等待
b.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
"b对象计算的总和是:" + b.total);
}
}
}
public
class ThreadB
extends Thread {
int total;
public
void run() {
synchronized (
this) {
for (
int i = 0; i < 101; i++) {
total += i;
}
//(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线程A被唤醒
notify();
}
}
}
等待对象b完成计算。。。
b对象计算的总和是:5050
Process finished with exit code 0
千万注意:
当在对象上调用wait()方法时,执行该代码的线程当即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁。若是线程荣然在完成同步代码,则线程在移出以前不会放弃锁。所以,只要调用notify()并不意味着这时该锁变得可用。
2、多个线程在等待一个对象锁时候使用notifyAll()
在多数状况下,最好通知等待某个对象的全部线程。若是这样作,能够在对象上使用notifyAll()让全部在此对象上等待的线程冲出等待区,返回到可运行状态。
下面给个例子:
public
class Calculator
extends Thread {
int total;
public
void run() {
synchronized (
this) {
for (
int i = 0; i < 101; i++) {
total += i;
}
}
//通知全部在此对象上等待的线程
notifyAll();
}
}
public
class ReaderResult
extends Thread {
Calculator c;
public ReaderResult(Calculator c) {
this.c = c;
}
public
void run() {
synchronized (c) {
try {
System.out.println(Thread.currentThread() +
"等待计算结果。。。");
c.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() +
"计算结果为:" + c.total);
}
}
public
static
void main(String[] args) {
Calculator calculator =
new Calculator();
//启动三个线程,分别获取计算结果
new ReaderResult(calculator).start();
new ReaderResult(calculator).start();
new ReaderResult(calculator).start();
//启动计算线程
calculator.start();
}
}
运行结果:
Thread[Thread-1,5,main]等待计算结果。。。
Thread[Thread-2,5,main]等待计算结果。。。
Thread[Thread-3,5,main]等待计算结果。。。
Exception in thread
"Thread-0" java.lang.IllegalMonitorStateException: current thread not owner
at java.lang.Object.notifyAll(Native Method)
at threadtest.Calculator.run(Calculator.java:18)
Thread[Thread-1,5,main]计算结果为:5050
Thread[Thread-2,5,main]计算结果为:5050
Thread[Thread-3,5,main]计算结果为:5050
Process finished with exit code 0
运行结果代表,程序中有异常,而且屡次运行结果可能有多种输出结果。这就是说明,这个多线程的交互程序还存在问题。到底是出了什么问题,须要深刻的分析和思考,下面将作具体分析。
实际上,上面这个代码中,咱们指望的是读取结果的线程在计算线程调用notifyAll()以前等待便可。 可是,若是计算线程先执行,并在读取结果线程等待以前调用了notify()方法,那么又会发生什么呢?这种状况是可能发生的。由于没法保证线程的不一样部 分将按照什么顺序来执行。幸运的是当读取线程运行时,它只能立刻进入等待状态----它没有作任何事情来检查等待的事件是否已经发生。 ----所以,若是计算线程已经调用了notifyAll()方法,那么它就不会再次调用notifyAll(),----而且等待的读取线程将永远保持 等待。这固然是开发者所不肯意看到的问题。
所以,当等待的事件发生时,须要可以检查notifyAll()通知事件是否已经发生。
线程休眠的目的是使线程让出CPU的最简单的作法之一,线程休眠时候,会将CPU资源交给其余线程,以便能轮换执行,当休眠必定时间后,线程会苏醒,进入准备状态等待执行。
线程休眠的方法是Thread.sleep(long millis) 和Thread.sleep(long millis, int nanos) ,均为静态方法,那调用sleep休眠的哪一个线程呢?简单说,哪一个线程调用sleep,就休眠哪一个线程。