Java多线程编程基础
1. 进程与线程
进程与线程的基本认识
进程(Process):进程是程序的一次动态执行过程,它经历了从代码加载、执行、到执行完毕的一个完整过程;同时也是并发执行的程序在执行过程中分配和管理资源的基本单位,竞争计算机系统资源的基本单位。
线程(Thread):线程可以理解为进程中的执行的一段程序片段,是进程的一个执行单元,是进程内可调度实体,是比进程更小的独立运行的基本单位,线程也被称为轻量级进程。
为什么会有线程
每个进程都有自己的地址空间,即进程空间,在网络或多用户换机下,一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通,即使多进程可以提高硬件资源的利用率,但是进程的启动和销毁需要消耗大量的系统性能,从而导致程序的执行性能下降。所以为了进一步提升并发操作的处理能力,在进程的基础上又划分了多线程概念。
以B站看视频为例:
打开B站看视频可以理解为实现了一个“进程”,而在看的时候,会同时听到声音,看到图片,还有可以发弹幕等……这些都是多线程实现。
简而言之:一个程序至少一个进程,一个进程至少一个线程。
2. 多线程实现
在java中,如果要实现多线程,就必须依靠线程主体类,而java.lang.Thread
是java中负责多线程操作类,只需继承Thread类,就能成为线程主体类,为满足一些特殊要求,也可以通过实现Runnable接口或者Callable接口来完成定义。
具体方式如下:
1.继承Thread类,重写run方法(无返回值)
2.实现Runnable接口,重写run方法(无返回值)
3.实现Callable接口,重写call方法(有返回值且可以抛出异常)
2.1 Thread类实现多线程
通过继承Thread类,并重写父类的run()方法实现
public void run()
定义线程类:
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0 ; i < 50 ; i++) {
System.out.println(this.name + "正在工作中……" + i);
}
}
}
多线程启动:
public class testThread {
public static void main(String[] args) {
// 实例化线程对象
MyThread mt1 = new MyThread("线程一");
MyThread mt2 = new MyThread("线程二");
MyThread mt3 = new MyThread("线程三");
// 启动实例线程对象
mt1.start();
mt2.start();
mt3.start();
}
}
运行情况:
以上结果可以发现,它并不是按照顺序执行,而是以一种随机交替方式执行的,每次的结果都可能不一样。
通过以上代码可以知道,要想实现多线程,就要依靠Thread类的start()方法执行,线程启动后会默认调用run()方法。
2.2 Runnable接口实现多线程
使用Thread类的确可以实现多线程,但是也容易发现它的缺陷:面向对象的单继承局限,因此才采用Runnable接口来实现多线程。
该接口的定义如下(以下并不需要在代码中实现):
@FunctionalInterface
public interface Runnable{
public void run();
}
定义线程类:
public class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0 ; i<50 ;i++) {
System.out.println(this.name + " 正在执行中……" + i);
}
}
}
多线程启动:
public class testThread {
public static void main(String[] args) {
// 实例化继承Runnable接口的MyThread类
Runnable mt1 = new MyThread("线程一");
Runnable mt2 = new MyThread("线程二");
Runnable mt3 = new MyThread("线程三");
// 多线程启动
new Thread(mt1).start();
new Thread(mt2).start();
new Thread(mt3).start();
}
}
运行情况:
以上程序实例化三个继承Runnable接口的MyThread类,然后通过Thread类的一个构造函数public Thread(Runnable target)
分别实例化Thread类,再利用start()方法实现多线程。
Thread方案实现和Runnable方案实现的区别:
// Thread类定义:
public class Thread extends Object implements Runnable {}
也就是说,Thread类是Runable接口的子类,通过直接覆写Thread类的run方法实际上依然是覆写Runnable接口内的run方法,其实本质上是没有区别的,但是利用Runnable方案实现更加能体现面向对象思维,有点类似于代理设计模式。
2.3 Callable接口实现多线程
使用Runnable接口实现的多线程可以避免单继承的局限,但是还有一个问题就是run方法没有返回值,为了解决这个问题,所以提供了一个Callable接口java.util.concurrent.Callable
其定义如下:
@FunctionalIterface
public interface Callable<T>{
public T call() throws Exception;
}
FutureTask类常用方法:
import java.util.concurrent.ExecutionException; // 导入ExecutionException异常包
public FutureTask(Callable<T> callable) // 构造函数:接收Callable接口实例
public FutureTask(Runable runnable,T result) // 构造函数:接收Runnable接口实例,同时指定返回结果类型
public T get() throws InterruptedException,ExecutionException // 取得线程操作返回结果
Thread类的一个构造方法:
public Thread(FutureTask<T> futuretask) //构造方法:接收FutureTask实例化对象
定义线程主体类:
import java.util.concurrent.Callable;
public class MyThread implements Callable<Integer>{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public Integer call(){
Integer sum = 0;
for(int i = 0 ; i < 500;i++) {
System.out.println(this.name + i);
sum += i;
}
return sum;
}
}
多线程启动:
import java.util.concurrent.FutureTask;
public class testThread {
public static void main(String[] args) throws Exception{
// 实例化继承Callable接口的MyThread类
MyThread mt1 = new MyThread("线程一");
MyThread mt2 = new MyThread("线程二");
MyThread mt3 = new MyThread("线程三");
// FutureTask类接收继承Callable接口的MyThread的实例
FutureTask<Integer> ft1 = new FutureTask<Integer>(mt1);
FutureTask<Integer> ft2 = new FutureTask<Integer>(mt2);
FutureTask<Integer> ft3 = new FutureTask<Integer>(mt3);
// 启动多线程
new Thread(ft1).start();
new Thread(ft2).start();
new Thread(ft3).start();
System.out.println(ft1.get());
System.out.println(ft2.get());
System.out.println(ft3.get());
}
}
运行情况:
通过以上代码容易了解到,Callable接口实现采用泛型技术实现,继承需要重写call方法,再通过FutureTask包装器包装,传入后实例化Thread类实现多线程。
其中FutureTask类是Runnable接口的子类,所以才可以利用Thread类的start方法启动多线程,读者可以将call方法假设为有返回值的run方法。
2.3 多线程运行状态
任意线程具有5种基本的状态:
1.创建状态
实现Runnable接口和继承Thread类创建一个线程对象后,该线程对象处于创建状态,此时它已经有了内存空间和其它资源,但他还是处于不可运行的。
2.就绪状态
新建线程对象后,调用该线程的start方法启动该线程。启动后,进入线程队列排队,由CPU调度服务。
3.运行状态
就绪状态的线程获得处理器的资源时,线程就进入了运行状态,此时将自动调用run方法。
4.阻塞状态
正在运行的线程在某些特殊情况下,如:当前线程调用sleep、suspend、wait等方法时,运行在当前线程里的其它线程调用join方法时,以及等待用户输入的时候。只有当引起阻塞原因消失后,线程才能进入就绪状态。
5.终止状态
当线程run方法运行结束后,或者主线程的main()方法结束后,线程才能处于终止状态,线程一旦死亡就不能复生。
3. 多线程常用操作方法
- 线程的命名与取得
- 线程休眠方法
- 线程中断方法
- 线程强制执行
- 线程让步
- 线程优先级
3.1 线程的命名和获取
线程是不确定的运行状态,名称就是线程的主要标记。因此,需要注意的是,对于线程的名字一定要在启动之前设置进程名称,不建议对已经启动的线程,进行更改名称,或者为不同线程设置重名。
其主要方法:
public Thread(Runnable runnable,String name) //构造函数:实例化线程对象,为线程对象设置名称
public final void setName(String name) // 普通函数:设置线程名字
public final String getName() // 普通函数:获取线程名字
观察线程命名操作:
class MyThread implements Runnable{
@Override
public void run() {
System.out.println( Thread.currentThread().getName());
System.out.println( Thread.currentThread().getName());
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
new Thread(mt1,"线程一").start();
new Thread(mt2,"线程二").start();
new Thread(mt3).start(); // 使用默认的线程名称
mt1.run(); // 这里直接查看main方法的线程名称
}
}
运行情况:
需要注意的是主方法也是进程的一个线程。
3.2 线程休眠
sleep方法定义在java.lang.Thread中,由Thread.sleep()调用实现。其作用是需要暂缓线程的执行速度,则可以让当前线程休眠,即当前线程从“运行状态”进入到“阻塞状态”。sleep方法会指定休眠时间,线程休眠的时间会大于或等于该休眠时间,该线程会被唤醒,此时它会由“阻塞状态”变成“就绪状态”,然后等待CPU的调度执行。
其主要方法:
public static void sleep(long millis) throws InterruptedException // 普通函数:设置休眠时间的毫秒数
public static void sleep(long millis,int nanos) throws InterruptedException // 普通函数:设置休眠毫秒数和纳秒数
定义线程主体类:
public class MyThread implements Runnable{
@Override
public void run() {
for(int i = 0 ; i