什么是线程
说起线程,还得从两年前说起……
算了^_^,我直接从WiKi说起吧,WiKi上是这么介绍线程的(不好上中文WiKi的同志可以去Shreagle,那里有办法):
各地线程写法:
- 英语:Thread
- 大陆:线程
- 台湾:執行緒
- 港澳:線程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程之中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程可以并发多个线程每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指的是内核线程(kernel thread),而把用户线程称(user thread)为线程。线程是独立调度和分派的基本单位。线程可以操纵系统内核调度的内核线程,如win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的不同线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
一个进程可以有很多线程,每条线程并行执行不同的任务。
在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责IO处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。
嗯,WiKi的介绍基本都解释清楚了什么是线程。
Java中的线程
创建线程
Java中线程的创建有两种方式,继承一个类(Thread)或者是实现一个接口(Runnable),继承的话只能继承一个父类,受到了限制。那实现接口就不一样了,可以实现多个接口,一定程度上不受限制,因此这也是通常用的比较多的创建线程的方式。
- 继承实现线程
public class ThreadExtend extends Thread {
@Override
public void run() {
//do something
}
}
- 接口实现线程
public class ThreadInterface implements Runnable {
@Override
public void run() {
//do something
}
}
不必要把Runnable
接口想的有多复杂,内部进行了多么复杂的操作,其实它只不过是定义了个线程的执行方式,看下面的源码就可以明白,它只定义了一个抽象方法:
package java.lang;
public interface Runnable {
public abstract void run();
}
那其实Thread
类也是实现了Runnable
接口的,来看看Thread
源码:
public class Thread implements Runnable {
...
public void run() {
if (target != null) {
target.run();
}
}
...
}
Thread
源码中对目标线程对象进行了空检查,如果不为空呢,那这个target
对象的run()
方法就会在自己的线程下运行。
Thread方法预览
那在继续了解如何开始结束等一系列的线程操作之前呢,先看看Thread中有哪些常用的方法呢:
public static native Thread currentThread();
`返回当前在执行的线程的对象引用public static native void yield();
对线程调度器的一个提示,告诉它当前在执行的线程可以退让CPU资源的使用。调度器可以无视这个提示。public static native void sleep(long millis) throws InterruptedException;
使得当前在执行的线程进入沉睡(暂时的停止)状态,保持指定长度的毫秒数。但是线程不会放弃锁或者是监视器的拥有权。public synchronized void start()
触发当前线程开始执行,Java Virtual Machine会去调用run()
方法来执行指定任务public void run()
当前线程要执行的特定线程任务@Deprecated public final void stop()
此方法已弃用。用来强制线程停止执行public void interrupt()
阻断当前线程的执行@Deprecated public void destroy()
此方法已经启用,再调用则会抛出NoSuchMethodError
异常public final native boolean isAlive();
测试当前线程是在激活执行,即已经开始且未死亡@Deprecated public final void suspend()
已弃用,暂停当前线程的执行@Deprecated public final void resume()
此方法已弃用,恢复一个已经暂停运行的线程到执行状态public final void setPriority(int newPriority)
设置当前线程的执行优先级public final synchronized void join(long millis)
如果当前线程的结束需要依赖别的线程执行结果,那么执行这个方法来等待最大毫秒数。0
标识永远等待public final void setDaemon(boolean on)
设置当前线程为后台运行线程或者是用户线程
以上列举出了Thread
的大部分方法,有静态方法也有实例方法,同时也附上了方法的简介。
Java线程的6种状态以及生命周期
6种状态
- new
线程已经被创建,但是还未启动的状态 - runnable
从操作系统的底层来看,JVM的runnable状态可以理解为由两个自状态组成的混合状态。当线程向runnable JVM状态转换时,线程首先进入预备子状态。线程管理程序就会决定线程是否进入开始,运行还是暂停。Thread.yield()
是个显式的指令来建议线程管理程序暂停当前在执行的线程,来允许其他线程的执行。
处于runnable状态的线程,在JVM运行节点看来,它其实是在等待操作性的某些系统资源。 - timed waiting
- waiting
当程序中调用如下几个方法时候,线程便会进入waiting状态 :
Object.wait()
Thread.join()
LockSupport.park()
线程处于waiting状态其实是在等待其他线程进行一些特定操作。例如一个已经在某对象上调用Object.wati()
方法的线程需要等待另一个线程中的一个对象进行Object.notify()
或者是Object.notifyAll()
的调用。一个执行了Thread.join()
方法的线程是需要等待特定线程去结束它。这就意味着线程处于waiting的状态可以通过一些特点条件关联的状态来形成混合状态。
- blocked
当线程处于blocked状态,表示正在等待监视器锁以进入同步带模块或者某方法或者是调用Object.wait()
方法之后重新进入同步代码块或者是某方法
同步语句需要两个线程都不互相持有对方需要的监视器锁,需要在执行完代码块或者是方法之后,再释放锁资源。当正在执行的线程持有锁时,其他线程无法获取到这个锁,只有进入blocked状态来等待获取到锁资源。 - terminated
当线程执行玩run()
方法中的任务之后便进入terminated状态
这些状态是JVM向程序报告的当前的状态。在线程执行线的任意一个节点上,只可能有一种状态。
下面看这张状态转换图,反正我看的头晕,不知道你晕不晕:
线程的启动
继承方式
直接上例子:
package org.strongme;
public class ThreadExtend extends Thread {
private String name;
public ThreadExtend() {
}
public ThreadExtend(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name+" 运行 "+i);
}
}
public static void main(String[] args) {
ThreadExtend te1 = new ThreadExtend("A");
ThreadExtend te2 = new ThreadExtend("B");
te1.start();
te2.start();
}
}
运行结果:
A 运行 0
A 运行 1
B 运行 0
A 运行 2
A 运行 3
B 运行 1
A 运行 4
B 运行 2
B 运行 3
B 运行 4
接口方式
直接上例子:
package org.strongme;
public class ThreadInterface implements Runnable {
private String name;
public ThreadInterface() {
}
public ThreadInterface(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name+" 运行 "+i);
}
}
public static void main(String[] args) {
ThreadInterface ti1 = new ThreadInterface("A");
Thread t1 = new Thread(ti1);
ThreadInterface ti2 = new ThreadInterface("B");
Thread t2 = new Thread(ti2);
t1.start();
t2.start();
}
}
运行结果:
A 运行 0
B 运行 0
B 运行 1
A 运行 1
A 运行 2
A 运行 3
A 运行 4
B 运行 2
B 运行 3
B 运行 4
线程之Thread.sleep()
使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。
例如有两个线程同时执行(没有
synchronized
)一个线程优先级为MAX_PRIORITY
,另一个为MIN_PRIORITY
,如果没有sleep()
方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)
后,低优先级就有机会执行了。总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会
直接上例子:
package org.strongme;
public class ThreadSleep implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ i);
}
}
public static void main(String[] args) {
ThreadSleep ts = new ThreadSleep();
Thread t = new Thread(ts, "测试休眠");
t.start();
}
}
运行结果
测试休眠0//等2秒
测试休眠1//等2秒
测试休眠2
线程之new Thread(...).join()
主线程生成了子线程,而子线程要进行大量耗时的运算,主线程有需要用到子线程的处理结果,只有等到子线程执行完毕才可以继续执行主线程(wait for this thread to die)。
上例子:
package org.strongme;
public class ThreadJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("Thread "+Thread.currentThread().getName()+" running to "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
Thread t = new Thread(tj1,"线程1");
Thread t2 = new Thread(tj2,"线程2");
t.start();
t.join();
t2.start();
}
}
[with join()
]运行结果:
Thread 线程1 running to 0
Thread 线程1 running to 1
Thread 线程1 running to 2
Thread 线程2 running to 0
Thread 线程2 running to 1
Thread 线程2 running to 2
[without join()
]运行结果:
Thread 线程2 running to 0
Thread 线程1 running to 0
Thread 线程2 running to 1
Thread 线程1 running to 1
Thread 线程1 running to 2
Thread 线程2 running to 2
看上面两种不同的运行结果,应该可以看出个究竟了吧。
线程之new Thread(...).yield()
>