Java 在语言级别提供了支持多线程开发需要的类、接口和相关方法,支持线程的设计是JAVA的重要特征之一。
跟进程比较,线程的特点:
线程是程序中的一个执行流,一个执行流是由CPU运行程序代码并操作程序的数据形成的。JAVA 中的线程模型就是一个CPU、程序代码和数据的封装体。线程在JAVA 中是由 java.lang.Thread 类来定义和描述的,程序中的线程都是 Thread 的实例。
JAVA 提供了一下两创建线程的方法:
方法 run() 是线程在运行时所要执行的动作,但是在执行线程过程中,并不是直接调用该方法,而是在由该类创建实例对象后,通过方法start()来启动线程。
接口 Runnable 只有一个抽象方法 run(),在实现该接口的类中需要重写该方法。
在线程运行时,实现了接口 Runnable 的对象需要由类 Thread 封装为线程实例,类 Thread 的构造方法可以接受接口 Runnable 的实例,然后线程实例通过 start() 方法启动线程。
两种创建线程的方法有所区别,各有利弊。
1、使用继承类 Thread 的方法相对简单,比较直观,但是由于 JAVA 语言是单继承机制,使得一个类继承了类 Thread 之后不能再继承其他类。
2、通过实现 Runnable 接口的方法来创建线程时,虽然在生成线程实例时需要再通过 Thread 实例对 Runnable 实例进行封装;但定义时该类可以再继承其他类。
Thread 类不支持线程重用。在处理多任务时,通常会创建多个线程,让一个线程执行一个任务。其实也可以创建几个线程,然后重用这些线程执行任务,相关可以学习线程池。
线程本身有一些特有的属性,例如:线程标识符、线程名以及线程间的优先级属性等。通过这些属性,可以用来识别一个线程、了解线程的状态、控制线程的优先权等。
该属性为每一个线程存储了唯一的一个标识符,通过线程标识符,可以对线程进行区分。使用方法getId()。
thread:this.getId()
Runnable: Thread.currentThread().getId()
每个线程默认有一个名字,默认的名字采用 Thread-1,Thread-2 ….形式;也可以对创建的线程设置线程名。通过getName()可以获取线程名。
一个线程从创建、运行到终止称为一个生命周期。线程在其生命周期中要经历创建、就绪、运行、阻塞和终止5中状态。通过getState()获取运行状态。
1、创建 New
当一个 Thread 类或其子类使用 new 关键词声明一个对象实例时,此时线程处于创建的状态。处于创建状态的线程有自己的内存空间,但是线程还没有运行。
2、就绪 Runnable
处于创建状态的线程通过调用start()方法启动后,线程进入就绪状态。这时的线程就已经拥有了运行所需的所有条件,将进入线程队列等待CPU调度。由于还没有获取到CPU的时间片,所以不会立即执行。
3、运行 Running
当处于就绪状态的线程被调度,并获得CPU资源时,便进入运行状态。
4、阻塞 Blocked
在某些情况下,一个正在运行的线程会让出正在使用的CPU资源,进入阻塞状态。
为了提供CPU的利,当一个线程被阻塞时,另一个线程就获得了运行的机会。
5、终止 Terminated
线程到达终止状态可能有几种原因:
在 JAVA 语言中,每个线程都有一个优先级,不同线程可以被赋予不同的优先级。
通过 getPriority()
方法获取线程优先级,通过 setPriority()
方法设置线程优先级。
线程优先级共分为10个级别,最低为1,最高为10,默认优先级为5。
前面提到的都是用户线程,另外还有一个是守护线程。两者没有什么太大不同,守护线程的唯一作用是为用户线程提供服务。
通过 setDaemon()
方法,将线程设置为守护线程。
当线程启动后,可以管理线程,使线程休眠、等待或中断执行等。
在某些情况下,一个线程需要等另外一个线程执行结束后,才能执行。这种功能可以使用 join()
方法实现。
当调用了某个线程类的对象实例的 join()
方法,则会等待该对象执行结束。
线程的 sleep()
方法使一个线程暂停一段固定的时间,该方法能够把CPU让给优先级比其低的线程。
为了防止某个线程独占 CPU 资源,可以让当前执行的线程休息一下, yield()
方法可以实现该功能。
yield()
方法用于使当前线程让出CPU的使用权,但是这并不能保证CPU接下来调用的不是该线程。
一个线程除了正常执行结束外,也可以人为地中断线程的执行,线程的中断可以使用 interrupt()
方法。
除非是一个线程正在尝试中断它本身,否则中断的请求一般都会被接受。如果一个线程由于调用wait()方法或join()方法正处于阻塞队列中,则中断请求不会被响应,并会抛出 InterruptedExcepton 异常。
在程序中调用 interrupt() 方法后,通过需要在线程的 run() 方法中使用类 Thread 的方法 isInterrupted() 进行判断。
用于管理线程的方法,还有stop()、suspend()和resume()等,这里就不说明了。
如果有若干个正在做同一个工作的线程,为了方便管理,可以对这些线程进行分组,从而把同一组的线程作为一个整体进行操作。
在线程 Thread 的构造方法中,可以指明线程属于哪一个分组:
public Thread(ThreadGroup group,Runnable target)
其中,参数group 可以指明该线程属于哪一个线程组。
在并发编程过程中,有一个疑问:在程序中创建多少个线程最合适?创建多少个线程才能使程序性能运行最佳?
线程数目的多少对程序性能有一定的影响。
数目少了,处理器会处于闲置状态,浪费资源。
数目多了,多余的线程不能马上执行,浪费了软件资源。
在 JAVA 中提供了相关的方法,可用于获取处理器可以同时处理的最大线程数:
int nthreads = Runtime.getRuntime().availableProcessors()
类 ThreadLocal 是一个非常有用的类。 JAVA 通过类 ThreadLocal 实现线程本地对象,使用类 ThreadLocal 将会使变量在每个线程的私有区域内有一个副本,每个线程都可以相对独立的改变自己的副本,而不会影响到其他线程的副本。值得说明的是,ThreadLocal 并不是一个线程,而是表示线程的一个局部变量。
ThreadLocal 类提供了方法 set()
和 get()
用于设置和读取线程的本地值。一个线程首次获取一个线程本地对象值的时候将调用 initialValue()
方法,该方法用于对每个线程对象进行初始化。
无论是从 Thread 类继承还是实现 Runnable 接口来创建线程,方法 run()
是没有返回值的。带返回值的线程可以通过 Callable 来定义,通过 Future 接口来获得线程的返回值。
接口 Callable 和 Runnable 类似,其定义的一般形式:
public interface Callable<V> { V call() throws Exception; }
其中,类型参数 V 指明了线程返回值的类型。Callable<Integer> 将返回一个 Integer 型的值。
使用接口 Callable 创建的线程必须实现 call()
方法, call()
方法的返回值类型由 V 来指定。
Future 接口允许在未来某个时间获得线程运行的结果,它保存了使用 Callable 接口定义的线程异步运行的结果。当启动一个 Future 对象后,相当于启动了一个计算, Future 对象的计算结果在计算好后得到。
Future 接口的常用方法:
FutureTask 包装器同时实现了接口 Runnable 和 Callable,它可以很方便地对 Callable 对象进行封装并转换成 Future 对象。
public FutureTask(Callable<V> callable) {} public FutureTask(Runnable runnable, V result) {}