线程在一定条件下,状态会发生变化。线程一共有以下几种状态:
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):也就是可运行状态,线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
(1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,
(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
(3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时(如:等待用户输入),JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程变化的状态转换图如下(这个图需吃透):
注:拿到对象的锁标记,即为获得了对该对象(临界区)的使用权限。即该线程获得了运行所需的资源,进入“就绪状态”,只需获得CPU,就可以运行。因为当调用wait()后,线程会释放掉它所占有的“锁标志”,所以线程只有在此获取资源才能进入就绪状态。
下面小小的作下解释:
1、线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样, 当我们new了这个对象后,线程就进入了初始状态;
2、当该对象调用了start()方法,就进入就绪状态;
3、进入就绪后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;
4、进入运行状态后情况就比较复杂了
4.1、run()方法或main()方法结束后,线程就进入终止状态;
4.2、当线程调用了自身的sleep()方法或其他线程的join()方法,进程让出CPU,然后就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源即调用sleep ()函数后,线程不会释放它的“锁标志”。)。当sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配CPU时间片。典型地,sleep()被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。
4.3、线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到就绪状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态; 调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间片从而需要转到另一个线程。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
4.4、线程进入运行状态,执行到某句代码时发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记,一旦线程获得锁标记后,就转入就绪状态,等待系统分配CPU时间片;
4.5. suspend() 和 resume()方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和 resume() 被用在等待另一个线程产生的结果的情形:代码判断发现另一线程的执行结果(比如设置的某个数据)还没有产生时,让线程阻塞,另一个线程产生了结果后,调用 resume()使其恢复。
4.6、wait()和 notify() 方法:当线程调用wait()方法后会进入等待队列,进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。
wait() 使得线程进入阻塞状态,它有两种形式:
一种允许指定以毫秒为单位的一段时间作为参数;另一种没有参数。前者当对应的 notify()被调用或者超出指定时间时,线程进入“锁池”状态,后者则必须对应的 notify()被调用才能进入“锁池”状态 ,并尝试获取“锁标记”。当调用wait()后,线程会释放掉它所占有的“锁标记”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。waite()和notify()因为会对对象的“锁标记”进行操作,所以它们必须在synchronized函数或synchronizedblock中进行调用。如果在non-synchronized函数或non-synchronizedblock中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
注意区别:初看起来wait() 和 notify() 方法与suspend()和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的suspend()及其它所有方法在线程阻塞时都不会释放占用的锁(如果占用了的话),而wait() 和 notify() 这一对方法则相反。
上述的核心区别导致了一系列的细节上的区别
首先,前面叙述的所有方法都隶属于 Thread类,但是wait() 和 notify() 方法这一对却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
其次,前面叙述的所有方法都可在任何位置调用,但是wait() 和 notify() 方法这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException异常。
死锁,略一分析就能发现,suspend()方法和不指定超时期限的wait()方法的调用都可能产生死锁。遗憾的是,Java并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。suspend不推荐使用。
死锁:当一个线程永远地持有一个锁,并且其他线程都尝试去获得这个锁时,那么它们将永远被阻塞,这就会造成死锁。如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。
造成死锁的原因是代码逻辑上错误导致的,所以,在编写代码时,要保证所有的线程都有机会执行。
死锁的后果:获取不到资源的线程一直等待,系统资源得不到释放,系统变慢,新创建的线程进来又会阻塞,形成骨牌效应,耗尽资源,系统彻底崩溃。
1、调用Thread.sleep():使当前线程睡眠至少多少毫秒(尽管它可能在指定的时间之前被中断)。
2、调用Thread.yield():不能保障太多事情,尽管通常它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行,有可能被再次选中。
3、调用join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。
join例子如下:
public class JoinExample
{
public static void main(String[] args) throws InterruptedException
{
Thread t = new Thread(new Runnable()
{
public void run()
{
System.out.println("First task started");
System.out.println("First task Sleeping for 2 seconds");
try
{
Thread.sleep(2000);
System.out.println("First task completed");
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
});
Thread t1 = new Thread(new Runnable()
{
public void run()
{
System.out.println("Second task completed");
}
});
t.start();
t.join();
System.out.println("哈哈");
t1.start();
}
}
运行结果:
First task started
First task Sleeping for 2 seconds
First task completed
哈哈
Second task completed
说明:
主线程创建了2个线程,线程t启动,然后执行t.join();主线程会暂停,等待被加入的线程执行完,才往下执行,启动线程t1。(t.join() t表示被加入的线程,是主线程需要等待执行完的线程,t.join(1000)则表示主线程等待t线程1000毫秒,之后继续玩下执行)。join方法实现了类似同步的效果
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):也就是可运行状态,线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
(1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,
(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
(3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时(如:等待用户输入),JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程变化的状态转换图如下(这个图需吃透):
注:拿到对象的锁标记,即为获得了对该对象(临界区)的使用权限。即该线程获得了运行所需的资源,进入“就绪状态”,只需获得CPU,就可以运行。因为当调用wait()后,线程会释放掉它所占有的“锁标志”,所以线程只有在此获取资源才能进入就绪状态。
下面小小的作下解释:
1、线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样, 当我们new了这个对象后,线程就进入了初始状态;
2、当该对象调用了start()方法,就进入就绪状态;
3、进入就绪后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;
4、进入运行状态后情况就比较复杂了
4.1、run()方法或main()方法结束后,线程就进入终止状态;
4.2、当线程调用了自身的sleep()方法或其他线程的join()方法,进程让出CPU,然后就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源即调用sleep ()函数后,线程不会释放它的“锁标志”。)。当sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配CPU时间片。典型地,sleep()被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。
4.3、线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到就绪状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态; 调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间片从而需要转到另一个线程。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
4.4、线程进入运行状态,执行到某句代码时发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记,一旦线程获得锁标记后,就转入就绪状态,等待系统分配CPU时间片;
4.5. suspend() 和 resume()方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和 resume() 被用在等待另一个线程产生的结果的情形:代码判断发现另一线程的执行结果(比如设置的某个数据)还没有产生时,让线程阻塞,另一个线程产生了结果后,调用 resume()使其恢复。
4.6、wait()和 notify() 方法:当线程调用wait()方法后会进入等待队列,进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。
wait() 使得线程进入阻塞状态,它有两种形式:
一种允许指定以毫秒为单位的一段时间作为参数;另一种没有参数。前者当对应的 notify()被调用或者超出指定时间时,线程进入“锁池”状态,后者则必须对应的 notify()被调用才能进入“锁池”状态 ,并尝试获取“锁标记”。当调用wait()后,线程会释放掉它所占有的“锁标记”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。waite()和notify()因为会对对象的“锁标记”进行操作,所以它们必须在synchronized函数或synchronizedblock中进行调用。如果在non-synchronized函数或non-synchronizedblock中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
注意区别:初看起来wait() 和 notify() 方法与suspend()和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的suspend()及其它所有方法在线程阻塞时都不会释放占用的锁(如果占用了的话),而wait() 和 notify() 这一对方法则相反。
上述的核心区别导致了一系列的细节上的区别
首先,前面叙述的所有方法都隶属于 Thread类,但是wait() 和 notify() 方法这一对却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
其次,前面叙述的所有方法都可在任何位置调用,但是wait() 和 notify() 方法这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException异常。
死锁,略一分析就能发现,suspend()方法和不指定超时期限的wait()方法的调用都可能产生死锁。遗憾的是,Java并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。suspend不推荐使用。
死锁:当一个线程永远地持有一个锁,并且其他线程都尝试去获得这个锁时,那么它们将永远被阻塞,这就会造成死锁。如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。
造成死锁的原因是代码逻辑上错误导致的,所以,在编写代码时,要保证所有的线程都有机会执行。
死锁的后果:获取不到资源的线程一直等待,系统资源得不到释放,系统变慢,新创建的线程进来又会阻塞,形成骨牌效应,耗尽资源,系统彻底崩溃。
1、调用Thread.sleep():使当前线程睡眠至少多少毫秒(尽管它可能在指定的时间之前被中断)。
2、调用Thread.yield():不能保障太多事情,尽管通常它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行,有可能被再次选中。
3、调用join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。
join例子如下:
public class JoinExample
{
public static void main(String[] args) throws InterruptedException
{
Thread t = new Thread(new Runnable()
{
public void run()
{
System.out.println("First task started");
System.out.println("First task Sleeping for 2 seconds");
try
{
Thread.sleep(2000);
System.out.println("First task completed");
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
});
Thread t1 = new Thread(new Runnable()
{
public void run()
{
System.out.println("Second task completed");
}
});
t.start();
t.join();
System.out.println("哈哈");
t1.start();
}
}
运行结果:
First task started
First task Sleeping for 2 seconds
First task completed
哈哈
Second task completed
说明:
主线程创建了2个线程,线程t启动,然后执行t.join();主线程会暂停,等待被加入的线程执行完,才往下执行,启动线程t1。(t.join() t表示被加入的线程,是主线程需要等待执行完的线程,t.join(1000)则表示主线程等待t线程1000毫秒,之后继续玩下执行)。join方法实现了类似同步的效果
发表评论
-
对java 多线程 wait notify notifyAll 的理解
2016-12-14 15:53 4623个人玩游戏一台手柄游戏,一次只能有一个人玩 示例代码1 ... -
java中中synchronized的用法详解
2016-12-13 16:43 11001.对象锁: 1.1对象锁是run方法所在类的实例 s ... -
compareTo
2016-12-09 16:24 461compareTo是按字典顺序比较两个字符串。该比较基于字符串 ... -
集合(放对象)排序
2016-12-09 15:37 455//转载:http://blog.csdn.net/zxy_s ... -
MyEclipse提示“错误: 找不到或无法加载主类”-转载
2016-07-18 10:44 1472转载:http://www.ithao123.cn/conte ... -
java复制的实现方式比较(clone,序列化)
2014-07-27 19:44 613java对象的复制 方式1:clone 所需操作:实现Clo ... -
集合的复制
2014-07-27 19:32 482//示例 //学生类 package com.softst ... -
数组的copy
2014-07-27 19:28 393package com.softstome.clone.arr ... -
深层复制与浅层复制(通过序列化的方式实现)
2014-07-27 19:23 891package com.softstome.clone.arr ... -
深层复制与浅层复制(通过clone的方式)
2014-07-27 19:12 794深层复制与浅层复制 深 ...
相关推荐
1. **线程状态** Delphi中的线程状态主要有以下几个: - **创建(Created)**:线程被创建但尚未开始执行。 - **就绪(Ready)**:线程已分配到CPU资源,等待执行。 - **运行(Running)**:线程正在执行。 - *...
### MFC的状态模块状态、进程状态、线程状态详解 #### 9.1 模块状态 MFC中的模块状态是指一个可执行程序或使用MFCDLL的动态链接库(DLL)的状态信息。例如,一个OLE控件可以视为一个模块。每个应用程序的模块都有...
这个主题“设计滚动字演示线程状态及改变方法”主要涵盖了如何利用Java的多线程特性来实现滚动文字效果,并且管理线程的状态变化。下面我们将详细探讨相关的知识点。 1. **线程基础**: - **线程与进程**:在...
Java 线程状态转换图 Java 线程状态转换图是 Java 编程中非常重要的一个概念,它描述了线程在不同的状态之间的转换关系。了解线程状态转换图对 Java 编程的理解和应用非常重要。本文将详细介绍 Java 线程状态转换图...
### Java线程:线程状态的转换 #### 一、线程状态及其转换 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程中的多个线程共享同一份内存空间,使得线程间的通信...
对于判断线程状态,我们需要设置为`SystemThreadInformation`,这样返回的数据将包含所有系统线程的信息。 当`SystemInformationClass`设置为`SystemThreadInformation`时,`NtQuerySystemInformation`会返回一个`...
在MFC中,模块状态、进程状态和线程状态是理解MFC应用程序运行机制的关键概念。 模块状态指的是一个可执行程序或使用MFC的DLL的状态。对于一个应用程序来说,每个模块都有自己的状态,包含了如资源加载的实例句柄、...
Java线程状态流转图知识点总结 Java线程状态流转图是一种用于描述Java线程生命周期中不同的状态和状态转换的图形表示方式。该图形展示了Java线程从创建到终止的整个生命周期,并详细介绍了每种状态的特点和转换...
标题和描述中提到的知识点是关于线程状态及其在计算机科学中的作用,特别是在Java编程语言中的应用。线程,作为进程中的独立控制流,是现代操作系统(如Mac、Windows NT、Windows 95等)和Java平台的核心概念之一。...
- 可以使用`GetThreadContext()`,`GetThreadTimes()`,或`QueryThreadCycleTime()`等API查询线程状态。 4. **线程同步**: - 为了确保线程安全,通常需要使用同步机制,如互斥量(`Mutex`)、信号量(`Semaphore...
在探究JVM线程状态以及Thread.sleep的实现原理时,我们首先需要了解Java线程与操作系统线程之间的关系。在Java虚拟机(JVM)中,每个线程通常都是以一对一的关系映射到操作系统线程上的。然而,尽管两者在实现上是...
java线程从新建到死亡所能经历的各种状态之间的流转。包括运行到阻塞、进入锁池、等待队列,全面而清晰的一张图
Java线程状态转换是Java多线程编程中的关键概念,对于理解和优化并发程序至关重要。Java线程在其生命周期中经历多种状态,这些状态之间的转换是由线程调度器根据特定的策略来决定的。以下是对Java线程状态转换的详细...
### C++ 调用 Python API 的线程状态与全局解释器锁(GIL) #### 一、背景介绍 在探讨C++如何调用Python时,我们首先需要理解Python的线程安全机制。Python解释器并非完全线程安全的,这意味着在多线程环境下访问...
在Java编程中,线程是并发执行任务的基本单位,理解线程状态的转换对于编写高效的多线程程序至关重要。Java线程的状态主要包括以下五种: 1. 新状态(New):当通过`new Thread()`创建了一个线程对象,但还未调用`...
在Windows Forms(Winform)开发中,线程状态管理是一项重要的技术。线程是操作系统分配CPU时间的基本单元,用于执行程序中的并发任务。Winform应用程序通常由主线程(UI线程)驱动,处理用户界面交互,但有时我们...
在多线程编程中,了解线程状态是至关重要的,因为这可以帮助我们诊断问题、优化性能并确保程序的正确运行。线程状态是线程在生命周期中的不同阶段,每个阶段都对应着特定的行为和资源使用情况。下面我们将深入探讨...
线程状态转换是一个复杂的过程,涉及到线程调度、锁的管理等多个方面。了解这些状态和转换机制,有助于编写高效、可控的多线程程序。在实际编程中,应根据需求合理利用这些状态控制,确保线程间的协作和资源利用达到...