THIS IS B3c0me

记录生活中的点点滴滴

0%

Java多线程与并发

一、术语一览

  • 进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是系统进行资源分配和调度运行的基本单位
  • 线程:线程有时被称为轻量级进程,是程序执行流的最小单位
  • 线程调度:线程调度是指按照特定机制为多个线程分配CPU的使用权
  • 线程阻塞:线程阻塞通常是指一个线程在执行过程中暂停,以等待某个条件的触发
  • 线程同步:线程同步是指线程执行的协同、协助、配合
  • 线程安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可以使用。以免出现数据不一致或者数据污染。

二、线程的创建方法

Java中线程的创建方式有三种:可以继承Thread类、实现Runnable接口、用Callable接口和Future类实现

2.1 扩展Thread类

扩展Thread类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run方法称为执行体,或者叫线程体。

示例:基于Thread类实现的多线程

说明:本段代码可以多运行几遍,会出现不同的运行结果(具体来说是A和B的执行顺序不同),这取决于操作系统CPU的线程调度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
*该类通过将继承Thread类实现多线程的创建
* 类中必须重写run方法
*/
class ExtThread extends Thread{
//构造方法,用于设定线程名
public ExtThread(String name){
super(name);
}
//重写run方法,作为线程体
@Override
public void run() {
for(int i = 0; i < 4; i++){
System.out.println("线程" + Thread.currentThread().getName() + "执行" + i + "次");
try {
sleep(500); //进程休眠,不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public class MyThreadDemo {
public static void main(String[] args) {
//创建两个ExtThread类的实例,分别作为两个线程
Thread t1 = new ExtThread("A");
Thread t2 = new ExtThread("B");
//让两个线程处于就绪态
t1.start();
t2.start();
}
}

运行结果:

2.2 实现Runnable接口

​ 直接扩展Thread类的方式虽然简单直接,但是由于Java是单继承的,所以继承了Thread类的子类无法再继承其他类。这种情况下可以通过实现Runnable接口来创建线程类。Runnable接口中只声明了一个run方法,所以实现该接口的类必须重定义该方法。

​ 要启动线程,必须调用线程类Thread中的方法start(),所以即使是通过实现Runnable接口实现线程,也必须有Thread类的对象,并且该对象的run方法是由实现Runnable接口的类的对象提供。Thread一共有8个构造方法,其中一个构造方法为:

1
Thread(Runnable target)

需要由参数target提供run方法

示例:通过实现Runnable接口实现多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//实现Runnable接口,并重写run方法
class ImpRunnable implements Runnable{
private String name;//自定义线程名,并通过构造方法命名线程
public ImpRunnable(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("线程" + Thread.currentThread().getName() + "执行了" + i + "次");
for (long j = 0; j <10000000 ; j++); //起到延时的作用
}
}
}

public class MyThreadDemo2 {
public static void main(String[] args) {
ImpRunnable ds1 = new ImpRunnable("A");
ImpRunnable ds2 = new ImpRunnable("B");
//用Thread类封装Runnable实现对象,并使用Thread的start方法开启线程
new Thread(ds1).start();
new Thread(ds2).start();
}
}

运行结果: 多次运行结果不同,跟CPU调度有关

2.3 用Callable和FutureTask定义线程

用Callable和FutureTask定义线程,包含以下4个步骤:

  1. 创建Callable接口的实例,并实现call方法,该call方法将作为线程执行体,并且有返回值
  2. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象call方法的返回值
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程
  4. 调用FutureTask对象的get方法来获得子线程执行结束后的返回值

示例:用Callable和FutureTask定义线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//1.创建Callable接口的实例,并实现call方法,该call方法将作为线程执行体,并且**有返回值**
public class MyThreadDemo3 implements Callable<Integer> {
//实现call方法作为线程体,返回类型是Integer
public Integer call() throws Exception{
int i = 0;
for(;i < 5; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
//2.创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象call方法的返回值
public static void main(String[] args) {
MyThreadDemo3 ct = new MyThreadDemo3();
FutureTask<Integer> ft = new FutureTask<>(ct);
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "的循环变量的i的值为" + i);
if(i == 2){ //当i = 2 时启动线程体
//3.使用FutureTask对象作为Thread对象的target创建并启动新线程
new Thread(ft,"有返回的线程").start();
}
}
try{
//4.调用FutureTask对象的get方法来获得子线程执行结束后的返回值
System.out.println("子线程的返回值" + ft.get() );
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}

运行结果:

三、线程常用方法总结

方法 操作
currentThread() 返回当前正在执行的线程对象的引用
getName() 返回该线程的名称
interrupt() 使一个阻塞状态的线程中断执行
jion() 等待该线程执行完毕
run() 如果该线程是独立的Runnable运行对象构造的,则调用该Runnable对象的run方法,否则该方法不执行任何操作并返回
sleep() 在指定的毫秒数内让该线程休眠,不释放锁
start() 让该线程处于可运行态
yield() 让当前的线程重新回到可运行态

欢迎关注我的其它发布渠道