一、概念
1、进程:是指一个内存中运行的应用程序。
每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。
比如,在windows系统中,一个运行的exe就是一个进程。
2、线程:是进程中的一个执行流程,一个进程中可以运行多个线程。
比如:java.exe进程中可以运行多个线程。
线程总是属于某一个进程的,进程中的多个线程共享进程的内存。
注:所谓的“同时”,实际上是多个线程之间轮流执行的结果,只不过每个线程执行的时间很短,就切换到另一个线程,人眼看起来像是同时在执行。
二、java中线程
1、使用Thread类或者Runnable接口 来定义、实例化和启动新线程。
2、一个Thread类实例是一个对象,具有变量和方法,存放在堆内存中。
3、每个线程都有一个调用栈,一旦创建一个新线程,就会产生一个新的调用栈。
线程的创建和启动
一、实例化线程
a) 扩展java.lang.Thread类,如:
package com;
public class MyThread extends Thread {
public MyThread(String name) {
super(name) ;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 10; j++) {
System.out.println(this.getName()+" : "+i);
}
}
}
public static void main(String[] args) {
Thread t1 = new MyThread("张三") ;
Thread t2 = new MyThread("李四") ;
t1.start();
t2.start();
}
}
a) 实现java.lang.Runnable接口
package com;
public class MyRunnable implements Runnable {
private String name ;
public MyRunnable(String name) {
this.name = name ;
}
public void run() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 10; j++) {
System.out.println(name +" : "+i);
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable("张三")) ;
Thread t2 = new Thread(new MyRunnable("李四")) ;
t1.start();
t2.start();
}
}
一、启动线程
a) 在线程的Thread方法上,调用start()方法,不是run()方法
b) 在调用start()方法前:
i. 线程处于新状态,新状态指有一个Thread对象,但还并不是一个真正的线程。
c) 在调用start()方法后:
i. 发生如下变化:
1. 启动新的执行线程(具有新的调用栈)
2. 该状态从新状态转移到可执行状态
3. 当该线程获得机会执行时,其目标run()方法将运行
注:对于java来说,run()方法就像main()方法一样,只是新线程知道调用的方法名称和签名,
因此,在Runnable上或者Thread上调用run方法是合法的,但并不启动新的线程。
二、线程模型和线程调度原理
a) 线程栈模型
i. 线程模型是理解线程调度原理以及线程执行过程的基础。
ii. 线程栈:是指某一个时刻内存中线程调度的栈信息,当前正在调用的方法总是位于栈顶,线程栈中的内容是随着线程的运行状态变化而变化的,我们要研究线程栈,就要选择某一个运行时间(也就是代码运行到哪一行),如下图:
i. 上图中,
1. 第一个栈A是主线程main()方法运行带System.out.pringln(“Hello Java”);时的运行栈信息,main()方法位于栈A的栈顶;
2. 第二个栈A是主线程main()方法运行到new JavaThreadDemo().threadMethod();方法时的栈信息,threadMethod()方法位于栈A的栈顶。
3. 在threadMethod()方法中,运行到start()方法时,会新建立一个线程,新建立的线程也将拥有自己的线程栈B,run()方法位于栈B的栈顶。
4. 此时,线程栈A和线程栈B并行运行
5. 由此可以看出,方法调用和线程启动的区别:
a) 方法调用只是在原来的线程栈中调用方法即可。
b) 而线程启动会新建立一个独立的线程栈来运行自己的线程。
b) 线程的生命周期
线程的生命周期包括5中状态:新建、可运行状态、运行状态、阻塞、死亡。
i. 新建:创建了一个新的线程对象,但是还没有调用线程的start()方法,此时此案成处于新建状态,如:Thread t1 = new MyThread("张三") ;
ii. 可运行状态:调用线程的start()方法,线程进入可运行状态,此时线程等待JVM的调度程序将其变为运行状态
iii. 运行状态:线程调度程序,从众多的可运行状态线程中,选择一个线程来执行。这也是线程进入运行状态的唯一方式,必须有JVM来调度。
iv. 阻塞状态:线程的等待、睡眠和阻塞统称为阻塞状态,此时线程依然是活着的,处于待命状态,知道某个条件出现时,变为可运行状态。
v. 死亡状态:当线程的run()方法执行完毕后,线程结束。此时线程已经不存在,它所占用的所有资源都会被回收。
二、线程阻塞:
a) 线程阻塞有多种,常见的有三种(IO阻塞不讨论):
i. 睡眠:调用线程的sleep()方法进入睡眠状态。
当线程睡眠时,它入睡在某个地方,在苏醒之前不会返回到可运行状态。当睡眠时间到期,则返回到可运行状态。
1. 线程处于睡眠状态时,JVM调度程序会暂停此线程的执行,从而去执行其他的处于可运行状态的线程。
2. sleep()方法是Thread的静态方法,只能控制当前线程的睡眠。
3. 线程睡眠时间到了之后,返回到可运行状态,等待JVM调度。
ii. 等待:
iii. 获取线程锁而阻塞
b) 让线程暂时离开运行状态的三中方法:
i. 调用线程的sleep()方法,使线程睡眠一段时间
ii. 调用线程的yield()方法,使线程暂时回到可运行状态,来使其他线程有机会执行。
iii. 调用线程的join()方法,使当前线程停止执行,知道当前线程中加入的线程执行完毕后,当前线程才可以执行
三、线程的优先级和让步
a) 线程让步:通过Thread.yield()来实现的,yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。
b) JVM线程调度程序是基于优先级的抢先调度机制。
c) 当线程池中线程都具有相同的优先级,调度程序的JVM*选择他喜欢的线程;如果存在不同级别的优先级,JVM会有高概率选择级别高的线程,但并不一定选择高的,只是概率大一点而已。
d) 如果是线程池中具有相同的优先级,这个时候调度程序的操作有两种可能:
i. 选择一个线程运行,知道他阻塞或者运行完成为止;
ii. 时间分开,为池内的每个线程提供均匀的运行机会
e) 可以通过setPriority(int newPriority),更改线程的优先级。如:
Thread t = new MyThread();
t.setPriority(8);
t.start();
四、小结:
a) 到目前为止,我们知道了,线程离开运行状态共有3中方法:
i. 调用Thread.sleep():是当前线程睡眠n毫秒以后,进入可执行状态,等待调度;
ii. 调用Thread.yield(): 让当前运行线程回到可运行状态,是的具有相同优先级的线程有机会执行;
iii. 调用join()方法:保证当前线程停止运行,直到该线程所加入的线程完成为止,然而,如果它加入的线程没有存活,则当前线程不需要停止。
b) 除了以上三种方式外,还有下面几种特殊情况可能是线程离开运行状态
i. 线程的run()方法完成;
ii. 在对象上调用wait()方法(不是在线程上);
iii. 线程不能再对象上获得锁定,他正试图运行该对象的方法代码;
iv. 线程调度程序可以决定将当前运行状态移动到可运行状态,以便让另一个线程获得运行机会,而不需要任何理由。
参考文章:http://www.cnblogs.com/riskyer/p/3263032.html
http://www.2cto.com/kf/201409/335651.html
http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html