目录
线程简介
操作系统运行一个程序时,会为其创建一个进程,一个进程里可以创建多个线程,线程为操作系统调度的最小单位,每个线程拥有各自的计数器、堆栈、、局部变量等属性,并且可以访问共享变量,CPU在这些线程上高速切换调度执行,早期CPU由单核实现,同一时刻只能调度一个线程执行,并不存在多个线程并发执行的情况,随着科技不断进步,现代CPU基本都为多核,并且超线程技术的广泛运用,CPU并行执行任务能力大大提升,为了充分利用CPU的资源,提升程序的运行效率,减少程序的运行时间,多线程编程已成为当代编程的必选之路。
线程实现
实现线程主要有三种方式:
1.使用内核线程实现
内核线程(Kernel-Level Thread,KLT)就是直接有操作系统内核(Kernel)支持的线程,这种线程有内核来完成线程切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上,程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口—轻量级进程(Light Weight Process,LWP),轻量级线程和内核线程之间1:1的关系成为1对1线程模型
2.使用用户线程实现
从广义上讲,一个线程只要不是内核线程就是用户线程(User Thread,UT),从这个定义来说,轻量级进程也属于用户线程,但轻量级线程是建立在内核线程上的,许多操作都需要进行系统调用,效率受到限制,而狭义的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现,用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助,不需要切换到内核态,操作是非常快切低消耗的,这种进程与用户线程之间1:N的关系成为一对多的线程模型,使用用户线程的优势在于不需要内核支援,劣势也在于没有内核支援,所有的线程操作都需要用户程序自己处理,线程的创建、切换和调度都是需要考虑的问题
3.使用用户线程+轻量级进程混合实现
线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式,这种方式既存在用户线程,也存在轻量级进程,用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发,而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能以及处理器映射,用户线程的系统调用通过轻量级线程来完成,这种混合模式用户线程和轻量级进程数量比是不确定的,因此称为N:M的关系(多对多线程模型)
Java线程的实现:java线程在JDK1.2之前使用用户线程实现,目前对于Sun JDK,Windows版和Linux版都是使用一对一的线程模型(即内核线程实现),而在Solaris平台,由于操作系统支持一对一和多对多的线程模型,在Solaris版的JDK中提供了两个平台的专有虚拟机参数:
-XX:UseLWPSynchronization(默认值 一对一线程模型)
-XX:UseBoundThreads( 多对多线程模型)
JAVA线程调度
线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种:
1.协同式线程调度(Cooperative Threads-Scheduling)
如果使用协同式调度的多线程系统,线程执行时间有线程本身来控制,线程执行完自己的任务后主动通知系统切换到另外一个线程,协同式线程最大的好处是实现简单,切换操作对线程是可知的,所有没有什么线程同步的问题,但是如果线程出现问题,阻塞在某个线程上,那么后续线程将得不到系统的调度,整个程序将一直阻塞,导致系统崩溃
2.抢占式线程调度(Preemptive Threads-Scheduling)
如果使用抢占式调度的多线程系统,那么每个线程将有系统来分配执行时间,线程的切换不由线程本身决定,线程的执行时间是系统可控的,也不会有一个线程导致整个进程阻塞的问题,Java使用的这种线程调度方式
JAVA线程的状态
线程在运行的生命周期中可能处于6中不同的状态,在给定的一个时刻,线程只能处于其中的一个状态
1)NEW:初始状态,线程被构建,但是还没有调用start()方法
2)RUNNABLE:运行状态,Java线程将操作系统中的就绪和运行状态统称为"运行中"状态,表示线程已经准备就绪或者这在被CPU调度执行
3)BLOCKED:阻塞状态,当线程在抢占锁失败是则进入阻塞状态,直到锁释放后唤醒该线程继续争夺锁
4)WAITING:等待状态,线程进入等待状态后需要通过其他线程作出唤醒动作(通知或中断)进行唤醒,否则将永远处于等待
5)TIME_WAITING:超时等待状态,不同于WAITING,它是可以在指定的时间自动返回的
6)TERMINATED:终止状态:表示该线程已经执行完毕
JAVA新建线程
1.继承Thread,重写run方法
class ThreadTest1 extends Thread{
@Override
public void run() {
System.out.println("ThreadTest1 run");
}
public static void main(String[] args) {
ThreadTest1 threadTest1 = new ThreadTest1();
//调用start方法启动线程
threadTest1.start();
}
}
2.实现Runnable接口,重写run方法
class ThreadTest2 implements Runnable{
@Override
public void run() {
System.out.println("ThreadTest2 run");
}
public static void main(String[] args) {
Thread thread = new Thread(new ThreadTest2());
thread.start();
}
}
3.实现Callable接口,重写call方法
class ThreadTest3 implements Callable<Void> {
@Override
public Void call() throws Exception {
System.out.println("ThreadTest3 run");
return null;
}
public static void main(String[] args) {
FutureTask futureTask = new FutureTask(new ThreadTest3());
Thread thread = new Thread(futureTask);
thread.start();
}
}
JAVA线程Thread类
Thread类是java提供的线程基础类,可以通过线程的一些方法对线程的状态控制或者获取线程的状态,下面分析线程Thread的关键方法
1). sleep方法将该线程的Runnable转态进入TimeWaiting状态,如果该线程持有资源锁时,并不释放锁,如果线程被打断将抛出InterruptedException异常,如果没有捕获该异常,run方法将结束执行
public static native void sleep(long millis) throws InterruptedException;
2). start方法为Thread启动线程的方法,启动一个线程一定要执行该方法,而不是执行run方法,如果执行run方法则和执行普通方法一致,该方法执行立即返回,并且启动一个新的线程执行run方法
public synchronized void start(){};
3).向线程发送中断请求,线程的中断状态将被设置为true,如果该线程阻塞调用则抛出InterruptedException异常中断该线程执行,可以通过该方法在结束线程,而不是使用stop方法,stop方法为强制结束线程,存在安全隐患,stop方法已被JDK废弃,使用interrupt只是将中断请求发送,但线程具体怎么响应中断,是由线程本身决定
public void interrupt() {};
JAVA线程属性
线程的属性包括:
1.线程优先级
一个线程默认继承它的父线程的优先级,Java将线程优先级设置在1(MIN_PRIORITY)-10(MAX_PRIORITY)之间的任何值,线程优先级是高度依赖操作系统,Java线程的优先级被映射到宿主平台的优先级上,线程优先级是不可靠的,对于某些平台优先级将被忽略,不要将程序构建的正确性依赖于优先级
2.守护线程
线程的setDaemon(true)将线程转换为守护线程,守护线程的唯一用途是为其他线程提供服务,当程序中只剩下守护线程,虚拟机也将退出结束程序,守护线程不应该访问固有资源,如文件,数据库等,因为它在任何时候可能会发生中断
3.线程组
线程组是一个可以统一管理的线程集合,默认情况下,创建的所有线程属于同一线程组,但是也可能会建立其他的组,建议不要在自己的程序中使用线程组
4.未捕获异常处理器
线程run方法不能抛出任何受检查异常,但是非受检查异常会导致线程终止,在线程死亡之前,异常会被传递到一个用于未捕获异常的处理器,该处理器是一个实现了Thread.UncaughtExceptionHandler接口的类,这个接口只有一个方法
void uncaughtException(Thread t, Throwable e);
如果不设置处理器,默认处理器为空,如果需要在线程异常时做一些后续的处理(如关闭资源、记录日志等)可以使用这种方式进行操作
线程安全
什么是线程安全:
当多个线程访问一个对象是,如果不用考虑线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。反之这个对象为线程不安全的。
线程不安全的原因:
a.多个线程共享同一资源;
b.没有对资源访问进行同步措施保护;
线程不安全的影响:
程序在多线程环境下运行时,可能出现与预期结果不一致的现象,导致程序异常或者崩溃,在实际开发中一定要识别到线程安全的重要性
解决办法:
同步共享资源的访问,同一时刻只能有一个线程使用该共享资源,保证操作结果的正确性,在java中可以使用synchronized关键字来实现同步,或者在JDK1.5版本以上使用Lock实现资源同步
参考:
《深入理解Java虚拟机:JVM高级特性与最佳实践》
《JAVA核心技术卷1》
(转载请注明出处
)