JUC学习
Posted 三笠·阿卡曼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC学习相关的知识,希望对你有一定的参考价值。
线程基础知识复习
多线程的编程步骤
- 创建资源类,在资源类中创建属性和操作方法;
- 在操作方法中进行判断、干活、通知
- 创建多个线程,调用资源类的操作方法;
- 要防止虚假唤醒问题,要把判断条件改成while
为什么多线程极其重要
硬件方面
摩尔定律失效
它是由英特尔创始人之一Gordon Moore(戈登·摩尔)提出来的。其内容为:
当价格不变时,集成电路上可容纳的元器件的数目约每隔18-24个月便会增加一倍,性能也将提升一倍。
换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。这一定律揭示了信息技术进步的速度。
可是从2003年开始CPU主频已经不再翻倍,而是采用多核而不是更快的主频。
摩尔定律失效。
在主频不再提高且核数在不断增加的情况下,要想让程序更快就要用到并行或并发编程。
软件方面
高并发系统:需要处理异步+回调等生产需求;
多线程相关基本概念
进程:是程序的一次执行,,是系统进行资源分配和调度的基本单位,每一个进程都有自己的内存空间和系统资源;
线程:在同一个进程中又可以执行多个任务,而这每一个任务就可以看作是一个线程,一个进程会有1~多个线程;
用户线程和守护线程
Java线程分为用户线程和守护线程;
线程的daemon属性为true表示是守护线程,false表示是用户线程;
- 用户线程:是系统的工作线程,他会完成这个程序需要完成的业务操作;
- 守护线程:是一种特殊的线程,在后台默默的完成一些系统性的服务,比如垃圾回收线程;
总结:
- 当程序中所有用户线程执行完毕之后,不管守护线程是否结束,系统都会自动退出
- 如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以当系统只剩下守护进程的时候,java虚拟机会自动退出;
- 设置守护线程,需要在start()方法之前进行
实现线程的四种方式
- 继承Thread类;
- 实现Runnable接口;
- 实现Callable接口;
- 线程池实现;
CompletableFuture
Future和Callable接口
-
Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
-
Callable接口中定义了需要有返回的任务需要实现的方法。
-
比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,过了一会才去获取子任务的执行结果。
JUC学习
JUC概述
什么是JUC
在 Java 中,线程部分是一个重点,本篇文章说的 JUC 也是关于线程的。JUC就是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包,JDK1.5 开始出现的。
进程和线程的概念
- 进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
- 线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 总结来说:
- 进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。
- 线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。
线程的状态
- NEW,(新建)
- RUNNABLE,(准备就绪)
- BLOCKED,(阻塞)
- WAITING,(不见不散)
- TIMED_WAITING,(过时不候)
- TERMINATED;(终结)
wait和sleep的区别
- sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用;
- sleep不会释放锁,他也不需要占用锁;wait会释放锁,调用它的前提是当前线程占有锁(代码要在synchronized中);
- 他们都可以被interrupted方法中断;
管程(Monitor,监视器)
是一种同步机制,保证同一个时间,只要一个线程访问被保护的数据或者代码;jvm同步基于进入和退出,使用管程对象实现的;
用户线程与守护线程
- 用户线程:
- 守护线程:
LOCK接口讲解
Synchronized关键字回顾
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法义的一部分,因此,synchronized 关键字不能被继承。如果在父类中的某个方法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized 关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的
所有对象; - 修改一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用主
的对象是这个类的所有对象。
什么是Lock锁
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 提供了比 synchronized 更多的功能。
Lock和Synchronized的区别
- Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
• Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
Lock接口
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Con
lock方法
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用 Lock 必须在 try{}catch{}块中进行,并且将释放锁的操作放在finally 块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用 Lock来进行同步的话,是以下面这种形式去使用的:
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
newCondition
关键字 synchronized 与 ==wait()/notify()==这两个方法一起使用可以实现等待/通知模式, Lock 锁的 newContition()方法返回 Condition 对象,Condition 类也可以实现等待/通知模式。用 notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以进行选择性通知, Condition 比较常用的两个方法:
- await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重新获得锁并继续执行。
- signal()用于唤醒一个等待的线程。
注意:在调用 Condition 的 await()/signal()方法前,也需要线程持有相关的 Lock 锁,调用 await()后线程会释放这个锁,在 singal()调用后会从当前Condition 对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦获得锁成功就继续执行。
ReentrantLock
ReentrantLock,意思是“可重入锁”,关于可重入锁的概念将在后面讲述。
ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法。下面通过一些实例看具体看一下如何使用。
小结
Lock 和 synchronized 有以下几点不同:
- Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
- synchronized 在发生异常时,会==自动释放线程占有的锁,=因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
- Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断;
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
- Lock 可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized。
线程间通信
synchronized实现线程间交替打印(线程间通信)
始终牢记方法:
* 创建资源类,在资源类中创建属性和操作方法;
* 在操作方法中进行判断、干活、通知
* 创建多个线程,调用资源类的操作方法;
package com.vleus.juc.sync;
/**
* @author vleus
* @date 2021年07月14日 22:06
*/
//第一步:创建资源类,定义属性和方法
class Share {
//初始值
private int number = 0;
//+1方法
public synchronized void incr() throws InterruptedException {
//第二步:判断 干活 通知
if (number != 0) { //判断number是否是0,如果不是0,等待
this.wait();
}
//如果number值是0,就+1操作
number++;
System.out.println(Thread.currentThread().getName() + " :: " + number);
//通知其他线程
this.notifyAll();
}
//-1方法
public synchronized void decr() throws InterruptedException {
// 判断
if (number != 1) {
this.wait();
}
//干活
number--;
System.out.println(Thread.currentThread().getName() + " :: " + number);
//通知其他线程
this.notifyAll();
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
//第三步 创建多个线程,调用资源类的操作方法
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
}
}
输出效果如下:
wait可能导致虚假唤醒
多个线程执行上述交替打印会出现虚假唤醒的问题;需要将判断条件放到while循环中;
Lock锁实现1、0的交替打印
package com.vleus.juc.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author vleus
* @date 2021年07月14日 22:35
*/
//创建资源类,定义属性和方法
class Share {
private int number = 0;
//创建lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1
public void incr() throws InterruptedException {
//上锁
lock.lock();
try {
//判断
while (number != 0) {
condition.await();
}
//干活
number++;
System.out.println(Thread.currentThread().getName() + " :: " + number);
//通知
condition.signalAll();
} finally {
//解锁
lock.unlock();
}
}
//-1
public void decr() throws InterruptedException {
//上锁
lock.lock();
try {
//判断
while (number != 1) {
condition.await();
}
//干活
number--;
System.out.println(Thread.currentThread().getName() + " :: " + number);
//通知
condition.signalAll();
} finally {
//解锁
lock.unlock();
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "CC").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "DD").start();
}
}
打印结果如下:
线程间的定制化通信
启动三个线程,按照如下要求:
- AA打印五次,BB打印10次,CC打印15次;
- AA打印五次,BB打印10次,CC打印15次;
进行10轮;
package com.vleus.juc.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author vleus
* @date 2021年07月14日 23:06
*/
//第一步:创建资源类,定义属性和操作方法
class ShareResource{
//定义标志位
private int flag = 1; //1 AA 2 BB 3 CC
//创建lock锁
private Lock lock = new ReentrantLock();
//创建3个Condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
//打印5次,参数第几轮
public void print5(int loop) throws InterruptedException {
lock.lock();
try{
//判断
while (flag != 1) {
c1.await();
}
//干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " :: " + i + " : 轮数: " + loop);
}
//通知
flag = 2; //先修改标志位2
c2.signal(); //通知BB线程
}finally {
//释放锁
lock.unlock();
}
}
//打印10次,参数第几轮
public void print10(int loop) throws InterruptedException {
lock.lock();
try{
//判断
while (flag != 2) {
c2.await();
}
//干活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + " :: " + i + " : 轮数: " + loop);
}
//通知
flag = 3; //先修改标志位2
c3.signal(); //通知BB线程
}finally {
//释放锁
lock.unlock();
}
}
//打印15次,参数第几轮
public void print15(int loop) throws InterruptedException {
lock.lock();
try{
//判断
while (flag != 3) {
c3.await();
}
//干活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + " :: " + i + " : 轮数: " + loop);
}
//通知
flag = 1; //先修改标志位1
c1.signal(); //通知AA线程
}finally {
//释放锁
lock.unlock();
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
shareResource.print5(i);
} catch juc学习四(集合不安全问题)
JUC 系列:AQS -->学习 Doug Lea 大神思想
JUC 系列:AQS -->学习 Doug Lea 大神思想
JUC 系列:AQS -->学习 Doug Lea 大神思想