Java学习笔记-Day36 Java 多线程(一)
一、多线程的简介
1、程序、进程和线程
程序:是一组静态的计算机指令的集合,不占用系统运行资源,不能被系统调度,也不能作为独立运行的单位,它以文件的形式存储在磁盘上。
进程:是一个程序在其自身的地址空间中的一次执行活动。进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源。而程序不能申请系统资源,一个程序可以对应多个进程。
线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程 是系统运行程序的 基本单位,拥有独立的内存空间和系统资源。线程 是进程中执行运算的 最小单位,系统将处理器分配给线程,所以,真正在处理器上运行的是线程。
2、多线程的优点
(1)可以更好的实现并行。
(2)恰当地使用线程时,可以降低开发和维护的开销,并且能够提高复杂应用的性能。
(3)CPU在线程之间开关时的开销远比进程要少得多。因开关线程都在同一地址空间内,只需要修改线程控制表或队列,不涉及地址空间和其他工作。
(4)创建和撤销线程的开销较之进程要少。
3、Java在多线程应用中的优势
Java在语言级提供了对多线程程序设计的支持。多线程操作会增加程序的执行效率。各线程之间切换执行,时间比较短,看似是多线程同时运行,但对于CPU来说,某一个时刻只有一个线程在运行。
二、Java线程的生命周期
(1)新建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片和其他线程运行资源。
(2)就绪状态:在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。
(3)运行状态:就绪状态的线程获得CPU就进入运行状态。
(4)等待/阻塞状态:线程运行过程中被剥夺资源或者等待某些事件就进入等待/阻塞状态,suspend()方法被调用,sleep()方法被调用,线程使用wait()来等待条件变量,线程处于I/O等待等等,调用suspend方法将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,但是并不释放锁,所以容易引发死锁,直至应用程序调用resume方法恢复线程运行。等待事件结束或者得到足够的资源就进入就绪状态。
(5)死亡状态:当线程体运行结束或者调用线程对象的stop方法后线程将终止运行,由JVM收回线程占用的资源。
三、三种创建线程的方式
1、Thread类
对于Java而言,每一个独立的线程都是java.lang.Thread类的一个对象,而线程中独立于其他线程所执行的指令代码由Thread类的run方法提供。因此,要想在Java中实现多线程协作运行,就需要创建多个独立的Thread类对象。
1.1、构造方法
(1) Thread()
:创建一个新的线程。
(2) Thread(String name)
:创建一个指定名称的线程。
(3) Thread(Runnable target)
:利用Runnable对象创建一个线程,启动时将执行该对象的run方法。
(4) Thread(Runnable target, String name)
:利用Runnable对象创建一个线程,并指定该线程的名称。
1.2、Thread的部分方法
(1)public void start()
:启动该线程,使该线程开始执行,Java虚拟机调用该线程的run()方法,多次启动一个线程是不允许的,特别是当该线程已经结束后,就不能再重新启动。结果是两个线程同时运行:当前线程(从start方法的调用返回)和另一个线程(执行其run方法)。
注意:start方法会调用本地方法start0方法(JVM调用),JVM再调用run方法。
(2)public void run()
:如果该线程是使用独立的Runnable运行构造的,则调用该Runnable对象的run方法。如果这个类是一个线程类,只要启动线程,就会执行run()方法。
(3)public static void sleep(long millis) throws InterruptedException
:使该线程睡眠(暂停执行)millis毫秒,此时,该线程不会丢失任何监听器所属权。
注意:同一个线程只能调用start()方法一次,多次调用会抛出java.lang.IllegalThreadStateException
。而run()方法可以进行多次调用,因为它只是一种正常的方法调用。启动一个线程,需要调用start()方法而不是run()方法。此时,当前线程会被添加到线程组中,进入就绪状态,等待线程调度器的调用,若获取到了资源,则能进入运行状态,run()方法只是线程体,即线程执行的内容,若没调用start()方法,run()方法只是一个普通的方法。
1.3、通过Thread类创建线程类
(1)创建一个继承Thread类的自定义类。
(2)重写自定义类中的run方法。
(3)创建自定义类的对象。
(4)调用 start 方法来启动线程。
/**
* 线程实现方式1:继承Thread类
*/
public class TestThread01 {
public static void main(String[] args) {
T1 t1 = new T1();//新线程
t1.start();//进入就绪状态
T2 t2 = new T2();//新线程
t2.start();//进入就绪状态
}
}
//T1继承Thread类
class T1 extends Thread {
/**
* 重写run方法
*/
@Override
public void run() {
System.out.println("t1 -> hello");
}
}
//T2继承Thread类
class T2 extends Thread {
/**
* 重写run方法
*/
@Override
public void run() {
System.out.println("t2 -> world");
}
}
注意:
① 需要注意run()方法的签名。
② run()方法中的代码和其他方法中的代码区别在于:它可以和其他线程的run()方法代码 并行执行。
③ 线程不具备任何循环特性,一旦run()方法代码执行结束,线程的生命周期即会自动结束,因此如果需要在线程中循环执行某些代码需要自行声明循环控制。
④ 可以通过标记位结束线程。
1.4、继承Thread类实现线程的方法的局限性
由于Java是典型的单亲继承体系,因此一旦类继承Thread之后就不能再继承其他父类,对于一些必须通过继承关系来传播的特性显然会造成困扰。在上述情况下,可以通过实现 java.lang.Runnable 接口的方式来实现线程。
2、Runnable接口
在Runnable接口中只有一个签名和Thread中一致的run()方法,满足函数式接口的要求,可以使用Lambda表达式。
Runnable接口的子类并不是线程类,只是通过这种形式向线程类提供run()方法指令代码,最后还需借助Thread类和Runnable接口的依赖关系和Thread类的Thread(Runnable runnable)构造方法来创建线程对象。
切记Runnable不是线程类,而是一个实现线程为其提供run()方法指令代码的工具,因此创建其对象之后需要将其作为Thread类的构造方法参数以创建实际的线程对象。
1.1、通过Runnable接口实现线程
(1)创建一个实现Runnable接口的自定义类。
(2)在自定义类中实现Runnable接口的run方法。
(3)创建自定义类的对象。
(4)将自定义类的对象作为Thread类的构造方法的参数来创建线程对象。
(5)线程对象调用 start 方法来启动线程。
public class TestRunnable {
public static void main(String[] args) {
//创建TR1对象
TR1 tr = new TR1();
//将TR1对象做为Thread构造方法的参数
Thread t = new Thread(tr);
t.start();
TR2 tr2 = new TR2();
Thread t2 = new Thread(tr2);
t2.start();
}
}
//实现Runnable接口
class TR1 implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("TestRunnable1");
}
}
}
//实现Runnable接口
class TR2 implements Runnable{
@Override
public void run() {
while(true) {
System.out.println("TestRunnable2");
}
}
}
1.2、实现方式
1.2.1、独立类
public class TestRunnable {
public static void main(String[] args) {
//创建TR1对象
TR1 tr = new TR1();
//将TR1对象做为Thread构造方法的参数
Thread t = new Thread(tr);
t.start();
}
}
//实现Runnable接口
class TR1 implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("TestRunnable1");
}
}
}
1.2.2、匿名内部类
public class TestRunnable {
public static void main(String[] args) {
// 匿名内部类
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("TestRunnable1");
}
};
new Thread(r).start();
}
}
1.2.3、匿名内部类 (lambda表达式)
可以使用lambda表达式(在JDK1.8中)的条件:
① 在接口中只有一个抽象方法。
② 在接口前有 @FunctionalInterface 注解。
public class TestRunnable {
public static void main(String[] args) {
new Thread(r).start();
// 匿名内部类(lambda表达式)
Runnable r2 = () -> {
System.out.println("TestRunnable2");
};
new Thread(r2).start();
// 匿名内部类(lambda表达式),只有一行代码时可以去掉{}
Runnable r3 = () -> System.out.println("TestRunnable3");
new Thread(r3).start();
}
}
3、Callable接口
Callable接口实际上是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更加强大的功能。Callable可以在任务结束的时候提供一个返回值,Runnable无法提供这个功能。Callable的call方法分可以抛出异常,而Runnable的run方法不能抛出异常。
1.1、通过Callable接口实现线程
(1)创建一个实现Callable接口的自定义类。
(2)在自定义类中实现Callable接口的call方法。
(3)创建自定义类的对象。
(4)将自定义类的对象作为FutureTask类的构造方法的参数来创建FutureTask类的对象。
(5)将FutureTask类的对象作为作为Thread类的构造方法的参数来创建线程对象(FutureTask< T > 实现了RunnableFuture< T > 接口,RunnableFuture< T > 接口实现了Runnable接口)。
(6)线程对象调用 start 方法来启动线程。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestCallable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<String> mycall = new Mycall();
FutureTask<String> ft = new FutureTask<String>(mycall);
Thread t = new Thread(ft);
t.start();
String s = ft.get();
System.out.println(s);
}
}
class Mycall implements Callable<String>{
@Override
public String call() throws Exception {
double random = Math.random();
System.out.println("call...");
return "call:random is "+random;
}
}