进程与线程
进程:
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU资源,程序开始真正运行。
线程
线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈来存储临时变量和局部变量。
线程的生命周期
- 新建状态
使用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start()这个线程。 - 就绪状态
当线程对象调用了start()方法之后,该线程就进入了就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。 - 运行状态
如果就绪状态的线程获取CPU资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变成阻塞状态、就绪状态和死亡状态。 - 阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入了阻塞状态。在睡眠时间已到或获得设备资源后就可以重新进入就绪状态。- 等待阻塞:运行状态中的线程执行了wait()方法,使线程进入等待阻塞状态。
- 同步阻塞:线程获取synchronized同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的sleep()、join()、发出I/O请求时,线程就会进入到阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程重新转入就绪状态。
- 死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
创建线程的方式
一、通过继承Thread类
Thread类中两个常用的构造方法是:
public Thread()//创建一个新的线程对象
public Thread(String threadName)//创建一个名称为threadName的线程对象
当一个类继承Thread类后,需要在该类中覆盖run()方法,将完成线程真正功能的代码写入run()方法中,然后调用Thread类中的start()方法启动线程。
例如:
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0;i < 20;i++) {
System.out.println("一边听歌");
}
}
}
测试代码:
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();//创建实例
myThread.start();//启动线程
for (int i = 0;i < 20;i++) {
System.out.println("一边敲代码");
}
}
}
运行结果:
在main()方法中,启动线程需要调用Thread类中的start()方法,start()方法调用被覆盖的run()方法。如果不调用start()方法,线程永远都不会启动,在没有调用start()方法之前,线程对象处于新建状态。
调用start()方法后并不是立即执行多线程代码,而是使得该线程变为可运行状态,线程什么时候调度运行由操作系统决定的。
注意:如果不调用start()方法启动线程,而是直接调用run()方法,那么就不是多线程而只是一个简单的方法调用。
二、通过实现Runnable接口
通过一个类继承Thread类来实现一个线程具有单继承的局限性,该子类不能再继承其他类,为了解决单继承的局限性,推荐通过实现Runnable()接口来实现一个线程。
启动一个通过实现Runnable接口实现的线程也需要通过Thread类对象的start()方法,Thread类中还有另外两个构造方法:
public Thread(Runnable target);//通过一个Runnable类型的对象实例化一个Thread对象
public Thread(Runnable target,String name); //通过一个Runnable类型的对象实例化一个名称为name的Thread对象
使用Runnable()接口启动新线程的步骤如下:
- 自定义类实现Runnable接口,并实现该接口的run()方法
- 用实现Runnable接口的对象作为参数(target)实例化一个Thread对象
- 调用Thread对象的start()方法
例如:
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0;i < 10;i++) {
System.out.println("一边听歌");
}
}
}
测试代码:
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
MyThread myThread = new MyThread(myRunnable);
myThread.start();
for (int i = 0;i < 10;i++) {
System.out.println("一边敲代码");
}
}
}
运行结果:
其实不管是通过继承Thread类还是通过实现Runnable接口来实现线程,最终都是通过Thread对象的API来控制线程(代理设计模式)。
Thread类本质上也是实现了Runnable接口,启动线程的唯一方法就是通过Thread类的start()方法。start()方法是一个native(本地)方法,它将与操作系统交互,执行run()方法(Thread类中提供的run()方法是一个空方法)。
注意:一个Native Method就是一个Java调用非Java代码的接口。我们把这类接口称为JNI(Java Native Interface),它提供了若干的API实现了Java和其他语言的通信(主要是C&C++),它允许Java代码和其他语言写的代码进行交互。Thread类的start()方法是一个native method,它是与操作系统进行交互。
三、通过Callable和Future创建线程
通过实现Callable接口,重写call()方法来实现一个线程。Callable接口是juc(java.util.concurrent)包下的一个泛型接口,只有一个call()方法。
Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能:
- Callable可以在线程结束后提供一个返回值,Runnable不能提供这个功能
- Callable接口中的call()方法可以抛出异常,而Runnable接口中的run()方法不能抛出异常,必须在run()方法中捕获。
- java5提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口,所以可以作为Thread的target。
使用Callable接口实现线程的步骤如下:
- 自定义类实现Callable接口,并实现该接口的call()方法
- 用实现Callable接口的对象作为参数实例化一个FutureTask对象
- 用FutureTask对象作为参数(target)实例化一个Thread对象
- 调用Thread对象的start()方法
- 调用FutureTask对象的get()方法获取结果
例如:
class MyCallable implements Callable {
private String str;
public MyCallable(String str) {
this.str = str;
}
@Override
public Boolean call() throws Exception {
for (int i = 0;i < 10;i++) {
System.out.println("一边"+str);
}
return true;
}
}
测试代码:
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallable mc1 = new MyCallable("唱歌");
MyCallable mc2 = new MyCallable("敲代码");
FutureTask futureTask1 = new FutureTask(mc1);
FutureTask futureTask2 = new FutureTask(mc2);
new Thread(futureTask1).start();
new Thread(futureTask2).start();
//获取结果
boolean r1 = futureTask1.get();
boolean r2 = futureTask2.get();
System.out.println("线程1执行结果:" + r1);//打印结果
System.out.println("线程2执行结果:" + r2);//打印结果
}
}
四、线程池
五、简化线程的实现
- 内部类实现简化
对于只使用一次并且逻辑较简单的线程,为了提升性能我们可以使用内部类的方式对它的表示进行简化。 - Lambda表达式实现简化
lambda表达式就是在匿名内部类的基础上把接口名和方法名都删掉,只保留方法体。
注意:内部类只在外部类使用时才会加载,外部类不使用时不会加载,所以可以提升性能。