文章目录
几个概念
- 进程:程序执行的一个过程(从创建到消亡,一个软件的运行就可以认为是一个进程)
- 线程:与进程类似,比进程更下的执行单位,一个进程可以包含多个线程,多个线程共享进程的堆和方法区的资源
- 并发:多个线程/进程在同一时间段内执行(可以看成是一堆人去某个地方,没有先后顺序)
- 并行:多个线程/进程在同一时间点执行(一堆人排成一排同时去某个地方)
- 同步:调用方法一旦开始,就必须等待调用方法返回后,才能执行后续的操作
- 异步:调用方法,不用等待返回,直接执行后续的操作,当方法结束后,发一个通知给当前线程
- 阻塞:调用结果还没有返回之前,线程阻塞挂起,得到结果时,再执行后续操作(与同步不同,阻塞是有目的性的等待)
- 死锁:不同的线程占用了对方的资源不放,都在等待对方释放资源
JUC(java.util.concurrent):基于多线程的一种编程技术
创建多线程的几个方式
public class Chap01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// main函数也是一个线程(当启动main函数时,还会有其他的线程同时启动)
new ThreadTest1().start(); // 启动线程
new Thread(new ThreadTest2()).start(); // 启动线程
// 与方式二相同
new Thread(() -> System.out.println(Thread.currentThread().getName() + ":开始")).start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
// 创建FutureTask的对象, 此处同样可以使用匿名的操作
// FutureTask可用于包装Callable或Runnable对象
FutureTask futureTask = new FutureTask(new ThreadTest3());
new Thread(futureTask).start(); // 启动线程
// 拿到返回值,此处需要捕获异常,执行此语句时,主线程会阻塞
System.out.println(futureTask.get());
System.out.println(Thread.currentThread().getName() + "主线程结束");
}
}
// 方式一:继承Thead,重写run()方法,就是一个线程
class ThreadTest1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
// 方式二:实现Runnable接口,实现run方法
class ThreadTest2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
// 方式三,实现Callable接口,可以指定泛型,存在放回值(泛型所指定的对象)
class ThreadTest3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Integer num = 0;
for (int i = 0; i < 10; i++) {
num += i;
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 开始了");
return num;
}
}
线程的声明周期:六种
状态 | 说明 |
---|---|
NEW | 初始状态,调用start之前 |
RUNNABLE | 运行状态,调用start。又分为就绪和运行中 |
BLOCKED | 阻塞状态,同步锁操作 |
WAITING | 等待,sleep,wait等操作 |
TIMED_WAITING | 超时等待 |
TERMINATED | 中止,执行完成 |
volatile
与synchronized
- volatile:java提供的轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
- 只能用于变量
- synchronized:同步锁,修饰代码块或方法
public class Chap02 {
private static volatile int flag = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(() ->{
// 不叫volatile,数据不可见,进入循环,
// 主线程改变了数据,但是该线程‘看不见’数据改变了,就会进入死循环,
while (true){
if (flag == 1){
System.out.println(flag);
break;
}
}
}, "线程一").start();
Thread.sleep(1000); // 让主线程处于等待状态,保证线程一进入循环
flag = 1;
System.out.println(flag);
}
}
wait/sleep
的区别
- 来自不同的类 wait --> Object sleep --> Thread
- 锁的释放 wait会释放锁, sleep不会释放锁
- 使用的地方不同 wait必须在同步代码块中使用 sleep任何地方都可以使用
Lock锁
// 买票模型
public class Chap03 {
// Lock锁
public static void main(String[] args) {
// 创建三个窗口来卖票
new Thread(() -> {
for (int i = 0; i < 30; i++) {
Ticket.sale();
}
}, "窗口一").start();
new Thread(() -> {
for (int i = 0; i < 30; i++) {
Ticket.sale();
}
}, "窗口二").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
Ticket.sale();
}
}, "窗口三").start();
}
}
class Ticket{
private static int num = 100;
// 创建Lock锁
static Lock lock = new ReentrantLock();
public static void sale(){ // 卖票
lock.lock(); // 上锁
try { // 防止异常之后不能解锁,导致死锁
if (num > 0){
System.out.println(Thread.currentThread().getName() + "卖出了第:" + (100-(--num)) + " 张票,剩余:" + num);
}
}finally {
lock.unlock(); // 解锁
}
}
}
synchronized
与Lock
的区别
- 都是解决线程安全问题的
Lock
是一个接口,而synchronized
是 java 的关键字synchronized
在执行完对应的操作后,自动释放锁,Lock
锁需要手动上锁(lock())和解锁(unlock()),为了防止死锁,需要加上try-ffinally
// Lock锁与Condition线程监视器的例子
public class Chap04 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
data.printA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
data.printB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
data.printC();
}
}, "C").start();
}
}
class Data2{
// 等待,业务,通知
private int num = 1;
// 创建锁
final private Lock lock = new ReentrantLock();
// Condition取代了对象监视器方法的使用。使线程按照一定规律来执行
final private Condition condition1 = lock.newCondition(); // 线程a的监视器
final private Condition condition2 = lock.newCondition(); // 线程b的监视器
final private Condition condition3 = lock.newCondition(); // 线程c的监视器
// num == 1时工作
public void printA(){
lock.lock(); // 上锁
try{
while (num != 1){
condition1.await(); // 等待
}
num = 2;
System.out.println(Thread.currentThread().getName() + ": " + num);
condition2.signal(); // 唤醒b
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock(); // 解锁
}
}
// num == 2时工作
public void printB(){
lock.lock();
try{
while (num != 2){
condition2.await();
}
num = 3;
System.out.println(Thread.currentThread().getName() + ": " + num);
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
// num == 3时工作
public void printC(){
lock.lock();
try{
while (num != 3){
condition3.await();
}
num = 1;
System.out.println(Thread.currentThread().getName() + ": " + num);
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
说明:其中的await()
和signal()
对应使用synchronized
时的wait()
和notify()
生产者消费者问题可以使用上面类似的思路
集合的线程安全
// 异常情况
public class Chap05 {
// 集合的线程安全操作
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; i++) { // 创建20个线程往集合中添加数据
int finalI = i;
new Thread(() -> {
list.add(finalI);
System.out.println(list); // java.util.ConcurrentModificationException
}, String.valueOf(i)).start();
}
}
}
public class Chap05 {
// 集合的线程安全操作
public static void main(String[] args) {
// List<Integer> list = new ArrayList<>();
// 解决方式一,使用线程安全的Vector集合类
// List<Integer> list = new Vector<>();
// 方法二,使用Collections工具类,底层与方式一相同
// List<Integer> list = Collections.synchronizedList(new ArrayList<>());
// 方法三,使用juc,底层使用的是CopyOnWrite,写入时复制,使用的是Lock锁,效率更高
List<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 20; i++) { // 创建20个线程往集合中添加数据
int finalI = i;
new Thread(() -> {
list.add(finalI);
System.out.println(list); // java.util.ConcurrentModificationException
}, String.valueOf(i)).start();
}
}
}
map
与set
的操作类似,都可以使用CopyOnWrite
List<Integer> list = new CopyOnWriteArrayList<>();
Map<String, String> map = new ConcurrentHashMap<>();
Set<String> set = new CopyOnWriteArraySet<>();
CopyOnWrite
是如何做到的:
add,put
等添加操作,底层会创建一个原数组的副本进行写入,然后再将修改完的副本替换为原来的数据,实现读与写的互不影响- 效率高:是因为读取操作不存在同步或锁操作,内部数据的改变只能通过副本替换,可以保证数据的安全
- 添加时,使用的是Lock锁
ThreadLocal:规避线程不安全的另一种方法
- 提供线程的本地变量,当创建了一个
ThreadLocal
变量,那么访问这个变量的每一个线程都会拷贝一份自己的副本 - 底层原理:当调用set的时候,会将当前线程与该值绑定,使用
ThreadLocalMap
,将该线程作为建,将set的内容作为值存储起来,
public class Chap06 {
static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(0); // 主线程
new Thread(() -> {
threadLocal.set(1); // 添加值
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 1
}, "线程一").start();
new Thread(() -> {
threadLocal.set(2); // 添加值
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 2
}, "线程二").start();
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 0
}
}
读写锁(ReadWriteLock)
public class Chap09 {
// 读写锁(接口):读的时候可以被多个线程读取,写的时候只能一个线程操作
public static void main(String[] args) {
MyCache myCache = new MyCache();
// 写入
for (int i = 1; i <= 6; i++) {
final int temp = i;
new Thread(() -> {
myCache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
// 读取
for (int i = 1; i <= 6; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<String, String> map = new HashMap<>();
// Lock lock = new ReentrantLock();
// 读写锁
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 写
public void put(String key, String value){
// lock.lock();
readWriteLock.writeLock().lock(); // 读锁
System.out.println(Thread.currentThread().getName() + ",写入:" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + ",写入完成");
// lock.unlock();
readWriteLock.writeLock().unlock();
}
// 读
public void get(String key){
readWriteLock.readLock().lock(); // 写锁
System.out.println(Thread.currentThread().getName() + ",读取:" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + ",读取完成");
readWriteLock.readLock().unlock();
}
}
线程池: 别人建好的线程,放在线程池,当我们需要的时候去拿,用完还回去
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
- 三大方法,七大参数,四种拒绝策略
// 使用Executors创建线程池,开发中禁止这样使用(浪费资源)
public class Chap07 {
// 线程池
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();// 可变大小的线程池
System.out.println(Runtime.getRuntime().availableProcessors()); // 获取cup核数
// 创建指定大小的线程池
ExecutorService executorService1 = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
try{
for (int i = 0; i < 20; i++) {
int finalI = i; // 使用线程池创建线程
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + ": " + finalI);
});
}
}finally {
executorService.shutdown(); // 执行完成之后,需要关闭线程池(把线程还给线程池)
}
}
}
// 应该使用这种方式创建线程池
public class Chap08 {
/* 源码
public ThreadPoolExecutor(int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数,当缓冲区满时,才会使用最大线程
long keepAliveTime, // 当核心线程没有工作到一定时间,才会销毁线程
TimeUnit unit, // 时间参数的单位
BlockingQueue<Runnable> workQueue, // 队列,缓冲,当到达核心线程数时,将新来的线程放入队列等待
ThreadFactory threadFactory, // 生产线程的工厂(一般用默认的) Executors.defaultThreadFactory()
RejectedExecutionHandler handler) { // 拒绝策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
四大拒绝策略(以银行为例)
new ThreadPoolExecutor.AbortPolicy() // 窗口和休息区都满了,不处理这个人,抛异常,默认使用
new ThreadPoolExecutor.CallerRunsPolicy() // 哪里来的去哪里
new ThreadPoolExecutor.DiscardPolicy() // 丢掉任务,不抛异常
new ThreadPoolExecutor.DiscardOldestPolicy() // 尝试和最早的竞争,不抛异常
*/
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(
5,
Runtime.getRuntime().availableProcessors(), // cup核数
2,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try{
for (int i = 0; i < 20; i++) {
int finalI = i;
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + ": " + finalI);
});
}
}finally {
executorService.shutdown();
}
}
}
AQS:AbstractQuenedSynchronizer
AQS的核心思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
BlockingQueue(阻塞队列)
public class BlockingQueueTest {
/*
什么时候会用到阻塞队列
多线程并发处理,线程池
队列的操作 (添加,移除,生产者消费者模型)
*/
public static void main(String[] args) {
// test1();
// test2();
// test3();
test4();
}
// 抛异常的情况 add()/remove()
public static void test1(){
// 创建队列,并设置队列的大小
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println(blockingQueue.element()); // 检查队首的元素
// Queue full
// System.out.println(blockingQueue.add("d"));
System.out.println("+++++++++++++++++++=");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// java.util.NoSuchElementException
// System.out.println(blockingQueue.remove());
}
// 不抛异常的情况 offer()/poll()
public static void test2(){
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d")); // false
System.out.println(blockingQueue.peek()); // 检查队首的元素
System.out.println("_______________----");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll()); // null
}
// 等待阻塞 put()/ take()
public static void test3(){
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
try {
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// 阻塞
// blockingQueue.put("c");
blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 超时等待
public static void test4(){
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
try {
System.out.println(blockingQueue.offer("a", 2, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("b", 2, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("c", 2, TimeUnit.SECONDS));
//
// System.out.println(blockingQueue.offer("d", 2, TimeUnit.SECONDS));
System.out.println("+++++++++++++++++++");
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 四组API(当队列满/空时,有不同的策略)
方法(添加/取出) | 处理方式 |
---|---|
add()/remove() | 抛出异常 |
offer()/poll() 无参 | 不抛异常 |
put()/take() | 等待阻塞 |
offer()/poll() 多了两个参数:超时时间和单位 | 超时等待 |
Semaphore(信号量)
synchronized
和 ReentrantLock
都是一次只允许一个线程访问某个资源,Semaphore
(信号量)可以指定多个线程同时访问某个资源。
public class Chap10 {
// Semaphore(信号量)
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(50); // 创建指定大小的线程池
Semaphore semaphore = new Semaphore(5); // 创建许可
try{
for (int i = 0; i < 100; i++) {
int finalI = i;
semaphore.acquire(); // 获得许可(可以执行的线程数量为:5/1 = 5)
executorService.execute(() -> System.out.println(Thread.currentThread().getName() + ": " + finalI));
semaphore.release(); // 释放许可
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
- 创建许可时,可以指定第二个参数,是否为公平模式,默认是非公平模式
- 获得和释放许可时,可以指定参数,表示获取和释放许可的数量
CountDownLatch(减法计数器)
public class Chap11 {
//CountDownLatch()
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(50);
CountDownLatch countDownLatch = new CountDownLatch(50); // 总数为50,必须要执行任务的时候使用
try{
for (int i = 0; i < 50; i++) {
int finalI = i;
executorService.execute(() -> System.out.println(Thread.currentThread().getName() + ": " + finalI));
countDownLatch.countDown(); // 每次执行一条线程,计数减一
}
}finally {
executorService.shutdown();
}
countDownLatch.await(); // 等待计数器归零,然后再继续往后执行
System.out.println("倒数完成,后续的主线程开始执行");
}
}
- 一个过多个线程需要等待前面n个线程执行完成后才执行,可以使用
- 多个线程有并行需求的时候,可以使用(计数完成,多条线程同时执行)
- 如果控制不当,可能会造成死锁,导致后续线程无法执行
CyclicBarrier: 加法计数器
public class Chap12 {
public static void main(String[] args) {
// CyclicBarrier: 加法计数器
// 每次执行完一条线程之后,将计数加一,当计数为规定值的时候,执行指定的某个操作
ExecutorService executorService = Executors.newFixedThreadPool(10);
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("成功召唤神龙"));
for (int i = 1; i < 8; i++) {
int finalI = i;
executorService.execute(() -> {
System.out.println("拿到第" + finalI + "颗龙珠");
try {
cyclicBarrier.await(); // 等待,有一个线程经过,计数加一
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
- 每次计数执行完操作之后,会将计数器重置,可以多次使用
- 多个线程,在任意一个线程没有完成,所有线程都必须等待