参考书籍:《Java核心技术卷1》第14章
一、线程和进程
1.1 定义
进程是计算机正在执行的一个独立的应用程序,是一个动态的概念,即必须是进行状态,如果一个应用程序没有启动,就不是进程
线程是组成进程的基本单位,可以完成特定的功能,一个进程由一个或多个线程组成
1.2 区别与联系
- 区别:
(1)内存空间:进程是有独立的内存空间,进程之间相互独立,互不干扰;线程共享内存空间。
(2)安全性:进程之间相互独立,一个进程的崩溃不会影响到其他的进程;线程内存共享,一个线程的崩溃可能会影响到其他线程的执行,线程的安全性不如进程 - 联系:一个进程可以包含一个或多个线程
Java中很少用到进程概念,创建进程方式:
Runtime runtime = Runtime.getRuntime();
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("jps");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String msg = null;
while ((msg = reader.readLine()) != null){
System.out.println(msg);
}
java中创建线程的方式:java中多线程的创建需要调用start方法创建出新线程
private native void start0();
/**
start底层调用native修饰的start0()的方法,native需要借助于操作系统来执行,即java创建多线程的方式需要借助于底层操作系统的线程创建,线程创建交给底层C++的线程创建方式,java是对底层线程创建的封装
二、线程的创建方式
- 继承Thread类
- 实现Runable接口
- 实现callable接口
2.1 继承Runable接口
Runable接口定义:
public interface Runnable {
//抽象的run方法
public abstract void run();
}
通过接口定义可知,通过Runable接口实现多线程就需要实现该接口中的run方法。
public class RunnableDemo implements Runnable {
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName()+"的线程在执行");
}
}
public static void main(String[] args) {
//第一种:通过实现Runnable接口来创建线程
//执行任务体
RunnableDemo runnableDemo = new RunnableDemo();
//将任务体提交给线程,获取线程实例
Thread thread = new Thread(runnableDemo);
//当前才会创建子线程
thread.start();
System.out.println("main方法中线程名:"+Thread.currentThread().getName()+"线程在执行");
}
}
实现Runnable接口创建线程的步骤:
1.创建一个特定类,必须实现Runnable接口,并实现run方法
2.实例化特定类对象
3.创建Thread类对象,将特定类对象作为参数传递
4.启动子线程,调用Thread的类的start方法
2.2 继承Thread类
Thread类的定义:
public class Thread implements Runnable{
private Runnable target;
public void run() {
if (target != null) {
target.run();
}
}
}
Thread类本身实现了Runnable的接口,即Thread类是Runnable的实现类,Thread类也有run方法,run方法来执行任务体target,需要来重写run方法
继承自Thread类的Demo:
/**
* 继承自Thread类
*/
public class ThreadDemo extends Thread {
//重写run方法
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":子线程执行...");
}
}
public static void main(String[] args){
//继承自Thread类
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
System.out.println(Thread.currentThread().getName()+":main方法所在类正在执行...");
}
继承自Thread类创建线程的方式:
1.创建特定类,继承自Thread类,重写run方法
2.实例化创建类的对象
3.调用对象的start方法启动子线程
2.3 实现Callable接口
Callable接口声明如下:
public interface Callable<V> {
V call() throws Exception;
}
callable接口提供了call方法,具有返回值,通过泛型定义(控制返回值),该接口可以抛出异常
callable接口的不能直接使用,需要借助FutureTask类,FutureTask类可以接受Callable接口的实现类
FutureTask类定义如下:
public class FutureTask<V> implements RunnableFuture<V>{
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
}
public interface RunnableFuture<V> extends Runnable, Future<V>
FutureTask类是继承Runnable接口,即FutureTask是是Runnable接口的实现类,而FutureTask类提供了构造函数FutureTask(Callable callable) 可以接受Callable类型的任务,
可以实现将Callable类型实例转化为Runnable类型实例,交给Thread类处理
Callable接口的实现类Demo
public class CallableDemo implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+":子线程正在执行...");
return null;
}
}
public static void main(String[] args){
//实例化Callable接口实现类
CallableDemo callableDemo = new CallableDemo();
//FutureTask类实例
FutureTask <String> futureTask = new FutureTask <>(callableDemo);
Thread thread1 = new Thread(futureTask);
thread1.start();
}
实现Callable接口创建线程的步骤:
1.创建特定类,实现Callable接口,实现该接口call方法
2.创建特定类的实例
3.创建FutureTask实例,将Callable实例作为参数传入
4.创建Thread对象实例,将FutureTask实例作为参数传入(当做Runnable实例)
5.启动子线程,调用Thread类对象的start方法
2.4 匿名内部类形式实现
public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":匿名内部类实现...");
}
}).start();
System.out.println(Thread.currentThread().getName()+":main方法所在类正在执行...");
}
注: 线程的实现也可以是匿名内部类方式,可以归结为实现Runnable接口
Runnable接口和Thread类区别?
1、线程类继承自Thread则不能继承其他类(单继承),而Runnable接口可以
2、继承自Thread类相对于Runnable接口实现,创建线程的方法更简单
Callable接口和Runable接口的区别:
1、Callable接口方法为call(),而Runable接口方法为run()
2、Callable接口是有返回值,而Runable的接口的方法是不能返回值
3、call()方法是可以抛出异常的,run()是不能抛出异常
4、运行Callable任务可以拿到FutureTask的对象,通过该对象可以对子线程进行操作,获取结果.
三、线程状态及其转换
3.1 线程状态
在JDK中的Thread类中有一个子枚举类State:定义线程存在状态
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
在JDK中线程的状态有6种:
(1)新建状态(NEW)
用new语句创建的线程就处于新建状态,和Java对象一样,仅在堆中分配了内存,在调用start之前,就处于新建状态
(2)就绪状态(Runnable)
在一个线程对象创建之后,调用它的start方法,该线程就进入就绪状态,为线程创建栈和程序计数器。处于这个状态的线程位于可运行池中,等待获取CPU的执行权
(3)运行状态(Running)
处于就绪状态的线程获取了CPU的执行权,执行程序代码,只有处于就绪状态的线程才有机会到运行状态
(4)阻塞状态(BLOCKED)
在线程期望进入同步代码块或者同步方法中时会进入该状态(Synchronized或者Lock锁),等待资源(非CPU资源),等待到了资源线程就会进入就绪状态等待CPU调度
(5)超时等待(TIMED_WAITING)
如果线程执行了sleep(long time),wait(long time),join(long time),设置等待时间,当设定的等待时间到了,就会脱离阻塞状态
(6)终止状态(TERMINATED)
线程退出run()方法时,就进入终止状态,该线程结束生命周期
3.2 线程状态转换
一个线程的生命周期中需要状态:New、Runnable、Running和Terminated四个状态。当线程需要相应资源时,进入到阻塞状态,阻塞状态包含Blocked、Waiting、和Timed_Waiting状态