
什么是进程?
进程就是正在运行的程序(程序本身是静态的,而进程则是动态的),它会占用对应的内存区域,由CPU进行执行与计算。
进程的特点:
什么是线程?
线程是操作系统所能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,所以说【进程包含线程】。
线程的特点:
线程拥有**【随机性】,即常见的Windows、Linux等操作系统都采用抢占式多任务,如何调度线程完全由操作系统决定**,程序并不能自己决定什么时候执行以及执行多长时间。
**一个Java程序实际上就是一个JVM进程。**JVM 进程先使用主线程来执行main()方法,然后又接着启动其他线程,此外 JVM 还存在着垃圾回收等其他工作线程。
当我们不使用线程池中的线程时,线程池里的线程会处于一种“休眠状态”,当再次被调用时恢复成使用状态(节约资源)。
数据库的连接是一种有限且昂贵的资源。在一般情况下,我们应该使用**【数据库连接池】**来减轻服务器压力、提高程序的运行效率,使得服务器能够支持并容纳更多的客户服务。
数据库连接池是不等同于线程池,它只是另外一种线程池。
简介:
.start()运行时,程序才会调用线程的run()方法。private native void start0();
Thread 与 Runnable 的区别:
class Thread implements Runnable {}
继承 Thread
class MyThread extends Thread {
@Override
public void run() {
System.out.println("start new thread!");
}
}
Thread t = new MyThread();
t.start();
new Thread(() -> {
System.out.println("start new thread!");
}).start();
实现 Runnable
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("");
}
Thread t = new Thread(new MyRunnable());
t.start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("");
}
}).start();
.start():启动线程,执行run()方法
t.start()
.sleep():强迫线程休眠一段时间。
t.sleep(1000);
.setPriority(10):设置线程优先级。优先级高的线程会被操作系统优先调度,但是并不能保证其一定会被优先执行,优先级从1~10共10个级别,其中默认值为5。
// 1~10, 默认值5
t.setPriority(int n);
.join():
t.join();
t.join(8000);
.getName():获取当前线程名
Thread.currentThread().getName();
.getId():获取当前现场 id 号
Thread.currentThread().getId()
线程组
Thread[] ts = new Thread[] { new AddStudentThread(), new DecStudentThread(), new AddTeacherThread(), new DecTeacherThread() };
一个线程对象只能调用一次start()方法。一旦run()方法执行结束,线程终止。
t.start();
线程拥有6种状态(不同JDK版本可能会有差别):
线程从 New 开始,途中可能经过几种不同的状态,但最后总会达到 terminated 终止线程状态。

线程终止的原因一共有3种:
什么时候需要中断线程?下载 100GB 的资源,中途取消。
利用.interrupt():手工编写中断代码
// 在线程中手动编写中断逻辑
class MyThread extends Thread {
public void run() {
while (! isInterrupted()) {
System.out.println(n + " hello!");
}
}
}
// 向线程发送中断请求
t.interrupt();
利用自定义变量
// 线程当中的具体逻辑
class HelloThread extends Thread {
// 自定义变量 + volatile
public volatile boolean running = true;
public void run() {
while (running) {
System.out.println(n + " hello!");
}
System.out.println("end!");
}
}
// 标识线程间共享变量 running 为 false
t.running = false;
volatile 关键字:

因此,volatile 关键字的作用为:
守护线程 而不是 守护进程。
前言
实现代码:
Thread t = new MyThread();
t.setDaemon(true); // 修改为【守护进程】
t.start();
注意:在编写守护线程的时候,守护线程不能持有任何需要关闭的资源(例如打开文件)。因为虚拟机退出时,守护线程没有任何机会来关闭文件,可能会导致数据丢失。
目的:解决线程安全问题。
弊端:性能下降(synchronized 无法并发执行)。
实际做法:将所有可能会存在冲突的操作定义为【原子操作】。
加锁原则:
synchronized关键字:
Java 使用 synchronized 对一个对象进行加锁(保证代码块在任意时刻只有一个线程在执行)。这种加锁和解锁之间的代码块我们称之为临界区。
synchronized( myLock ) { // 获取锁
// 临界区代码块
} // 释放锁
在使用synchronized的时候,不必担心程序抛出异常而导致锁未释放,因为无论有无异常,Java程序总会正确释放锁。
public void add(int m) {
synchronized (obj) {
if (m < 0) {
throw new RuntimeException();
}
this.value += m;
} // 无论有无异常,都会在此释放锁
}
Java规范中定义了几种原子操作,这些操作并不需要加锁:
简介:
在使用synchronized的时候,锁住的是哪个对象非常重要。让线程自己选择锁住的对象会使得代码逻辑混乱、不利于封装。更好的方法是对 synchronized 进行逻辑封装。而且不止局部代码,方法也可以被锁住(对方法加锁实际上就是对该方法所属实例this进行加锁),以下两种方法是等价的:
public void add(int n) {
synchronized(this) {
count += n;
} // 解锁
}
// 对方法进行加锁,锁住 this
public synchronized void add(int n) {
count += n;
} // 解锁
// 每次调用该方法时都会锁住自身 this,线程无需关心具体的逻辑。(且对创建多个对象进行并发操作无影响)
关于【线程安全】
前言:可重入锁
JVM允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁就叫“可重入锁”。例如在以下代码中,当传入 n < 0 时,线程一共会获取2次当前实例的this锁。
可重入锁每获取一次锁,记录都会加 1;每退出一次 synchronized 块,记录都会减 1。当减到 0 的时候,程序才会真正释放锁。
public class Counter {
private int count = 0;
public synchronized void add(int n) {
if (n < 0) {
dec(-n);
}
}
public synchronized void dec(int n) {
count += n;
}
}
主题:死锁(deadlock)
为什么要使用 wait 与 notify ?
while 循环与 wait/notify:
public synchronized String getTask() {
while (queue.isEmpty()) {
this.wait();
}
return queue.remove();
}
public synchronized void addTask(String s) {
this.queue.add(s);
this.notify(); // 唤醒在this锁等待的线程
}
【注意】:正确编写多线程代码是非常困难的,需要考虑的地方非常多,不正确编写多线程代码会导致程序运行时不正常。
re-entrant Lock,可重入锁
reentrantLock 用来配合 Conditon 进行工作,就像 wait 与 notify 的组合
从Java 5开始,引入了一个高级的处理并发的java.util.concurrent包,它提供了大量更高级的并发功能,能大大简化多线程程序的编写。
ReentrantLock是用来代替 synchronized 的,它更安全。
// synchronized
public void add(int n) {
synchronized(this) {
count += n;
}
}
// reentantLock
public class Counter {
private final Lock lock = new ReentrantLock();
private int count;
public void add(int n) {
lock.lock(); // 以这种方式加锁
try {
count += n;
} finally {
lock.unlock(); // 以这种方式解锁
}
}
}
【必要性说明】:
【其他说明】:
和 synchronized 不同的是,ReentrantLock 可以尝试获取锁。
下述代码在尝试获取锁的时候,最多等待 1 秒。如果 1 秒后仍未获取到锁,tryLock()返回 false,程序就可以做一些额外处理,而不是无限等待下去。
使用 ReentrantLock 比直接使用 synchronized 更安全,线程在 tryLock() 失败的时候不会导致死锁。
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
//
} finally {
lock.unlock();
}
}
Conditon 用来配合 reentrantLock 进行工作,就像 wait 与 notify 的组合
简介:Condition 与 wait/notify 类似,方法对应。
| Condition | synchronized | 对应功能 |
|---|---|---|
| await() | wait() | 释放当前锁,进入等待状态 |
| signal() | notify() | 唤醒某个等待线程 |
| signalAll() | notifyAll() | 唤醒所有等待线程 |
ReadWriteLock 是一种悲观锁,
StampedLock 是一种乐观锁。
简介
问题描述:synchronized 与 reentrantLock 都是【“不可重复读”锁】,但是我们还需要【“可重复读”锁】,可重复读锁能极大的提高程序运行效率。
解决方式:使用 readWriteLock(可重复读锁),允许多个线程同时读,但只要有一个线程在写,其他线程就必须等待。
| 读 | 写 | |
|---|---|---|
| 读 | 允许 | 不允许 |
| 写 | 不允许 | 不允许 |
但是,readWriteLock 是一种**【悲观锁】**。即如果有线程正在读,那么写线程需要等待读线程释放锁之后才能获取写锁(读的过程中不允许写)。
实现:分别获得读写锁。
public class Counter {
private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
private final Lock rlock = rwlock.readLock(); // 获取读锁
private final Lock wlock = rwlock.writeLock(); // 获取写锁
private int[] counts = new int[10];
public void inc(int index) {
wlock.lock(); // 加写锁
try {
counts[index] += 1;
} finally {
wlock.unlock(); // 释放写锁
}
}
public int[] get() {
rlock.lock(); // 加读锁
try {
return Arrays.copyOf(counts, counts.length);
} finally {
rlock.unlock(); // 释放读锁
}
}
}
ReadWriteLock 是一种悲观锁,
StampedLock 是一种乐观锁。
简介:
简单实现(获取代码):

和ReadWriteLock相比,写入的加锁是完全一样的,不同的是读取。注意到首先我们通过tryOptimisticRead()获取一个乐观读锁,并返回版本号。接着进行读取,读取完成后,我们通过validate()去验证版本号,如果在读取过程中没有写入,版本号不变,验证成功,我们就可以放心地继续后续操作。如果在读取过程中有写入,版本号会发生变化,验证将失败。在失败的时候,我们再通过获取悲观读锁再次读取。由于写入的概率不高,程序在绝大部分情况下可以通过乐观读锁获取数据,极少数情况下使用悲观读锁获取数据。
简介:
Java并发集合类
并发类容器是专门针对多线程并发设计的,使用【锁分段技术】,只对操作的位置进行同步操作,但是其他没有操作的位置其他线程仍然可以访问,提高了程序的吞吐量。
| 类型 | non-thread-safe | thread-safe |
|---|---|---|
| List | ArrayList | CopyOnWriteArrayList |
| Map | HashMap | ConcurrentHashMap |
| Set | HashSet/TreeSet | CopyOnWriteArraySet |
| Queue | ArrayDeque/LinkedList | ArrayBlockingQueue LinkedBlockingQueue |
| Deque | ArrayDeque/LinkedList | LinkedBlockingDeque |

【必要性说明】
**这些并发集合的使用与非线程安全的集合类完全相同。**因为所有的同步和加锁操作逻辑都在集合内部实现,所以外部调用者来说,只需修改类型即可。
Map<String, String> map = new ConcurrentHashMap<>();
// 在不同线程读写:
map.put("A", "1");
map.put("B", "2");
map.get("A", "1");
Map<String,String> map = new HashMap<>();
// 迅速转换类型,无需做其他任何处理
Map<String,String> map = new ConcurrentHashMap<>();
java.util.Collections工具类提供了一个旧的线程安全集合转换器。
// 不推荐使用
Map unsafeMap = new HashMap();
Map threadSafeMap = Collections.synchronizedMap(unsafeMap);
【小结】:使用 java.util.concurrent 包提供的线程安全并发集合类可以大大简化多线程编程。尽量使用 Java 标准库提供的并发集合,避免自己编写同步代码。
native方法,保证操作原子性。
简介:
java.util.concurrent.atomic 包提供了一组用于原子操作的封装类。

AtomicInteger:原子整数类型,它提供的操作有:
int addAndGet(int delta)int incrementAndGet()int get()int compareAndSet(int expect,int update)==CAS(自旋锁)==简介:
好几种翻译,分别为:Compare and Set、Compare And Swap、Compare And Exchange。
**作用:**确定期望值,如果相比较的两个值相等(符合期望),那么就进行更新操作,并返回true;否则什么也不做并返回 false。

AtomicInteger atomicInteger = new AtomicInteger(1);
// 期望为 1 ,若为 1 则更新至 2 ,否则不更新。
atomicInteger.compareAndSet(1, 2);
如果我们自己编写 CAS ,那么它大致长这个样子:
// 此例即 AtomicInteger 自带的功能:加1后返回新值
public int incrementAndGet(AtomicInteger var) {
int prev, next;
do {
prev = var.get();
next = prev + 1;
} while ( ! var.compareAndSet(prev, next));
return next;
}
AtomicLong可以编写多线程安全的全局唯一ID生成器。
class IdGenerator {
AtomicLong id = new AtomicLong(0);
public long getNextId() {
return id.incrementAndGet();
}
}
java.util.concurrent
简介
Java虽然支持多线程,启动一个新的线程非常简单。但是频繁创建销毁线程需要消耗大量的系统资源(线程资源、栈空间等),更好的做法是使用【线程池】。

Java标准库提供ExecutorService接口表示线程池,它的典型用法如下:
// 创建固定大小的线程池:
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交任务:
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
executor.submit(task4);
executor.submit(task5);
常见的 ExecutorService 接口实现类:
(创建这些线程池的方法都被封装到Executors这个类中)
ExecutorService es = Executors.newFixedThreadPool(4);
3种线程池关闭方式:
【必要性说明】:线程池最后需要手动关闭。
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(4);
for (int i = 0; i < 6; i++) {
es.submit(new Task());
}
// 关闭线程池:
es.shutdown();
}
}
class Task implements Runnable {
@Override
public void run() {
System.out.println("a");
}
}
创建动态线程池
原理:基于 CacheThreadPool 底层源码,创建存在 min 与 max 的线程池。

// 创建指定 min 与 max 的线程池
int min = 4;
int max = 10;
ExecutorService es = new ThreadPoolExecutor(min, max,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
【工作队列】说明:常见的工作队列有以下几种,前3种用的最多。
【小总结】
ScheduleThreadPool【定时任务】
简介:特殊的线程池,定时任务、可以反复执行。
执行类型:

ScheduledExecutorService s=Executors.newScheduledThreadPool(4);
// 1秒后执行任务,而且只执行一次。
ses.schedule(new Task("one-time"), 1, TimeUnit.SECONDS);
// FixedRate,10秒后开始执行定时任务,每3秒执行。
ses.scheduleWithFixedRate(new Task(),10,3, TimeUnit.SECONDS);
// FixedDelay,10秒后开始执行定时任务,每3秒执行。
ses.scheduleWithFixedDelay(new Task(),10,3, TimeUnit.SECONDS);
// lambda表达式
ses.schedule(()->{
System.out.PrintLn("hello");
}, 1, TimeUnit.SECONDS);
思考以下问题:
【Timer】说明:
Java标准库中海提供了一个java.util.Timer类,这个类也可以执行定时任务,但这个类所代表的是一个旧的体系,并不推荐,Shedule 完全可以代替 Timer。
Future 是 jdk8 之前的用法,在下一章有更好的做法。
简介:
存在问题:我们在执行多线程任务的时候,使用线程池的确很方便(提交的任务只要实现Runnable接口),但是Runnable接口并没有返回值。如果我们需要返回值还需要编写额外的方法、变量,非常不便。
解决之道:使用Callable(接口)。
// call() 方法中存在我们自定义的类型返回值
class Task implements Callable<String> {
public String call() throws Exception {
return longTimeCalculation();
}
}
那么,现在的问题是如何获得异步执行的结果:
【使用说明】:
submit() 方法会返回 Future 对象,然后利用该对象获取返回值。
在主线程中的某个时刻调用 Future 实例 .get() 方法获取值。(可能存在堵塞)
ExecutorService executor = Executors.newFixedThreadPool(4);
// 定义任务:
Callable<String> task = new Task();
// 提交任务并获得Future:
Future<String> future = executor.submit(task);
// 从Future获取异步执行返回的结果:
String result = future.get(); // 可能阻塞
Future 是个**【泛型对象】**,它定义的方法有:
本章小结:
Java8引入,更优的 Future。
简介:
简单实现:
public class Main {
public static void main(String[] args) throws Exception {
// 创建异步执行任务:
CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Main::fetchPrice);
// 如果执行成功:
cf.thenAccept((result) -> {
System.out.println("price: " + result);
});
// 如果执行异常:
cf.exceptionally((e) -> {
e.printStackTrace();
return null;
});
// 主线程不要立刻结束,
// 否则CompletableFuture默认使用的线程池会立刻关闭。
Thread.sleep(200);
}
/**
* 随机生成数,当 random < 0.3 时,抛出错误。
* 反之正常返回值
*/
static Double fetchPrice() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (Math.random() < 0.3) {
throw new RuntimeException("fetch price failed!");
}
return 5 + Math.random() * 20;
}
}
可见,CompletableFuture 的优点是:
简介
多线程是Java实现多任务的基础,一个 Thread 实例代表一个线程。
web应用是典型的多任务应用,每个用户请求都是一个线程。
ThreadLocal 属于**【泛型】**,在一个线程中传递同一个对象,可以解决单次请求中多方法之间的数据同步问题。
// 获取当前线程
Thread.currentThread().getName();
使用介绍
初始化:ThreadLocal 常以静态字段的形式存在。
在移除之前,所有方法都可以随时获取该User实例
static ThreadLocal<User> threadLocal = new ThreadLocal();
void processUser(user) {
try {
threadLocal.set(user);
step1();
step2();
} finally {
threadLocal.remove();
}
}
// 3次获取的 User 均是同一个对象。
void step1() {
User u = threadLocal.get();
log();
}
void step2() {
User u = threadLocal.get();
}
void log() {
User u =threadLocal.get();
}
ThreadLocal是什么?
ThreadLocal 是提供给线程内使用的局部变量。
ThreadLocal 实际上相当于全局性的 Map
每一个线程都有属于自己的 ThreadLcoal 对象,它会开辟一份线程专属空间,里面存放一些属于该线程的数据。
在 Web 应用中使用 ThreadLocal 时,要注意退出时清空数据(可以利用 finally 语句)。
// 清空 threadLocal 数据
threadLocal.remove();
【必要性说明】
我们可以使用 AutoCloseable 接口来封装 ThreadLcoal ,从而使得它可以在try(){}语句中被自动的关闭。
public class UserContext implements AutoCloseable {
static final ThreadLocal<String> ctx =
new ThreadLocal<>();
public UserContext(String user) {
ctx.set(user);
}
public static String currentUser() {
return ctx.get();
}
// 结合 try(){},该方法会在最后自动调用。
@Override
public void close() {
ctx.remove();
}
}
try (var ctx = new UserContext("Bob")) {
String currentUser = UserContext.currentUser();
}