目录
一、JUC定义
JUC实际上就是我们对于jdk中java.util .concurrent 工具包的简称,这个包下都是Java处理线程相关的类,自jdk1.5后出现。目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题!
二、JUC框架
UC是包的简称,JUC可能也是Java核心里最难的一块儿,JUC指的是Java的并发工具包,里边提供了各种各样的控制同步和线程通信的工具类。学习JUC之前,最重要的是了解JUC的结构是什么样的。就如同Java的集合框架的结构一样,JUC也有自己框架结构,只是往往被大家忽略,笔者就简单的梳理了下JUC的框架结构,JUC的框架结构不同于集合,它并非是实现继承框架结构。
三、JUC中常用类汇总
- tools(工具类):又叫信号量三组工具类,包含有
-
CountDownLatch(闭锁) 是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
-
CyclicBarrier(栅栏) 之所以叫barrier,是因为是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 ,并且在释放等待线程后可以重用。
-
Semaphore(信号量) 是一个计数信号量,它的本质是一个“共享锁“。信号量维护了一个信号量许可集。线程可以通过调用 acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。
- executor(执行者):是Java里面线程池的顶级接口,但它只是一个执行线程的工具,真正的线程池接口是ExecutorService,里面包含的类有:
-
atomic(原子性包):是JDK提供的一组原子操作类,包含有AtomicBoolean、AtomicInteger、AtomicIntegerArray等原子变量类,他们的实现原理大多是持有它们各自的对应的类型变量value,而且被volatile关键字修饰了。这样来保证每次一个线程要使用它都会拿到最新的值。
-
locks(锁包):是JDK提供的锁机制,相比synchronized关键字来进行同步锁,功能更加强大,它为锁提供了一个框架,该框架允许更灵活地使用锁包含的实现类有:
-
ReentrantLock 它是独占锁,是指只能被独自占领,即同一个时间点只能被一个线程锁获取到的锁。
-
ReentrantReadWriteLock 它包括子类ReadLock和WriteLock。ReadLock是共享锁,而WriteLock是独占锁。
-
LockSupport 它具备阻塞线程和解除阻塞线程的功能,并且不会引发死锁。
- collections(集合类):主要是提供线程安全的集合, 比如:
-
ArrayList对应的高并发类是CopyOnWriteArrayList,
-
HashSet对应的高并发类是 CopyOnWriteArraySet,
-
HashMap对应的高并发类是ConcurrentHashMap等等
- 分类总结
-
JUC的atomic包下运用了CAS的AtomicBoolean、AtomicInteger、AtomicReference等原子变量类
-
JUC的locks包下的AbstractQueuedSynchronizer(AQS)以及使用AQS的ReentantLock(显式锁)、ReentrantReadWriteLock
-
附:运用了AQS的类还有:
Semaphore、CountDownLatch、ReentantLock(显式锁)、ReentrantReadWriteLock -
JUC下的一些同步工具类:
CountDownLatch(闭锁)、Semaphore(信号量)、CyclicBarrier(栅栏)、FutureTask -
JUC下的一些并发容器类:
ConcurrentHashMap、CopyOnWriteArrayList -
读写分离:
CopyOnWriteArrayList
写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。
写操作需要加锁,防止并发写入时导致写入数据丢失。
写操作结束之后需要把原始数组指向新的复制数组。 -
JUC下的一些Executor框架的相关类:
线程池的工厂类->Executors 线程池的实现类->ThreadPoolExecutor/ForkJoinPool -
JUC下的一些阻塞队列实现类:
ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue
四、定义
进程和线程
- 进程
- 概述:进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
- 狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
- 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
- 线程
线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之 中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 线程是独立调度和分派的基本单位。
- 同一进程中的多条线程将共享该进程中的全部系统资源。
- 一个进程可以有很多线程,每条线程并行执行不同的任务。可并发执行。
当然,我们Java都知道的线程的同步是Java多线程编程的难点,因为要给哪些地方是共享资源(竞争资源),什么时候要考虑同步等,都是编程中的难点。
创建线程的几种常见的方式
- 通过实现Runnable接口来创建Thread线程
- 通过继承Thread类来创建一个线程
- 通过实现Callable接口来创建Thread线程
- 备注:详细看我写的上一篇文章:Java实现多线程的4种方式
并发和并行
在了解并发和并行之前,让我们先来看一看串行是什么样的吧。
- 串行模式:
串行模式:即表示所有任务都是按先后顺序进行。串行是一次只能取的一个任务,并执行这个任务。
举个生活中的小例子:就是在火车站买票,今天只开放这一个窗口卖票,那么我们只有等到前面的人都买了,才能轮到我们去买。即按先后顺序买到票。 - 并行模式:
概述:一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。对比地,并发是指:在同一个时间段内,两个或多个程序执行,有时间上的重叠(宏观上是同时,微观上仍是顺序执行)。
并行模式:并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。
我们还是用上面那个例子:还是在买票,以前是只有一个窗口卖票,但是近几年发展起来了,现在有五个窗口卖票啦,大大缩短了人们买票的时间。
并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。不过并行的效率,一方面受多进程/线程编码的好坏的影响,另一方面也受硬件角度上的CPU的影响。 - 并发:
并发:并发指的是多个程序可以同时运行的一种现象,并发的重点在于它是一种现象,并发描述的是多进程同时运行的现象。但真正意义上,一个单核心CPU任一时刻都只能运行一个线程。所以此处的"同时运行"表示的不是真的同一时刻有多个线程运行的现象(这是并行的概念),而是提供了一种功能让用户看来多个程序同时运行起来了,但实际上这些程序中的进程不是一直霸占 CPU 的,而是根据CPU的调度,执行一会儿停一会儿。
小小的总结一下:
并发:即同一时刻多个线程在访问同一个资源,多个线程对一个点
例子:秒杀活动、12306抢回家的票啦、抢演唱会的票…
并行:多个任务一起执行,之后再汇总
例子:电饭煲煮饭、用锅炒菜,两个事情一起进行,(最后我们一起干饭啦干饭啦😁)
用户线程和守护线程
-
用户线程:指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。
-
守护线程:是指在程序运行的时候在后台提供一种通用服务的线程,用来服务于用户线程;不需要上层逻辑介入,当然我们也可以手动创建一个守护线程。(用白话来说:就是守护着用户线程,当用户线程死亡,守护线程也会随之死亡)
比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
五、synchronized
synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰某一处代码块,被修饰的代码块称为同步语句块。作用范围就是{}之间。作用的对象是调用这个代码块的对象。
synchronized (this){
System.out.println("同步代码块 ");
}
- 修饰在方法上,被修饰的方法就称为同步方法。作用范围则是整个方法。作用的对象则是调用这个方法的对象。
public synchronized void sale() {
}
注:synchronized 关键字不能被继承,如果父类中某方法使用了synchronized 关键字,字类又正巧覆盖了,此时,字类默认情况下是不同步的,必须显示的在子类的方法上加上才可。当然,如果在字类中调用父类中的同步方法,这样虽然字类并没有同步方法,但子类调用父类的同步方法,子类方法也相当同步了。
- 修饰某个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
public static synchronized void test(){
}
- 修饰某个类,其作用的范围是 synchronized 后面括号括起来的部分,作用的对象是这个类的所有对象。
class Ticket {
public void sale() {
synchronized (Ticket.class) {
}
}
}
六、Lock锁(重点)
什么是 Lock
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。
锁类型
- 可重入锁:在执行对象中所有同步方法不用再次获得锁
- 可中断锁:在等待获取锁过程中可中断
- 公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利
- 读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写
Lock接口
public interface Lock {
void lock(); //获得锁。
/**
除非当前线程被中断,否则获取锁。
如果可用,则获取锁并立即返回。
如果锁不可用,则当前线程将出于线程调度目的而被禁用并处于休眠状态,直到发生以下两种情况之一:
锁被当前线程获取;
要么其他一些线程中断当前线程,支持中断获取锁。
如果当前线程:
在进入此方法时设置其中断状态;
要么获取锁时中断,支持中断获取锁,
*/
void lockInterruptibly() throws InterruptedException;
/**
仅在调用时空闲时才获取锁。
如果可用,则获取锁并立即返回值为true 。 如果锁不可用,则此方法将立即返回false值。
*/
boolean tryLock();
//比上面多一个等待时间
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 解锁
void unlock();
//返回绑定到此Lock实例的新Condition实例。
Condition newCondition(); 。
}
下面讲几个常用方法的使用。
lock()、unlock()
lock()是最常用的方法之一,作用就是获取锁,如果锁已经被其他线程获得,则当前线程将被禁用以进行线程调度,并处于休眠状态,等待,直到获取锁。
如果使用到了lock的话,那么必须去主动释放锁,就算发生了异常,也需要我们主动释放锁,因为lock并不会像synchronized一样被自动释放。所以使用lock的话,必须是在try{}catch(){}中进行,并将释放锁的代码放在finally{}中,以确保锁一定会被释放,以防止死锁现象的发生。
unlock()的作用就是主动释放锁。
lock接口的类型有好几个实现类,这里是随便找了个。
Lock lock = new ReentrantLock();
try {
lock.lock();
System.out.println("上锁了");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println("解锁了");
}
newCondition
关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock 锁的 newContition()方法返回 Condition 对象,Condition 类 也可以实现等待/通知模式。 用 notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以 进行选择性通知, Condition 比较常用的两个方法:
await():会使当前线程等待,同时会释放锁,当等到其他线程调用signal()方法时,此时这个沉睡线程会重新获得锁并继续执行代码(在哪里沉睡就在哪里唤醒)。
signal():用于唤醒一个等待的线程。
注意:在调用 Condition 的 await()/signal()方法前,也需要线程持有相关 的 Lock 锁,调用 await()后线程会释放这个锁,在调用singal()方法后会从当前 Condition对象的等待队列中,唤醒一个线程,后被唤醒的线程开始尝试去获得锁, 一旦成功获得锁就继续往下执行。
在这个地方我们举个例子来用代码写一下:
这里就不举例synchronized 实现了,道理都差不多。
例子:我们有两个线程,实现对一个初始值是0的number变量,一个线程当number = =0时 对number值+1,另外一个线程当number = = 1时对number-1。
class Share {
private Integer number = 0;
private ReentrantLock lock = new ReentrantLock();
private Condition newCondition = lock.newCondition();
// +1 的方法
public void incr() {
try {
lock.lock(); // 加锁
while (number != 0) {
newCondition.await();//沉睡
}
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
newCondition.signal(); //唤醒另一个沉睡的线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// -1 的方法
public void decr() {
try {
lock.lock();
while (number != 1) {
newCondition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
newCondition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class LockDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i=0;i<=10;i++){
share.incr();
}
},"AA").start();
new Thread(()->{
for (int i=0;i<=10;i++){
share.decr();
}
},"BB").start();
/**
* AA::1
* BB::0
* AA::1
* BB::0
* .....
*/
}
}
原文链接:https://blog.csdn.net/u011397981/article/details/129836706
原文链接:https://blog.csdn.net/u011397981/article/details/129836706