1.什么是线程/进程
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
简单来说,进程就是一个运行的程序,而线程就是这个程序中执行不同任务的运作单元。就好像吃饭这个行为相当于程序,具体的吃米饭、吃菜、喝汤则是其中不同的线程。
本篇主要介绍线程。
2.如何创建线程
Java中提供了三种创建方式 1.实现Runnable接口
2.继承Thread类
3.通过Callable和Future(很少使用)
*要注意的是,Java中能够创建线程的其实只有Thread类对象,其他方法也只是基于Thread类对象来创建的。
2.1 实现Runnable接口
要实现Runnable,我们要先清楚它的调用过程。其中有几个重要的元素和方法:
//可以被重写,在其中调用其他方法、使用其他类、声明变量
public void run();
//创建一个线程的实例化对象,其中threadOb是实现Runnable接口的类实例,threadName是新线程的名称
Thread(Runnable threadOb,String threadName);
//start()是真正启动这条线程的方法
void start();
第一步要明确该线程想要进行的操作,也就是run()方法内部的内容。run()会先等待start()来调用线程,待到线程被调用以后,才会执行run()中的操作。它并不会主动去执行!
public void run() {
//线程开始运行
System.out.println("开始运行:" + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("当前输出:" + threadName + ", 结果为:" + i);
//进程睡眠时间:50ms
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("线程:" + threadName + "异常");
}
System.out.println("线程:" + threadName + "运行结束");
}
第二步则是编写真正启动该线程的部分——start()方法。在创建好线程以后,调用该线程的start()即可自动运行run()中的内容。此处我们在重写的start()中,根据当前类(包含了run())创建了一个新的线程,最后调用Thread中的start()方法启动线程。
public void start () {
//线程准备就绪,准备运行
System.out.println("准备运行:" + threadName );
if (thread == null) {
//创建新线程,this意为将该对象放入线程中
thread = new Thread(this, threadName);
//线程开始运行
thread.start();
}
}
最后一步就是将Runnable接口实现类实例化,创建新线程并且启动,总而执行run()中的内容。
class RunnableDemo implements Runnable{
//Runnable实现类的有参构造器,传入一个name参数来作为threadName进程名
RunnableDemo(String name) {
threadName = name;
//正在创建线程
System.out.println("正在创建:" + threadName );
}
}
//创建实例化对象——创建了新的Thread类“线程1”
RunnableDemo R1 = new RunnableDemo("线程1");
//调用start()方法启动线程
R1.start();
再强调一下!Runnable接口的实现类并不是线程,它只是重写了run()方法。必须将它的实例对象放入一个新线程new Thread(R1),才可以调用start()方法启动线程!
最后整体演示一下Runnable接口实现多线程的过程:
class RunnableDemo implements Runnable {
//通过实现Runnable接口创建多线程
//创建一个线程Thread类
private Thread thread;
private String threadName;
//类的有参构造器,传入一个name参数来作为threadName进程名
RunnableDemo( String name) {
threadName = name;
//正在创建线程
System.out.println("正在创建:" + threadName );
}
public void run() {
//线程开始运行
System.out.println("开始运行:" + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("当前输出:" + threadName + ", 结果为:" + i);
//进程睡眠时间:50ms
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("线程:" + threadName + "异常");
}
System.out.println("线程:" + threadName + "运行结束");
}
public void start () {
//线程准备就绪,准备运行
System.out.println("准备运行:" + threadName );
if (thread == null) {
//创建新线程
thread = new Thread (this, threadName);
//线程开始运行
thread.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
//创建线程1
RunnableDemo R1 = new RunnableDemo( "线程1");
//线程启动
R1.start();
RunnableDemo R2 = new RunnableDemo( "线程2");
R2.start();
}
}
运行结果如下:
正在创建:线程1
准备运行:线程1
正在创建:线程2
准备运行:线程2
开始运行:线程1
开始运行:线程2
当前输出:线程2, 结果为:4
当前输出:线程1, 结果为:4
当前输出:线程1, 结果为:3
当前输出:线程2, 结果为:3
当前输出:线程1, 结果为:2
当前输出:线程2, 结果为:2
当前输出:线程1, 结果为:1
当前输出:线程2, 结果为:1
线程:线程1运行结束
线程:线程2运行结束
2.2 继承Thread类
通过创建一个类来继承Thread类,来创建新的线程。这种方法和实现Runnable接口基本上是相同的,本质上就是实现Runnable接口的一个实例。
直接上一个例子,大伙看看就好。
class ThreadDemo extends Thread {
//通过实现继承Thread创建多线程
//创建一个线程Thread类
private Thread thread;
private String threadName;
//类的有参构造器,传入一个name参数来作为threadName进程名
ThreadDemo(String name) {
threadName = name;
//正在创建线程
System.out.println("正在创建:" + threadName );
}
public void run() {
//线程开始运行
System.out.println("开始运行:" + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("当前输出:" + threadName + ", 结果为:" + i);
//进程睡眠时间:50ms
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("线程:" + threadName + "异常");
}
System.out.println("线程:" + threadName + "运行结束");
}
public void start () {
//线程准备就绪,准备运行
System.out.println("准备运行:" + threadName );
if (thread == null) {
//创建新线程
thread = new Thread (this, threadName);
//线程开始运行
thread.start();
}
}
}
public class TestThread {
public static void main(String args[]) {
//创建线程1
ThreadDemo T1 = new ThreadDemo( "线程1");
//线程启动
T1.start();
ThreadDemo T2 = new ThreadDemo( "线程2");
T2.start();
}
}
3.线程中的方法
除了我们上面用到过的start()和run(),Thread类其实还包含很多方法。
//改变线程名称,使之与参数 name 相同
public final void setName(String name);
//更改线程的优先级
public final void setPriority(int priority);
//将该线程标记为守护线程或用户线程
public final void setDaemon(boolean on);
//等待该线程终止的时间最长为 millis 毫秒
public final void join(long millisec);
//中断线程
public void interrupt();
//测试线程是否处于活动状态
public final boolean isAlive();
还有一些静态方法,其中就有我们最常用的休眠方法sleep()。
//暂停当前正在执行的线程对象,并执行其他线程
public static void yield();
//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响
public static void sleep(long millisec);
//当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true
public static boolean holdsLock(Object x);
//返回对当前正在执行的线程对象的引用
public static Thread currentThread();
//将当前线程的堆栈跟踪打印至标准错误流
public static void dumpStack();
需要特别提一下的是,setDaemon()方法中提到了一个概念叫做“守护线程”与“用户线程”,这里我们详细解释一下。
只要有一个线程没有退出(执行完毕),JVM进程就不会退出,因此JVM的退出条件就是所有线程都结束。但在具体业务逻辑中,会有一些必须要无限循环的线程,例如每运行一小时就提示一次“您已使用一小时,请注意护眼”。
while(true){
System.out.println("您已使用一小时,请注意护眼");
try{
//休眠时间为一小时
Thread.sleep(3600000);
} catch(InterruptedException e){
break;
}
}
这个线程是无法自行结束的,那JVM岂不是永远无法退出了?这时就要运用到“守护线程”了。在不使用setDaemon()方法来指定线程属性时,默认为false“用户线程”,在用户线程没有结束以前,JVM都不会退出。但如果指定该线程为“守护线程”,所有非守护线程执行完毕后,不管JVM进程中还有没有运行中的守护线程,都会直接结束。
所以对于一些类似于心跳检测、事件监听、GC(垃圾回收)等独立于用户线程之外的线程,我们会将它设置为守护线程。
Thread t = new Thread();
//设置true为守护进程,false为用户进程,默认为false
t.setDaemon(true);
t.start();