Java多线程理解与实践(一)
前言
Java多线程是Java开发的基本功,而在日常面试中也会经常被问到。常常在这个类题目上回答不上,或者回答得卡卡顿顿的,其实就是自己没有理解到位。或者说实践不够。
本文将从最基本的线程创建,同步方法、及线程池开始介绍学习。同时,附上自己写的一些代码,希望从实践中感受到多线程编程的实质。
多线程相关概念介绍
多线程是一种编程模型
多线程本质上是一种编程模型,现代的计算机搭载多核心cpu,为了更好的体现多核心的优势,最大化cpu多核心的优势,提高cpu利用率,便出现了多线程编程。
进程与线程
进程是操作系统资源分配的基本单位。而线程是比进程颗粒度更细,一个程序中可能创建出多个线程同时执行多个任务。而线程是CPU调度的基本单位,共享着进程的资源。
JVM内存模型(JMM)
Java 内存模型(Java Memory Model, JMM)

主内存与工作内存
- 主内存:所有共享变量的存储区域,线程对变量的读写操作最终需同步到主内存。
- 工作内存:每个线程私有的内存空间,存储线程操作变量的副本。线程不能直接读写主内存中的变量,需通过工作内存交互。
线程对变量的操作流程:
- 从主内存读取变量到工作内存。
- 在工作内存中修改变量值。
- 将修改后的值写回主内存。
可见性、有序性与原子性
- 可见性:通过volatile、synchronized或final关键字实现。volatile变量修改后会立即同步到主内存,其他线程读取时强制刷新工作内存。
- 有序性:禁止指令重排序(通过volatile的禁止重排语义或synchronized的锁规则)。
- 原子性:基本类型(除long/double外)的读写是原子的,复杂操作需通过锁或Atomic类保证。
实践
创建线程
继承Thread 创建线程类
public class ThreadA extends Thread {
public void run(){
System.out.printf("[%s] A: Start\n",Thread.currentThread().getName());
for(int i = 1; i <= 3; i++){
try {
Thread.sleep(120);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.printf( "[%s] A: %s\n", Thread.currentThread().getName(), i);
}
System.out.printf("[%s] A: END\n",Thread.currentThread().getName());
}
}
实现Runnable接口 创建线程类
public class ThreadB implements Runnable {
@Override
public void run() {
System.out.printf("[%s] B: Start\n",Thread.currentThread().getName());
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.printf( "[%s] B: %s\n", Thread.currentThread().getName(), i);
}
System.out.printf("[%s] B: End \n",Thread.currentThread().getName());
}
}
创建线程
以下是main方法 创建线程类。
- 线程对象被创建后一定是 调用start方法
- 使用join方法代表等待改线程运行结束后才回到改线程继续执行。
public class Basic {
public static void main(String[] args) throws InterruptedException {
System.out.printf("[%s] Main: start \n",Thread.currentThread().getName());
//ThreadA 是继承自 Thread 类,直接start
ThreadA threadA = new ThreadA();
threadA.start();
// threadA.join();//如果中间加入这个join方法,下面就不会继续,直到A结束
//ThreadB 只是实现了 Runnable接口,所以它需要再新建一个Thread类
//这里构造函数需要的是一个实现了Runnable类型的对象(可以理解为一个任务 task)
Thread threadB = new Thread(new ThreadB());
threadB.start();
//threadA.run();//很明显这里的ThreadName 就是Main 主线程。
//启动 线程是start,而不是直接调用run。run只是主线程调用了这个对象的run方法,仅此而已。
System.out.printf("[%s] Main: end \n",Thread.currentThread().getName());
//虽然打印到这里。 但是 主线程还是在等其他线程结束才会结束。
//线程状态 : new 、runnable 、waiting、block、timed waiting 、terminated、
}
}
被interrupt(打断)的实验
当我们想在线程运行阶段调用sleep方法时,一个必检异常InterruptedException需要处理。那到底如何使用这个打断。接下来将有一个小实验帮助大家理解。

两个线程Class
A:打印1到10,打印之间睡觉一秒钟。
public class IThreadA extends Thread {
@Override
public void run() {
System.out.printf("%s ThreadA", Thread.currentThread().getName());
int n = 0;
while (true) {
n++;
System.out.printf("%s ThreadA ... %s \n", Thread.currentThread().getName(),n);
try {
sleep(1000L);
} catch (InterruptedException e) {
System.out.printf("%s ThreadA is interrupted\n", Thread.currentThread().getName());
this.interrupt();
}
if (n == 10) {
break;
}
if (interrupted()) {
System.out.printf("%s ThreadA jumps into interrupted checking.\n", Thread.currentThread().getName());
break;
}
}
}
}
B:和A类似,从1开始打印,知道running被改变。
public class IThreadB extends Thread {
public volatile boolean running = true;
@Override
public void run() {
System.out.printf("%s IThreadB is running.\n", Thread.currentThread().getName());
int n = 0;
while (running) {
try {
sleep(1000);
} catch (InterruptedException e) {
System.out.printf("INTERRUPTED %s, but keep running.\n", Thread.currentThread().getName());
}
n++;
System.out.printf("%s IThreadB ... %s \n", Thread.currentThread().getName(),n);
}
if (!running) {
System.out.printf("%s IThreadB jumps into interrupted checking.\n", Thread.currentThread().getName());
}
}
}
执行Main方法的class
- 创建并启动A线程 ==》打断 ==》等待A结束
- 创建并启动B线程 ==》修改其 running 至false
public static void main(String[] args) throws InterruptedException {
IThreadA iThreadA = new IThreadA();
iThreadA.start();
iThreadA.interrupt();
iThreadA.join();
IThreadB iThreadB = new IThreadB();
iThreadB.start();
iThreadB.running = false;
}
结果

现象
- A打印了1次,然后就被打断了。它跳进了catch代码块,之后通过interrupted()这个方法获取到当前是否被打断状态。
- B启动之后,主线程就修改了被volatile修饰的running属性,该属性原本是true。然后,B就没办法进入答应阶段了。正如Java内存模型提到的可见性问题,running 是一个volatile 修饰的变量,被 volatile 修饰完之后,就会让所有线程都可见。它解决的是一个可见性的问题。
总结
本文主要讲了一些Java线程的基础知识,包括简单的线程类创建方式以及InterruptException的出触发。
170万+

被折叠的 条评论
为什么被折叠?



