V1
版本1:其中定义了一个 Task
类和三个继承自 Thread
类的子类 TA
、TB
和 TC
。
-
Task 类:
num
是一个整数变量,用于存储任务的结果。taskA()
、taskB()
和taskC()
是三个任务方法,分别模拟了一些计算和等待的操作。
-
TA、TB、TC 类:
- 这三个类分别表示三个不同的线程,每个线程执行一组任务。
- 每个线程接收一个
ArrayList<Task>
类型的列表作为参数,在run()
方法中,通过迭代列表,对每个Task
对象调用相应的任务方法。
-
Main 类:
- 在
main
方法中,首先创建了一个包含50个Task
对象的列表list
。 - 创建了
TA
、TB
和TC
的实例,并将list
作为参数传递给它们。 - 启动了三个线程,分别执行
taskA
、taskB
和taskC
。 - 创建了一个额外的线程,该线程每秒输出部分任务的结果,以便在执行过程中观察任务的完成情况。
- 使用
join()
方法等待三个线程执行完成。
- 在
-
任务执行过程:
TA
线程每次迭代调用taskA()
,导致num
值增加 10。TB
线程每次迭代调用taskB()
,导致num
值乘以 20。TC
线程每次迭代调用taskC()
,导致num
值乘以自身。
-
输出:
- 在额外的线程中,每秒输出
list
中每个第五个任务的结果。
- 在额外的线程中,每秒输出
需要注意的是,由于线程之间并发执行,输出结果可能会交错。此外,对 num
的操作可能导致竞态条件,可能需要使用同步机制来确保线程安全性。
import java.util.ArrayList;
// A-B-C : 40000 正确完成任务后的结果
public class Task {
int num;
public void taskA() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
num += 10;
}
public void taskB() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
num *= 20;
}
public void taskC() {
try {
Thread.sleep(650);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
num *= num;
}
}
class TA extends Thread {
ArrayList<Task> list;
public TA(ArrayList<Task> list) {
this.list=list;
}
@Override
public void run() {
for(int i=0;i<list.size();i++){
Task task = list.get(i);
task.taskA();
}
}
}
class TB extends Thread {
ArrayList<Task> list;
public TB(ArrayList<Task> list) {
this.list=list;
}
@Override
public void run() {
for(int i=0;i<list.size();i++){
Task task = list.get(i);
task.taskB();
}
}
}
class TC extends Thread {
ArrayList<Task> list;
public TC(ArrayList<Task> list) {
this.list=list;
}
@Override
public void run() {
for(int i=0;i<list.size();i++){
Task task = list.get(i);
task.taskC();
}
}
}
class Main {
public static void main(String[] args) {
// 1: 定量任务
ArrayList<Task> list = new ArrayList<>();
for (int i = 0; i < 50; i++) {
list.add(new Task());
}
TA ta = new TA(list);
TB tb = new TB(list);
TC tc = new TC(list);
ta.start();
tb.start();
tc.start();
//监听状态线程
new Thread(){
public void run(){
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
int size=0;
for(int i=0;i< list.size();i++){
if(i%5==0){
System.out.println(list.get(i).num);
}
}
//System.out.println(list.size());
//System.out.println("已完成"+size+"任务");
}
}
}.start();
try {
ta.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
tb.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
tc.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
可见性问题
可见性问题是多线程并发编程中常见的一个挑战,它涉及到一个线程对共享变量的修改是否能够立即被其他线程看到。在没有适当同步机制的情况下,由于每个线程有自己的工作内存,可能会导致一个线程对变量的修改对其他线程不可见。
在 Java 中,主要有两个因素导致可见性问题:
-
线程工作内存: 每个线程都有自己的工作内存,用于存储主内存中的变量的副本。线程在执行时,操作的是工作内存中的变量,而不是直接操作主内存中的变量。
-
指令重排序: 编译器和处理器为了提高执行效率,可能会对指令进行重排序。这就可能导致一些操作的执行顺序与代码中的顺序不一致。
为了解决可见性问题,Java 提供了 volatile
关键字。使用 volatile
关键字修饰的变量具有以下特性:
-
可见性: 当一个线程修改了
volatile
变量的值,该变量的新值会立即被写回主内存,而其他线程在读取该变量时会直接从主内存中获取最新的值。 -
禁止指令重排序:
volatile
关键字禁止指令重排序,确保了变量的修改按照代码的顺序执行。
例
例子中演示了使用 volatile
关键字解决多线程可见性问题的情况。
-
volatile
关键字:volatile
修饰的变量flag
表示该变量是易变的,并且任何线程对它的修改都会立即反映到其他线程中。这解决了多线程之间的可见性问题,确保一个线程对该变量的修改对其他线程是可见的。
-
main
方法:- 创建了一个名为
t1
的线程,该线程在运行时将flag
设置为true
。 - 创建了一个匿名线程,该线程在运行时通过循环检查
flag
的值,一直等到flag
变为true
才输出 "T2-end"。 - 在
main
方法中,通过Thread.sleep(2000)
使得主线程休眠 2 秒,以确保t1
线程有足够的时间来设置flag
的值。 - 启动了
t1
线程。
- 创建了一个名为
-
输出:
T1 - start
:t1
线程开始执行,将flag
设置为true
。