Java多线程之 -- 进程和线程
Java多线程之 – 进程和线程
概念
进程
- 程序的动态执行过程
- 包括占用的资源(内存、CPU)和线程
线程
- 线程是程序中最小的执行单位
- 一个进程有多个线程
- 线程共享进程的资源
进程和线程的区分
我们可以想象为进程为班级而线程是邦奇中得每一个学生
线程之间的交互
- 互斥,类似于每一个学生都为了第一名而你争我让,线程也是,都想抢占CPU的资源
- 同步,当举行运动会的时候,大家都团结一心,彼此共享自己的资源
Thread、Runnable
Thread
Introduction
Thread 是Java当中的线程,线程是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。注意:Java中的多线程是一种抢占机制而不是分时机制。抢占机制指的是有多个线程处于可运行状态,但是只允许一个线程在运行,他们通过竞争的方式抢占CPU。
Define
/**
* 使用继承java.lang.Thread类的方式创建一个线程
*
* @author DreamSea 2011-12-29 20:17:06
*/
public class ThreadTest extends Thread {
/**
* 重写(Override)run()方法 JVM会自动调用该方法
*/
public void run() {
System.out.println("I‘m running!");
}
}
注意,重写(override)run()
方法在该线程的start()
方法之后被调用,但是重载(overload)run()
方法,该方法和普通的成员方法一样,并不会因调用该线程的start()
方法而被JVM自动运行。
public class ThreadTest extends Thread {
/**
* 重写(Override)run()方法 JVM会自动调用该方法
*/
@Override
public void run() {
System.out.println("I‘m running!");
}
/**
* 重载(Overload)run()方法 和普通的方法一样,并不会在该线程的start()方法被调用后被JVM自动运行
*/
public void run(int times) {
System.out.println("I‘m running!(Overload)");
}
}
使用建议
不建议使用这种方法,因为这个类继承自Thread之后不能再去继承其他的类,而且这种继承实现的方法是和实现Runnable接口有区别的
Start
ThreadTest thread = new ThreadTest();
thread.start();
State
新生状态(New): 当一个线程的实例被创建即使用new关键字和Thread类或其子类创建一个线程对象后,此时该线程处于新生(new)状态,处于新生状态的线程有自己的内存空间,但该线程并没有运行,此时线程还不是活着的(not alive);
就绪状态(Runnable): 通过调用线程实例的start()方法来启动线程使线程进入就绪状态(runnable);处于就绪状态的线程已经具备了运行条件,但还没有被分配到CPU即不一定会被立即执行,此时处于线程就绪队列,等待系统为其分配CPU,等待状态并不是执行状态;此时线程是活着的(alive);
阻塞状态(Blocked): 通过调用join()、sleep()、wait()或者资源被暂用使线程处于阻塞(blocked)状态;处于Blocking状态的线程仍然是活着的(alive);
死亡状态(Dead): 当一个线程的run()方法运行完毕或被中断或被异常退出,该线程到达死亡(dead)状态。此时可能仍然存在一个该Thread的实例对象,当该Thread已经不可能在被作为一个可被独立执行的线程对待了,线程的独立的call stack已经被dissolved。一旦某一线程进入Dead状态,他就再也不能进入一个独立线程的生命周期了。对于一个处于Dead状态的线程调用start()方法,会出现一个运行期(runtime exception)的异常;处于Dead状态的线程不是活着的(not alive).
线程生命周期: new 一个对象然后经过thread.start()执行Runnable,在JVM调度获得CPU资源之后处于running状态,最后死亡,中间线程可能被阻塞或者没有被调度。
Context
对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。
由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。
因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。
说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。
虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。
Function
类别 | 方法签名 | 简介 |
---|---|---|
线程的创建 | Thread() | |
线程的创建 | Thread(String name) | |
线程的创建 | Thread(Runnable runnable) | |
线程的创建 | Thread(Runnable target, String name) | |
线程的方法 | start() | 启动线程 |
线程的方法 | static void sleep(long millis) | 休眠线程 |
线程的方法 | static void sleep(long millis, int nanos) | 休眠线程 |
线程的方法 | void join() | 使其他线程等待当前线程终止 |
线程的方法 | void join(long millis) | 使其他线程等待当前线程终止 |
线程的方法 | void join(long millis, int nanos) | 使其他线程等待当前线程终止 |
获取可用线程 | static Thread currentThread() | 返回当前运行的线程的引用 |
注意:
- sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
public class Test implements Runnable {
@Override
public void run(){
this.s();
}
public synchronized void s(){
System.out.println(Thread.currentThread().getName()
+ " get lock.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " release lock.");
}
public static void main(String[] args){
Test st = new Test();
Thread t1 = new Thread(st, "t1");
Thread t2 = new Thread(st, "t2");
t1.start();
t2.start();
}
}
他的输出数据为:
t1 get lock.
t1 release lock.
t2 get lock.
t2 release lock.
yield方法调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。
由于wait方法会让线程释放对象锁,所以join方法同样会让线程释放对一个对象持有的锁。具体的wait方法使用在后面文章中给出。
public class Test1 implements Runnable {
private Object lock;
public Test1(Object lock){
this.lock = lock;
}
@Override
public void run(){
synchronized(lock){
System.out.println(Thread.currentThread().getName()
+ " get lock.");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " release lock.");
}
}
}
public class Test2 implements Runnable {
private Object lock;
public Test2(Object lock){
this.lock = lock;
}
@Override
public void run() {
try {
Thread.sleep(2*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock){
System.out.println(Thread.currentThread().getName()
+ " get lock.");
lock.notifyAll();
System.out.println(Thread.currentThread().getName()
+ " release lock.");
}
synchronized(lock){
System.out.println(Thread.currentThread().getName()
+ " get lock.");
System.out.println(Thread.currentThread().getName()
+ " release lock.");
}
}
public static void main(String[] args){
Object lock = new Object();
Thread t1 = new Thread(new Test1(lock), "t1");
Thread t2 = new Thread(new Test2(lock), "t2");
t1.start();
t2.start();
}
}
结果如下:
t1 get lock.
t2 get lock.
t2 release lock.
t2 get lock.
t2 release lock.
t1 release lock.
- interrupt方法interrupt,顾名思义,即中断的意思。单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。
Runnable
Definie
一个类如果需要具备多线程的能力,也可以通过实现java.lang.Runnable接口进行实现。按照Java语言的语法,一个类可以实现任意多个接口,所以该种实现方式在实际实现时的通用性要比前面介绍的方式好一些。但是单独的run方法是不能异步执行的,也就是直接调用run方法与Thread调用run方法结果会不一样。
差异性
以买票程序为例子,当我们使用Runnable实现多线程时
public class RunnableTest implements Runnable{
public static int count = 10;
public String name;
public RunnableTest(String name){
this.name = name;
}
@Override
public void run() {
while(count > 0){
count--;
print("线程: " + name + ",现在还有 " + count +" 张票!");
}
}
public static void main(String args[]){
RunnableTest test1 = new RunnableTest("小红");
RunnableTest test2 = new RunnableTest("小绿");
RunnableTest test3 = new RunnableTest("小蓝");
new Thread(test1).start();
new Thread(test2).start();
new Thread(test3).start();
}
public static void print(Object o){
System.out.println(o.toString());
}
}
输出结果是:
线程: 小红,现在还有 9 张票!
线程: 小红,现在还有 6 张票!
线程: 小蓝,现在还有 7 张票!
线程: 小蓝,现在还有 4 张票!
线程: 小绿,现在还有 8 张票!
线程: 小蓝,现在还有 3 张票!
线程: 小红,现在还有 5 张票!
线程: 小蓝,现在还有 1 张票!
线程: 小绿,现在还有 2 张票!
线程: 小红,现在还有 0 张票!
然而当我们使用Thread的时候结果不一样了:
线程: 小红,现在还有 8 张票!
线程: 小蓝,现在还有 7 张票!
线程: 小绿,现在还有 7 张票!
线程: 小蓝,现在还有 5 张票!
线程: 小红,现在还有 6 张票!
线程: 小蓝,现在还有 3 张票!
线程: 小绿,现在还有 4 张票!
线程: 小蓝,现在还有 1 张票!
线程: 小红,现在还有 2 张票!
线程: 小绿,现在还有 0 张票!
可以看出出现了重复的数据,这就是Runnable和Thread在不同实现情况之下的区别,这是因为继承Thread不能保证共享数据的可见性,至于什么是共享数据,怎么共享数据,我们下次再说
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。