生命无罪,健康万岁,我是laity。
我曾七次鄙视自己的灵魂:
第一次,当它本可进取时,却故作谦卑;
第二次,当它在空虚时,用爱欲来填充;
第三次,在困难和容易之间,它选择了容易;
第四次,它犯了错,却借由别人也会犯错来宽慰自己;
第五次,它自由软弱,却把它认为是生命的坚韧;
第六次,当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副;
第七次,它侧身于生活的污泥中,虽不甘心,却又畏首畏尾。
JUC:指的是java.util三个并发编程工具包的简称
java.util 工具包
线程和进程,如果不能使用一句话说出来的 - 技术不扎实!
进程:一个程序,QQ.exe - 程序的集合
线程:开了一个进程notepad++,写字 - 会自动保存(线程负责的!)
此处为语雀内容卡片,点击链接查看:https://www.yuque.com/laity-champion/base/fk2vsqq25g3egpyl
先说结论:不可以的开启
public static void main(String[] args) {
new Thread().start();
}
在Java等编程语言中,“native” 用来表示由本地代码(通常是用C或C++编写的)实现的方法。这些方法可以通过 Java Native Interface(JNI)调用。这样的方法通常涉及对底层系统或硬件的直接访问,提供了与平台相关的功能。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
// 本地方法,调用底层的C++, Java是无法直接操作硬件
private native void start0();
并发和并行
并发:并发编程 -> 多线程操作同一个资源
并行:多个人一起行走
充分利用CPU资源 -> 提高效率
管程是一种用于同步和互斥的高级抽象。它包含了共享数据和用于访问这些数据的一组过程。管程提供了一种结构化的方法来确保在并发程序中对共享资源的访问是安全的。Java中的 synchronized 关键字就是一种管程的实现。
用户线程是由应用程序开发人员创建和控制的线程。这些线程在用户空间中运行,而不需要内核支持。用户线程的创建、调度和销毁都由用户空间的线程库来管理,而不涉及操作系统内核的介入。
守护线程是在程序运行时在后台提供服务的线程。当所有的非守护线程结束时,程序会退出,不会等待守护线程执行完毕。典型的守护线程例子是垃圾回收线程。在Java中,可以通过 setDaemon(true) 方法将线程设置为守护线程。
Thread.State 线程枚举类可以自行查看
/**
* A thread state. A thread can be in one of the following states:
*
* - {@link #NEW}
* A thread that has not yet started is in this state.
*
* - {@link #RUNNABLE}
* A thread executing in the Java virtual machine is in this state.
*
* - {@link #BLOCKED}
* A thread that is blocked waiting for a monitor lock
* is in this state.
*
* - {@link #WAITING}
* A thread that is waiting indefinitely for another thread to
* perform a particular action is in this state.
*
* - {@link #TIMED_WAITING}
* A thread that is waiting for another thread to perform an action
* for up to a specified waiting time is in this state.
*
* - {@link #TERMINATED}
* A thread that has exited is in this state.
*
*
*
*
* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
public enum State {
// 线程新生
NEW,
// 线程运行
RUNNABLE,
// 线程阻塞
BLOCKED,
// 线程等待
WAITING,
// 超时等待 - 死死的等
TIMED_WAITING,
// 线程终止
TERMINATED;
}
可以看到有6个状态,分别是:
来自不同的类
关于锁的释放
使用的范围不同
是否需要捕获异常
package com.laity.demo1;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.demo1.SaleTicketDemo01
* @Date: 2023年11月21日 23:26
* @Description: 基本卖票例子
* 真正的多线程开发,公司中的开发,降低耦合性(高内聚、低耦合)
* 线程就是一个单独的资源类,没有附属的操作
* 资源类包含:1、属性;2、方法
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
// 多线程操作
// 并发:多个线程操作同一个资源类,
// 操作:把资源类丢进线程
Ticket ticket = new Ticket();
new Thread(() -> {
for (int j = 0; j < 30; j++) {
ticket.sale();
}
}, "线程A").start();
new Thread(() -> {
for (int j = 0; j < 30; j++) {
ticket.sale();
}
}, "线程B").start();
new Thread(() -> {
for (int j = 0; j < 30; j++) {
ticket.sale();
}
}, "线程C").start();
}
}
// 单独的资源类 OOP编程
class Ticket {
// 属性、方法
private int number = 50;
// 卖票的方法
// synchronized 本质就是队列,锁
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + " - 卖出了第" + (number--) + "张票" + ",剩余" + number);
}
}
}
结果 就会很有顺序,不加synchronized就会很乱:
线程A - 卖出了第10张票,剩余9
线程A - 卖出了第9张票,剩余8
线程A - 卖出了第8张票,剩余7
线程A - 卖出了第7张票,剩余6
线程A - 卖出了第6张票,剩余5
线程A - 卖出了第5张票,剩余4
线程A - 卖出了第4张票,剩余3
线程A - 卖出了第3张票,剩余2
线程A - 卖出了第2张票,剩余1
线程A - 卖出了第1张票,剩余0
Process finished with exit code 0
实现类
公平锁:十分公平,先来后到(阳光普照,效率相对低一些)
非公平锁:十分不公平,可以插队(默认,可能会造成线程饿死,但是效率高)
package com.laity.demo1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.demo1.SaleTicketDemo01
* @Date: 2023年11月21日 23:26
* @Description: 基本卖票例子
* 真正的多线程开发,公司中的开发,降低耦合性(高内聚、低耦合)
* 线程就是一个单独的资源类,没有附属的操作
* 资源类包含:1、属性;2、方法
*/
public class SaleTicketDemo02 {
public static void main(String[] args) {
// 多线程操作
// 并发:多个线程操作同一个资源类,
// 操作:把资源类丢进线程
Ticket2 ticket = new Ticket2();
new Thread(() -> {
for (int j = 0; j < 30; j++) {
ticket.sale();
}
}, "线程A").start();
new Thread(() -> {
for (int j = 0; j < 30; j++) {
ticket.sale();
}
}, "线程B").start();
new Thread(() -> {
for (int j = 0; j < 30; j++) {
ticket.sale();
}
}, "线程C").start();
}
}
// 单独的资源类 OOP编程
// lock三部曲
// 1、new ReentrantLock();
// 2、lock.lock() // 加锁
// 3、finally -> lock.unlock(); // 解锁
class Ticket2 {
// 属性、方法
private int number = 50;
// 使用 juc lock锁
Lock lock = new ReentrantLock();
// 卖票的方法
public void sale() {
// 加锁
lock.lock();
// ctrl + alt + t 快捷键
try {
// 业务代码
if (number > 0) {
System.out.println(Thread.currentThread().getName() + " - 卖出了第" + (number--) + "张票" + ",剩余" + number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
}
效果与Synchronized一样
1.synchronized 是内置的Java关键字,Lock是Java的一个接口
2.synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.synchronized 会自动释放锁,Lock必须手动释放锁!如果不释放锁,会造成死锁
4.synchronized 线程一在获得锁的情况下阻塞了,第二个线程就只能傻傻的等着;Lock就不一定会等待下去
5.synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,非公平/公平(可以自己设置,默认非公平锁)
6.synchronized 适合锁少量的同步代码;Lock适合锁大量同步代码!
7.Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者性能是差不多的,而当竞争资源非常激烈时(即大量线程同时竞争),此时Lock的性能要远远优于synchronized
个人理解:锁是让一个线程在时间内走一个执行代码,锁的是多个线程锁共享的对象(标志位、临界资源)和Class(static修饰)。
Synchronized版本 -> wait(等待)、notify(唤醒)
JUC版本lock -> Condition
面试四大考点:单例模式单例设计模式-复习、排序算法、生产者和消费者问题、死锁
Synchronized版本 -> wait(等待)、notify(唤醒) : 传统的三剑客
package com.laity.demo2;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.demo2.Communication01
* @Date: 2023年11月22日 00:25
* @Description: 线程直接通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程交替执行 t1 t2 两个线程操作同一个变量,进行 +1 | -1
*/
public class Communication01 {
static Thread t1 = null, t2 = null;
public static void main(String[] args) {
Data data = new Data();
t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
t2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2");
t1.start();
t2.start();
}
}
// 资源类:线程就是一个单独的资源类,没有附属的操作
// 判断等待、业务、通知
class Data {
private int number = 0;
// +1
// 只要有并发就一定要加锁
public synchronized void increment() throws InterruptedException {
if (number != 0) {
// 等待
this.wait();
}
// 业务
number++;
// 加1后通知其它线程我加一完毕了
System.out.println(Thread.currentThread().getName() + "==>" + number);
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if (number == 0) {
// 等待
this.wait();
}
number--;
// 通知其它线程我减一完毕了
System.out.println(Thread.currentThread().getName() + "==>" + number);
this.notifyAll();
}
}
输出结果:
t1==>1
t2==>0
t1==>1
t2==>0
t1==>1
t2==>0
t1==>1
t2==>0
t1==>1
t2==>0
t1==>1
t2==>0
t1==>1
t2==>0
t1==>1
t2==>0
t1==>1
t2==>0
t1==>1
t2==>0
两个线程可以看到结果是很正常的;但是也问题存在,4个线程或者8个线程,就不安全了
package com.laity.demo2;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.demo2.Communication01
* @Date: 2023年11月22日 00:25
* @Description: 线程直接通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程交替执行 t1 t2 两个线程操作同一个变量,进行 +1 | -1
*/
public class Communication01 {
static Thread t1 = null, t2 = null, t3 = null, t4 = null;
public static void main(String[] args) {
Data data = new Data();
t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
t2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2");
t3 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t3");
t4 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
// 资源类:线程就是一个单独的资源类,没有附属的操作
// 判断等待、业务、通知
class Data {
private int number = 0;
// +1
// 只要有并发就一定要加锁
public synchronized void increment() throws InterruptedException {
if (number != 0) {
// 等待
this.wait();
}
// 业务
number++;
// 加1后通知其它线程我加一完毕了
System.out.println(Thread.currentThread().getName() + "==>" + number);
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if (number == 0) {
// 等待
this.wait();
}
number--;
// 通知其它线程我减一完毕了
System.out.println(Thread.currentThread().getName() + "==>" + number);
this.notifyAll();
}
}
输出结果:
t1==>1
t3==>0
t4==>1
t1==>2
t4==>3
t2==>2
t2==>1
t2==>0
t3==>-1
t3==>-2
t3==>-3
t3==>-4
t3==>-5
t3==>-6
t3==>-7
t3==>-8
t3==>-9
t2==>-10
t2==>-11
t2==>-12
t2==>-13
t2==>-14
t2==>-15
t2==>-16
t4==>-15
t1==>-14
t4==>-13
t1==>-12
t4==>-11
t1==>-10
t4==>-9
t1==>-8
t4==>-7
t1==>-6
t4==>-5
t1==>-4
t4==>-3
t1==>-2
t4==>-1
t1==>0
四个线程的运行结果就很明显了,可以看出是有问题的,但是问题是由什么造成的呢?
虚假唤醒 - JDK源码文档中有提及:
由if判断改为官方的while判断即可
输出结果:
D:\Java\JDK\bin\java.exe "-javaagent:E:\IdeaInstall\IntelliJ IDEA 2020.3.4\lib\idea_rt.jar=64439:E:\IdeaInstall\IntelliJ IDEA 2020.3.4\bin" -Dfile.encoding=UTF-8 -classpath D:\java\JDK\jre\lib\charsets.jar;D:\java\JDK\jre\lib\deploy.jar;D:\java\JDK\jre\lib\ext\access-bridge-64.jar;D:\java\JDK\jre\lib\ext\cldrdata.jar;D:\java\JDK\jre\lib\ext\dnsns.jar;D:\java\JDK\jre\lib\ext\jaccess.jar;D:\java\JDK\jre\lib\ext\jfxrt.jar;D:\java\JDK\jre\lib\ext\localedata.jar;D:\java\JDK\jre\lib\ext\mysql-connector-java-5.1.48.jar;D:\java\JDK\jre\lib\ext\nashorn.jar;D:\java\JDK\jre\lib\ext\sunec.jar;D:\java\JDK\jre\lib\ext\sunjce_provider.jar;D:\java\JDK\jre\lib\ext\sunmscapi.jar;D:\java\JDK\jre\lib\ext\sunpkcs11.jar;D:\java\JDK\jre\lib\ext\zipfs.jar;D:\java\JDK\jre\lib\javaws.jar;D:\java\JDK\jre\lib\jce.jar;D:\java\JDK\jre\lib\jfr.jar;D:\java\JDK\jre\lib\jfxswt.jar;D:\java\JDK\jre\lib\jsse.jar;D:\java\JDK\jre\lib\management-agent.jar;D:\java\JDK\jre\lib\plugin.jar;D:\java\JDK\jre\lib\resources.jar;D:\java\JDK\jre\lib\rt.jar;D:\LaityWork\architecture\foodie-dev\target\classes;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter\2.1.5.RELEASE\spring-boot-starter-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot\2.1.5.RELEASE\spring-boot-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-context\5.1.7.RELEASE\spring-context-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-autoconfigure\2.1.5.RELEASE\spring-boot-autoconfigure-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-logging\2.1.5.RELEASE\spring-boot-starter-logging-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\logging\log4j\log4j-to-slf4j\2.11.2\log4j-to-slf4j-2.11.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\slf4j\jul-to-slf4j\1.7.26\jul-to-slf4j-1.7.26.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-core\5.1.7.RELEASE\spring-core-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-jcl\5.1.7.RELEASE\spring-jcl-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\yaml\snakeyaml\1.23\snakeyaml-1.23.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-web\2.1.5.RELEASE\spring-boot-starter-web-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-json\2.1.5.RELEASE\spring-boot-starter-json-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\core\jackson-databind\2.9.8\jackson-databind-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\core\jackson-core\2.9.8\jackson-core-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.9.8\jackson-datatype-jdk8-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.9.8\jackson-datatype-jsr310-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\module\jackson-module-parameter-names\2.9.8\jackson-module-parameter-names-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-tomcat\2.1.5.RELEASE\spring-boot-starter-tomcat-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\tomcat\embed\tomcat-embed-core\9.0.19\tomcat-embed-core-9.0.19.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\tomcat\embed\tomcat-embed-el\9.0.19\tomcat-embed-el-9.0.19.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.19\tomcat-embed-websocket-9.0.19.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\hibernate\validator\hibernate-validator\6.0.16.Final\hibernate-validator-6.0.16.Final.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\jboss\logging\jboss-logging\3.3.2.Final\jboss-logging-3.3.2.Final.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-web\5.1.7.RELEASE\spring-web-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-beans\5.1.7.RELEASE\spring-beans-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-webmvc\5.1.7.RELEASE\spring-webmvc-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-expression\5.1.7.RELEASE\spring-expression-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-aop\2.1.5.RELEASE\spring-boot-starter-aop-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-aop\5.1.7.RELEASE\spring-aop-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\aspectj\aspectjweaver\1.9.4\aspectjweaver-1.9.4.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-configuration-processor\2.1.5.RELEASE\spring-boot-configuration-processor-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\projectlombok\lombok\1.18.10\lombok-1.18.10.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\mysql\mysql-connector-java\5.1.47\mysql-connector-java-5.1.47.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\spring\boot\mybatis-spring-boot-starter\2.1.0\mybatis-spring-boot-starter-2.1.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-jdbc\2.1.5.RELEASE\spring-boot-starter-jdbc-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\zaxxer\HikariCP\3.2.0\HikariCP-3.2.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-jdbc\5.1.7.RELEASE\spring-jdbc-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-tx\5.1.7.RELEASE\spring-tx-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\spring\boot\mybatis-spring-boot-autoconfigure\2.1.0\mybatis-spring-boot-autoconfigure-2.1.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\mybatis\3.5.2\mybatis-3.5.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\mybatis-spring\2.0.2\mybatis-spring-2.0.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-spring-boot-starter\2.1.5\mapper-spring-boot-starter-2.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-core\1.1.5\mapper-core-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\javax\persistence\persistence-api\1.0\persistence-api-1.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-base\1.1.5\mapper-base-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-weekend\1.1.5\mapper-weekend-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-spring\1.1.5\mapper-spring-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-extra\1.1.5\mapper-extra-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-spring-boot-autoconfigure\2.1.5\mapper-spring-boot-autoconfigure-2.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\pagehelper\pagehelper-spring-boot-starter\1.2.12\pagehelper-spring-boot-starter-1.2.12.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\pagehelper\pagehelper-spring-boot-autoconfigure\1.2.12\pagehelper-spring-boot-autoconfigure-1.2.12.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\pagehelper\pagehelper\5.1.10\pagehelper-5.1.10.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\jsqlparser\jsqlparser\2.0\jsqlparser-2.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\commons-codec\commons-codec\1.11\commons-codec-1.11.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\commons\commons-lang3\3.4\commons-lang3-3.4.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\commons-io\commons-io\2.4\commons-io-2.4.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-swagger2\2.4.0\springfox-swagger2-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\swagger\swagger-annotations\1.5.6\swagger-annotations-1.5.6.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\swagger\swagger-models\1.5.6\swagger-models-1.5.6.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-spi\2.4.0\springfox-spi-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-core\2.4.0\springfox-core-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-schema\2.4.0\springfox-schema-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-swagger-common\2.4.0\springfox-swagger-common-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-spring-web\2.4.0\springfox-spring-web-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\google\guava\guava\18.0\guava-18.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\classmate\1.4.0\classmate-1.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\plugin\spring-plugin-core\1.2.0.RELEASE\spring-plugin-core-1.2.0.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\plugin\spring-plugin-metadata\1.2.0.RELEASE\spring-plugin-metadata-1.2.0.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-swagger-ui\2.4.0\springfox-swagger-ui-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\xiaoymin\swagger-bootstrap-ui\1.6\swagger-bootstrap-ui-1.6.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\slf4j\slf4j-api\1.7.21\slf4j-api-1.7.21.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\slf4j\slf4j-log4j12\1.7.21\slf4j-log4j12-1.7.21.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\log4j\log4j\1.2.17\log4j-1.2.17.jar com.laity.demo2.Communication01
t1==>1
t3==>0
t4==>1
t2==>0
t1==>1
t3==>0
t4==>1
t2==>0
t1==>1
t3==>0
t4==>1
t2==>0
t1==>1
t3==>0
t4==>1
t2==>0
t1==>1
t3==>0
t4==>1
t2==>0
t1==>1
t3==>0
t4==>1
t2==>0
t1==>1
t3==>0
t4==>1
t2==>0
t1==>1
t3==>0
t4==>1
t2==>0
t1==>1
t3==>0
t4==>1
t2==>0
t1==>1
t3==>0
t4==>1
t2==>0
Process finished with exit code 0
可以看到结果正常,已解决线程虚假唤醒问题。
通过java.util.concurrent.locks 的Lock找到Condition
代码实现:
package com.laity.demo2;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.demo2.Communication02
* @Date: 2023年11月22日 21:51
* @Description: JUC版本 生产者和发布者问题
*/
public class Communication02 {
static Thread t1 = null, t2 = null, t3 = null, t4 = null;
public static void main(String[] args) {
DataSource data = new DataSource();
t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
t2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2");
t3 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t3");
t4 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
// 资源类:线程就是一个单独的资源类,没有附属的操作
class DataSource {
private int number = 0;
// 使用JUC创建锁
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
public void increment() throws InterruptedException {
lock.lock();
try {
while (number != 0) {
// 等待
notFull.await();
}
// 业务
number++;
System.out.println(Thread.currentThread().getName() + "==>" + number);
// 通知唤醒
notFull.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number == 0) {
// 等待
notFull.await();
}
// 业务
number--;
System.out.println(Thread.currentThread().getName() + "==>" + number);
// 通知唤醒
notFull.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
结果没有问题,但是Condition的优势在哪里呢?这和Synchronized的完全没有区别啊?
任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,一定有优势和补充!
代码测试:
package com.laity.demo2;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.demo2.Communication03
* @Date: 2023年11月22日 22:19
* @Description: 基于JUC的Condition实现精准的通知和唤醒线程
* 需求 t1执行完,执行t2,t2执行完,执行t3,t3执行完,执行t4,t4执行完 调用t1
*/
public class Communication03 {
static Thread t1 = null, t2 = null, t3 = null, t4 = null;
public static void main(String[] args) {
DataService data = new DataService();
t1 = new Thread(data::printA, "t1");
t2 = new Thread(data::printB, "t2");
t3 = new Thread(data::printC, "t3");
t4 = new Thread(data::printD, "t4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
// 唯一资源类
class DataService {
// 使用JUC创建锁
private final Lock lock = new ReentrantLock();
// 监视器
private final Condition condition1 = lock.newCondition();
private final Condition condition2 = lock.newCondition();
private final Condition condition3 = lock.newCondition();
private final Condition condition4 = lock.newCondition();
// 模拟一个标识位
private int number = 1; // 1-t1;2-t2;3-t3;4-t4
public void printA(){
lock.lock();
try {
// 业务代码 : while条件判断 -> 程序执行 -> 通知唤醒
while (number!=1) {
condition1.await();
}
System.out.println(Thread.currentThread().getName() + " - number == 1");
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
// 业务代码 : while条件判断 -> 程序执行 -> 通知
while (number!=2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + " - number == 2");
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
// 业务代码 : while条件判断 -> 程序执行 -> 通知
while (number!=3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName() + " - number == 3");
number = 4;
condition4.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printD(){
lock.lock();
try {
// 业务代码 : while条件判断 -> 程序执行 -> 通知
while (number!=4) {
condition4.await();
}
System.out.println(Thread.currentThread().getName() + " - number == 4");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
输出结果:
t1number == 1
t2number == 2
t3number == 3
t4number == 4
在Java中,"8锁"问题通常指的是在多线程环境下对对象的锁定问题,特别是在使用synchronized关键字时可能遇到的情况。这个问题涉及到Java中的对象锁、方法锁以及静态方法锁。
具体来说,"8锁"问题通常涉及以下几种情况:
这里的"8锁"主要来源于这四种情况的组合,即2种对象锁(普通同步方法和同步方法块)乘以2种方法锁(普通方法和静态方法)等于8种可能的组合。
下面是一个简单的例子,展示了可能的"8锁"情况:
public class SynchronizedExample {
public synchronized void method1() {
// 对象锁:当前实例对象
}
public static synchronized void method2() {
// 对象锁:当前类的Class对象
}
public void method3() {
synchronized (this) {
// 对象锁:括号里面的对象
}
}
public static void method4() {
synchronized (SynchronizedExample.class) {
// 对象锁:括号里面的对象是当前类的Class对象
}
}
}
在多线程环境中,对于这些同步方法和同步块的组合,可能会导致不同的线程竞争锁,从而产生不同的执行结果。这就是"8锁"问题的核心。解决这个问题通常需要考虑同步的粒度、锁的选择以及代码的执行顺序等因素。
下面是这8种加锁情况:
这里涉及到了两个不同的锁:对象锁和类锁。同时,锁的粒度也有两种:粗粒度锁和细粒度锁。
在多线程环境下,如果不同的线程分别使用了这些锁的组合,可能会导致不同的线程争夺锁资源,从而产生死锁、性能下降等并发问题。理解并正确使用锁是多线程编程中非常重要的一部分,避免出现不必要的并发问题。
代码:集合类不安全 - List不安全
package com.laity.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.unsafe.ListTest
* @Date: 2023年11月25日 21:20
* @Description: 集合类不安全 - List不安全
*/
public class ListTest {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
// List list = Collections.synchronizedList(arrayList);
// List list = new Vector<>();
// List list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 单线程下确实很安全,所以模拟下多下次
// arrayList.add(UUID.randomUUID().toString().substring(0, 5));
// System.out.println(arrayList);
// 多线程下:异常
/*
Exception in thread "5" Exception in thread "1" Exception in thread "7" Exception in thread "0" java.util.ConcurrentModificationException
java.util.ConcurrentModificationException 只要并发下,集合里面都会出现这个问题
ConcurrentModificationException => 并发修改异常
*/
// 并发下ArrayList是不安全的
/*
解决方案:
1、将 new ArrayList<>(); 换为 new Vector<>(); 不好
2、使用工具类 List list = Collections.synchronizedList(arrayList);
3、使用JUC中的 List list = new CopyOnWriteArrayList<>();
- private transient volatile Object[] array;
volatile:使用 volatile 修饰 instance 可以确保在多线程环境下,一个线程对 instance 的修改对其他线程是可见的。
- CopyOnWrite:写入时复制,COW 计算机程序设计领域的一种优化策略;
- list被多个线程调用的时候,读取到的是固定的,写入的时候避免其它线程将数据覆盖,造成数据问题(脏读);
- 就是一种读写分离的思想,写入的时候复制出来,写入完再插回,保证线程安全
- CopyOnWriteArrayList 比 Vector
- Vector效率低(只要使用了Synchronized就会效率低)
- CopyOnWriteArrayList使用的是Lock锁
*/
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(Thread.currentThread().getName() + list);
}, String.valueOf(i)).start();
}
}
}
Vector JDK1.0发布的,比ArrayList发布还要早,所以不推荐
CopyOnWriteArrayList 写入时复制 - 推荐
package com.laity.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.unsafe.SetTest
* @Date: 2023年11月25日 21:57
* @Description: 集合不安全 - Set集合不安全
*/
public class SetTest {
public static void main(String[] args) {
// Set set = new CopyOnWriteArraySet<>();
Set<Object> set = Collections.synchronizedSet(new HashSet<>());
// Set set = new HashSet<>();
// 同理可证:java.util.ConcurrentModificationException ==> 并发修改异常
/*
解决方案:
1、Set
for (int i = 0; i < 100; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
HashSet底层是HashMap
回顾Map
代码
package com.laity.unsafe;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.unsafe.MapTest
* @Date: 2023年11月26日 02:16
* @Description: 集合不安全 - Map集合不安全
*/
public class MapTest {
public static void main(String[] args) {
// map是这样用的吗? 不是,工作中不用 HashMap
// 默认等价于什么?
// Map map = new HashMap<>();
// 加载因子,初始化容量
// (16, 0.75f)
// Map
Map<String, String> map = new ConcurrentHashMap<>(1 << 1);
// java.util.ConcurrentModificationException - 并发修改异常
/*
ConcurrentHashMap<>();
*/
for (int i = 0; i < 100; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(1, 5));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
// HashSet底层是HashMap
public HashSet() {
map = new HashMap<>();
}
所以HashSet的本质就是一个HashMap
可以看出 值存自于HashMap中的Key map的key是一个无法重复的,Value是一个Object空对象 - 不变的值。
位运算是一种计算机中的基本运算,它针对二进制位进行操作。
Java中的位运算符包括按位与(&)、按位或(|)、按位异或(^)、取反(~)以及
左移位和右移位运算符(<<, >>, >>>)等。
按位与运算是指两个二进制数对应位上的数字都为1时,结果位上的数字才为1;否则结果位上的数字为0。按位与通常用于掩码操作或清零操作。
举例说明:
3 & 5 = 1
3 的二进制表示为 0011
5 的二进制表示为 0101
& 的运算规则:
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
因此,3 & 5 = 0011 & 0101 = 0001 = 1
按位与运算主要用于掩码操作或清零操作。通过按位与运算可以将某些二进制位设置为0,从而达到掩码操作或清零操作的目的。
例如:
以下是按位与运算的几个实例:
public class BitwiseAndExample {
public static void main(String[] args) {
int a = 63; // 二进制:0011 1111
int b = 15; // 二进制:0000 1111
int c = a & b; // 二进制:0000 1111
System.out.println("a & b = " + c); // 输出:a & b = 15
int x = 10; // 二进制:1010
if ((x & 1) == 1) {
System.out.println(x + " is odd number."); // 输出:10 is odd number.
} else {
System.out.println(x + " is even number.");
}
int num = 0b1111_0000_1111_0101;
int n = 8;
num &= ~(0xFF >>> n);
System.out.println(Integer.toBinaryString(num)); // 输出:1111
}
}
按位或运算是指两个二进制数对应位上的数字有一个为1时,结果位上的数字就为1。按位或运算通常用于设置某些二进制位为1。
举例说明:
3 | 5 = 7
3 的二进制表示为 0011
5 的二进制表示为 0101
| 的运算规则:
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
因此,3 | 5 = 0011 | 0101 = 0111 = 7
按位或运算主要用于设置某些二进制位为1。通过按位或运算可以将某些二进制位设置为1,从而达到设置操作的目的。
例如:
以下是按位或运算的几个实例:
public class BitwiseOrExample {
public static void main(String[] args) {
int a = 63; // 二进制:0011 1111
int b = 15; // 二进制:0000 1111
int c = a | b; // 二进制:0011 1111
System.out.println("a | b = " + c); // 输出:a | b = 63
int x = 10; // 二进制:1010
x |= 1; // 将x的最后一位设置为1,即变成奇数
System.out.println(x); // 输出:11
int num = 0b1111;
int n = 8;
num |= (0xFF >>> n);
System.out.println(Integer.toBinaryString(num)); // 输出:1111_1111
}
}
按位异或运算是指两个二进制数对应位上的数字不相同时,结果位上的数字为1;否则结果位上的数字为0。按位异或运算通常用于加密和解密操作。
举例说明:
3 ^ 5 = 6
3 的二进制表示为 0011
5 的二进制表示为 0101
^ 的运算规则:
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
因此,3 ^ 5 = 0011 ^ 0101 = 0110 = 6
按位异或运算主要用于加密和解密操作。通过按位异或运算可以将某些二进制位取反,达到加密或解密的目的。
例如:
以下是按位异或运算的几个实例:
public class BitwiseXorExample {
public static void main(String[] args) {
int a = 63; // 二进制:0011 1111
int b = 15; // 二进制:0000 1111
int c = a ^ b; // 二进制:0011 0000
System.out.println("a ^ b = " + c); // 输出:a ^ b = 48
String s1 = "hello";
String s2 = "world";
String encrypted = encrypt(s1, s2);
String decrypted = encrypt(encrypted, s2);
System.out.println(encrypted); // 输出:¦¥ã¤ï
System.out.println(decrypted); // 输出:hello
int x = 10; // 二进制:1010
int y = 5; // 二进制:0101
x ^= y; // x = 1010 ^ 0101 = 1111
y ^= x; // y = 0101 ^ 1111 = 1010
x ^= y; // x = 1111 ^ 1010 = 0101
System.out.println(x + " " + y); // 输出:5 10
}
private static String encrypt(String s1, String s2) {
char[] chars1 = s1.toCharArray();
char[] chars2 = s2.toCharArray();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < chars1.length; i++) {
builder.append((char) (chars1[i] ^ chars2[i % chars2.length]));
}
return builder.toString();
}
}
按位取反运算是指一个二进制数的每个位取反,即0变成1,1变成0。
举例说明:
~3 = -4
3 的二进制表示为 0011
~ 的运算规则:
~0 = -1
~1 = -2
~2 = -3
~3 = -4
因此,~3 = 1100 = -4
按位取反运算主要用于一些特殊的应用场景,如在某些加密算法中,通过连续两次按位取反操作可以恢复原值。
以下是按位取反运算的几个实例:
public class BitwiseNotExample {
public static void main(String[] args) {
int a = 63; // 二进制:0011 1111
int b = ~a; // 二进制:1100 0000
System.out.println("~a = " + b); // 输出:~a = -64
int x = 10; // 二进制:1010
int y = ~x; // 二进制:0101
System.out.println(y); // 输出:-11
int num = 10; // 二进制:1010
num = (~num) & 0xFF; // 二进制:0101
System.out.println(num); // 输出:5
}
}
运算规则:实际操作的是位,可以理解为左移几位就相当于乘以 2 的几次方
左移运算是将一个数的二进制位向左移动指定的位数,因此在左移运算中,左移操作数的值相当于乘以 22 的左移位数次幂。左移操作数的数据类型可以是 int、long 和任意基本数据类型的 char、short、byte。
举例说明:
3 << 2 = 12 // 3 * 2的2次方 3 * 4 = 12
3 的二进制表示为 0011
3 << 2 的运算过程如下:
0011 -> 1100
因此,3 << 2 = 12
左移运算主要用于实现乘法运算,通过将乘数左移运算后得到结果。
例如,对于 a * 4,可以使用 a << 2 来代替。
以下是左移运算的几个实例:
public class LeftShiftExample {
public static void main(String[] args) {
int a = 3; // 二进制:0011
int b = a << 2; // 二进制:1100
System.out.println("a << 2 = " + b); // 输出:a << 2 = 12
int c = 10; // 二进制:1010
int d = c << 1; // 二进制:10100
System.out.println("c << 1 = " + d); // 输出:c << 1 = 20
int e = 5; // 二进制:0101
int f = e << 3; // 二进制:0101000
System.out.println("e << 3 = " + f); // 输出:e << 3 = 40
}
}
运算规则:实际操作的是位,可以理解为除以 2 的 n 次幂,如果不能整除,向下取整
右移运算是将一个数的二进制位向右移动指定的位数,其特点是符号位不变,空出来的高位补上原符号位的值。右移操作数的数据类型可以是 int、long 和任意基本数据类型的 char、short、byte。
举例说明:
-6 >> 1 = -3
-6 的二进制表示为 1111 1111 1111 1111 1111 1111 1111 1010
-6 >> 1 的运算过程如下:
1111 1111 1111 1111 1111 1111 1111 1010 -> 1111 1111 1111 1111 1111 1111 1111 1101
因此,-6 >> 1 = -3
注意:对于正数而言,右移运算和无符号右移运算结果相同。
右移运算主要用于实现除法运算,通过将被除数右移运算后得到结果。
例如,对于 a / 2,可以使用 a >> 1 来代替。
以下是右移运算的几个实例:
public class RightShiftExample {
public static void main(String[] args) {
int a = -6; // 二进制:1111 1111 1111 1111 1111 1111 1111 1010
int b = a >> 1; // 二进制:1111 1111 1111 1111 1111 1111 1111 1101
System.out.println("a >> 1 = " + b); // 输出:a >> 1 = -3
int c = 10; // 二进制:1010
int d = c >> 1; // 二进制:0101
System.out.println("c >> 1 = " + d); // 输出:c >> 1 = 5
int e = 5; // 二进制:0101
int f = e >> 2; // 二进制:0001
System.out.println("e >> 2 = " + f); // 输出:e >> 2 = 1
}
}
无符号右移运算是将一个数的二进制位向右移动指定的位数,其特点是空出来的高位用0补充。右移操作数的数据类型可以是 int、long 和任意基本数据类型的 char、short、byte。
举例说明:
-6 >>> 1 = 2147483645
-6 的二进制表示为 1111 1111 1111 1111 1111 1111 1111 1010
-6 >>> 1 的运算过程如下:
1111 1111 1111 1111 1111 1111 1111 1010 -> 0111 1111 1111 1111 1111 1111 1111 1101
因此,-6 >>> 1 = 2147483645
注意:对于正数而言,无符号右移运算和右移运算结果相同。
无符号右移运算主要用于将有符号数转换为无符号数,或者将无符号数右移运算后得到结果。
以下是无符号右移运算的几个实例:
public class UnsignedRightShiftExample {
public static void main(String[] args) {
int a = -6; // 二进制:1111 1111 1111 1111 1111 1111 1111 1010
int b = a >>> 1; // 二进制:0111 1111 1111 1111 1111 1111 1111 1101
System.out.println("a >>> 1 = " + b); // 输出:a >>> 1 = 2147483645
int c = 10; // 二进制:1010
int d = c >>> 1; // 二进制:0101
System.out.println("c >>> 1 = " + d); // 输出:c >>> 1 = 5
int e = -5; // 二进制:1111 1111 1111 1111 1111 1111 1111 1011
int f = e >>> 2; // 二进制:0011 1111 1111 1111 1111 1111 1111 1110
System.out.println("e >>> 2 = " + f); // 输出:e >>> 2 = 1073741822
}
}
和Runnable是一样的,只是Runnable没有返回值,而Callable可以有返回值并且可以抛出异常,方法不同run() & call();
代码实例:
package com.laity.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.callable.CallableTest
* @Date: 2023年11月26日 03:13
* @Description: Callable使用
*/
public class CallableTest {
public static void main(String[] args) {
/*
内部类实现Callable接口
new Thread(new Runnable() {}).start();
new Thread(new FutureTask() {}).start();
new Thread(new FutureTask( Callable ) {}).start();
实现原理就是:futureTask传入callable对象,在run方法中调用callable的call方法,将结果返回给futureTask
*/
try {
// 1. 创建Callable对象
Callable1 callable1 = new Callable1();
// 2. 创建FutureTask对象 - 适配类
FutureTask<Integer> futureTask = new FutureTask<>(callable1);
// 3. 创建线程
Thread thread = new Thread(futureTask, "Callable1");
// 4. 启动线程
thread.start();
// 5. 获取结果
Integer integer = futureTask.get();
System.out.println(integer);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @author Laity
*/
class Callable1 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("Callable1" + "==>" + Thread.currentThread().getName());
return 1 << 4;
}
}
细节点:
减法计数器
代码案例:
package com.laity.add;
import java.util.concurrent.CountDownLatch;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.add.CountDownLatchDemo
* @Date: 2023年11月26日 22:49
* @Description: 计数器
*/
public class CountDownLatchDemo {
public static void main(String[] args) {
// 总数为6,必须要执行任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
countDownLatch.countDown(); // 计数器减1
System.out.println(Thread.currentThread().getName() + "走了");
}, "thread-" + i).start();
}
try {
countDownLatch.await();
System.out.println("所有线程都走了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
原理:
每次有线程调用 countDown 数量-1,假设计数器为0,countDownLatch() 就会被唤醒,继续向下执行!
加法计数器
代码实例:
package com.laity.add;
import java.util.concurrent.CyclicBarrier;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.add.CyclicBarrierDemo
* @Date: 2023年11月26日 22:58
* @Description: CyclicBarrier 加法计数器
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙");
});
/*
集齐七颗龙珠,召唤神龙
*/
for (int i = 1; i <= 7; i++) {
final int index = i;
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 收集到" + index + "颗龙珠");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-" + i).start();
}
}
}
Thread-1 收集到1颗龙珠
Thread-2 收集到2颗龙珠
Thread-3 收集到3颗龙珠
Thread-4 收集到4颗龙珠
Thread-7 收集到7颗龙珠
Thread-6 收集到6颗龙珠
Thread-5 收集到5颗龙珠
召唤神龙
一个计数器的信号量,相当于通行证
package com.laity.add;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.add.SemaphoreDemo
* @Date: 2023年11月26日 23:08
* @Description: Semaphore 信号量
*/
public class SemaphoreDemo {
public static void main(String[] args) {
// 线程池大小 数量 - 一次性只能进入3个线程
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
// semaphore.acquire(); 获取信号量
// semaphore.release(); 释放信号量
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 准备");
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 进入");
TimeUnit.SECONDS.sleep(1);
// Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 离开");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
原理:
// semaphore.acquire(); 获取信号量,如果已经满了,等待释放
// semaphore.release(); 释放信号量,会将当前信号量释放,然后唤醒等待的线程。
作用:多个共享资源互斥时使用!并发限流,控制最大的线程数!
package com.laity.readWriteLock;
import java.util.HashMap;
import java.util.Map;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.readWriteLock.ReadWriteLockDemo
* @Date: 2023年11月26日 23:28
* @Description: ReadWriteLock 读写锁
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
Cache cache = new Cache();
for (int i = 1; i < 5; i++) {
final int index = i;
new Thread(() -> {
System.out.println(cache.get("key"));
}, "读线程" + i).start();
new Thread(() -> {
cache.put("key", "value" + index);
}, "写线程" + i).start();
}
}
}
// 自定义未加锁的一个缓存
class Cache {
private final Map<String, Object> cache = new HashMap<>();
// 读方法
public Object get(String key) {
System.out.println(Thread.currentThread().getName() + " 正在读取数据");
Object o = cache.get(key);
System.out.println(Thread.currentThread().getName() + " 读取数据完毕");
return o;
}
// 写方法
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + " 正在写入数据");
cache.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写入数据完毕");
}
}
未加锁结果
D:\Java\JDK\bin\java.exe "-javaagent:E:\IdeaInstall\IntelliJ IDEA 2020.3.4\lib\idea_rt.jar=54249:E:\IdeaInstall\IntelliJ IDEA 2020.3.4\bin" -Dfile.encoding=UTF-8 -classpath D:\java\JDK\jre\lib\charsets.jar;D:\java\JDK\jre\lib\deploy.jar;D:\java\JDK\jre\lib\ext\access-bridge-64.jar;D:\java\JDK\jre\lib\ext\cldrdata.jar;D:\java\JDK\jre\lib\ext\dnsns.jar;D:\java\JDK\jre\lib\ext\jaccess.jar;D:\java\JDK\jre\lib\ext\jfxrt.jar;D:\java\JDK\jre\lib\ext\localedata.jar;D:\java\JDK\jre\lib\ext\mysql-connector-java-5.1.48.jar;D:\java\JDK\jre\lib\ext\nashorn.jar;D:\java\JDK\jre\lib\ext\sunec.jar;D:\java\JDK\jre\lib\ext\sunjce_provider.jar;D:\java\JDK\jre\lib\ext\sunmscapi.jar;D:\java\JDK\jre\lib\ext\sunpkcs11.jar;D:\java\JDK\jre\lib\ext\zipfs.jar;D:\java\JDK\jre\lib\javaws.jar;D:\java\JDK\jre\lib\jce.jar;D:\java\JDK\jre\lib\jfr.jar;D:\java\JDK\jre\lib\jfxswt.jar;D:\java\JDK\jre\lib\jsse.jar;D:\java\JDK\jre\lib\management-agent.jar;D:\java\JDK\jre\lib\plugin.jar;D:\java\JDK\jre\lib\resources.jar;D:\java\JDK\jre\lib\rt.jar;D:\LaityWork\architecture\foodie-dev\target\classes;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter\2.1.5.RELEASE\spring-boot-starter-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot\2.1.5.RELEASE\spring-boot-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-context\5.1.7.RELEASE\spring-context-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-autoconfigure\2.1.5.RELEASE\spring-boot-autoconfigure-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-logging\2.1.5.RELEASE\spring-boot-starter-logging-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\logging\log4j\log4j-to-slf4j\2.11.2\log4j-to-slf4j-2.11.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\slf4j\jul-to-slf4j\1.7.26\jul-to-slf4j-1.7.26.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-core\5.1.7.RELEASE\spring-core-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-jcl\5.1.7.RELEASE\spring-jcl-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\yaml\snakeyaml\1.23\snakeyaml-1.23.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-web\2.1.5.RELEASE\spring-boot-starter-web-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-json\2.1.5.RELEASE\spring-boot-starter-json-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\core\jackson-databind\2.9.8\jackson-databind-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\core\jackson-core\2.9.8\jackson-core-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.9.8\jackson-datatype-jdk8-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.9.8\jackson-datatype-jsr310-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\module\jackson-module-parameter-names\2.9.8\jackson-module-parameter-names-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-tomcat\2.1.5.RELEASE\spring-boot-starter-tomcat-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\tomcat\embed\tomcat-embed-core\9.0.19\tomcat-embed-core-9.0.19.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\tomcat\embed\tomcat-embed-el\9.0.19\tomcat-embed-el-9.0.19.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.19\tomcat-embed-websocket-9.0.19.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\hibernate\validator\hibernate-validator\6.0.16.Final\hibernate-validator-6.0.16.Final.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\jboss\logging\jboss-logging\3.3.2.Final\jboss-logging-3.3.2.Final.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-web\5.1.7.RELEASE\spring-web-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-beans\5.1.7.RELEASE\spring-beans-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-webmvc\5.1.7.RELEASE\spring-webmvc-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-expression\5.1.7.RELEASE\spring-expression-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-aop\2.1.5.RELEASE\spring-boot-starter-aop-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-aop\5.1.7.RELEASE\spring-aop-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\aspectj\aspectjweaver\1.9.4\aspectjweaver-1.9.4.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-configuration-processor\2.1.5.RELEASE\spring-boot-configuration-processor-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\projectlombok\lombok\1.18.10\lombok-1.18.10.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\mysql\mysql-connector-java\5.1.47\mysql-connector-java-5.1.47.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\spring\boot\mybatis-spring-boot-starter\2.1.0\mybatis-spring-boot-starter-2.1.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-jdbc\2.1.5.RELEASE\spring-boot-starter-jdbc-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\zaxxer\HikariCP\3.2.0\HikariCP-3.2.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-jdbc\5.1.7.RELEASE\spring-jdbc-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-tx\5.1.7.RELEASE\spring-tx-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\spring\boot\mybatis-spring-boot-autoconfigure\2.1.0\mybatis-spring-boot-autoconfigure-2.1.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\mybatis\3.5.2\mybatis-3.5.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\mybatis-spring\2.0.2\mybatis-spring-2.0.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-spring-boot-starter\2.1.5\mapper-spring-boot-starter-2.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-core\1.1.5\mapper-core-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\javax\persistence\persistence-api\1.0\persistence-api-1.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-base\1.1.5\mapper-base-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-weekend\1.1.5\mapper-weekend-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-spring\1.1.5\mapper-spring-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-extra\1.1.5\mapper-extra-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-spring-boot-autoconfigure\2.1.5\mapper-spring-boot-autoconfigure-2.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\pagehelper\pagehelper-spring-boot-starter\1.2.12\pagehelper-spring-boot-starter-1.2.12.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\pagehelper\pagehelper-spring-boot-autoconfigure\1.2.12\pagehelper-spring-boot-autoconfigure-1.2.12.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\pagehelper\pagehelper\5.1.10\pagehelper-5.1.10.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\jsqlparser\jsqlparser\2.0\jsqlparser-2.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\commons-codec\commons-codec\1.11\commons-codec-1.11.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\commons\commons-lang3\3.4\commons-lang3-3.4.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\commons-io\commons-io\2.4\commons-io-2.4.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-swagger2\2.4.0\springfox-swagger2-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\swagger\swagger-annotations\1.5.6\swagger-annotations-1.5.6.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\swagger\swagger-models\1.5.6\swagger-models-1.5.6.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-spi\2.4.0\springfox-spi-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-core\2.4.0\springfox-core-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-schema\2.4.0\springfox-schema-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-swagger-common\2.4.0\springfox-swagger-common-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-spring-web\2.4.0\springfox-spring-web-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\google\guava\guava\18.0\guava-18.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\classmate\1.4.0\classmate-1.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\plugin\spring-plugin-core\1.2.0.RELEASE\spring-plugin-core-1.2.0.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\plugin\spring-plugin-metadata\1.2.0.RELEASE\spring-plugin-metadata-1.2.0.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-swagger-ui\2.4.0\springfox-swagger-ui-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\xiaoymin\swagger-bootstrap-ui\1.6\swagger-bootstrap-ui-1.6.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\slf4j\slf4j-api\1.7.21\slf4j-api-1.7.21.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\slf4j\slf4j-log4j12\1.7.21\slf4j-log4j12-1.7.21.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\log4j\log4j\1.2.17\log4j-1.2.17.jar com.laity.readWriteLock.ReadWriteLockDemo
写线程1 正在写入数据
写线程1 写入数据完毕
读线程1 正在读取数据
写线程2 正在写入数据 -- 这里2开始写入
读线程2 正在读取数据
读线程2 读取数据完毕
读线程1 读取数据完毕
value1
读线程3 正在读取数据
读线程3 读取数据完毕
value2
写线程3 正在写入数据 -- 在2还没有写完,3开始写了
value2
写线程2 写入数据完毕 -- 这里2写入完毕
写线程3 写入数据完毕
读线程4 正在读取数据
写线程4 正在写入数据
读线程4 读取数据完毕
value3
写线程4 写入数据完毕
Process finished with exit code 0
package com.laity.readWriteLock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.readWriteLock.ReadWriteLockDemo
* @Date: 2023年11月26日 23:28
* @Description: ReadWriteLock 读写锁
*/
/*
读写锁的特点:
读 - 读 可以共存并发
写 - 写 不能共存并发
读 - 写 不能共存并发
独占锁:一次只能被一个线程占用
共享锁:同时可以被多个线程占用
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
// Cache cache = new Cache();
CacheLock cache = new CacheLock();
for (int i = 1; i < 5; i++) {
final int index = i;
new Thread(() -> {
cache.put("key", "value" + index);
}, "写线程" + i).start();
new Thread(() -> {
System.out.println(cache.get("key"));
}, "读线程" + i).start();
}
}
}
// 自定义未加锁的一个缓存
class Cache {
private final Map<String, Object> cache = new HashMap<>();
// 读方法
public Object get(String key) {
System.out.println(Thread.currentThread().getName() + " 正在读取数据");
Object o = cache.get(key);
System.out.println(Thread.currentThread().getName() + " 读取数据完毕");
return o;
}
// 写方法
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + " 正在写入数据");
cache.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写入数据完毕");
}
}
class CacheLock {
private final Map<String, Object> cache = new HashMap<>();
// 读写锁 读写锁可以保证读写操作的顺序 更加细粒度控制
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 这是一个正常的可重入锁 - 普通锁
// private final Lock lock = new ReentrantLock();
// 读方法,读取的时候,可以允许多个线程同时读取
public Object get(String key) {
readWriteLock.readLock().lock();
Object o = null;
try {
System.out.println(Thread.currentThread().getName() + " 正在读取数据");
o = cache.get(key);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
System.out.println(Thread.currentThread().getName() + " 读取数据完毕");
return o;
}
// 写方法,写入的时候,只希望同时只有一个线程可以写入
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 正在写入数据");
cache.put(key, value);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
System.out.println(Thread.currentThread().getName() + " 写入数据完毕");
}
}
加锁后结果:
D:\Java\JDK\bin\java.exe "-javaagent:E:\IdeaInstall\IntelliJ IDEA 2020.3.4\lib\idea_rt.jar=55807:E:\IdeaInstall\IntelliJ IDEA 2020.3.4\bin" -Dfile.encoding=UTF-8 -classpath D:\java\JDK\jre\lib\charsets.jar;D:\java\JDK\jre\lib\deploy.jar;D:\java\JDK\jre\lib\ext\access-bridge-64.jar;D:\java\JDK\jre\lib\ext\cldrdata.jar;D:\java\JDK\jre\lib\ext\dnsns.jar;D:\java\JDK\jre\lib\ext\jaccess.jar;D:\java\JDK\jre\lib\ext\jfxrt.jar;D:\java\JDK\jre\lib\ext\localedata.jar;D:\java\JDK\jre\lib\ext\mysql-connector-java-5.1.48.jar;D:\java\JDK\jre\lib\ext\nashorn.jar;D:\java\JDK\jre\lib\ext\sunec.jar;D:\java\JDK\jre\lib\ext\sunjce_provider.jar;D:\java\JDK\jre\lib\ext\sunmscapi.jar;D:\java\JDK\jre\lib\ext\sunpkcs11.jar;D:\java\JDK\jre\lib\ext\zipfs.jar;D:\java\JDK\jre\lib\javaws.jar;D:\java\JDK\jre\lib\jce.jar;D:\java\JDK\jre\lib\jfr.jar;D:\java\JDK\jre\lib\jfxswt.jar;D:\java\JDK\jre\lib\jsse.jar;D:\java\JDK\jre\lib\management-agent.jar;D:\java\JDK\jre\lib\plugin.jar;D:\java\JDK\jre\lib\resources.jar;D:\java\JDK\jre\lib\rt.jar;D:\LaityWork\architecture\foodie-dev\target\classes;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter\2.1.5.RELEASE\spring-boot-starter-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot\2.1.5.RELEASE\spring-boot-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-context\5.1.7.RELEASE\spring-context-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-autoconfigure\2.1.5.RELEASE\spring-boot-autoconfigure-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-logging\2.1.5.RELEASE\spring-boot-starter-logging-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\logging\log4j\log4j-to-slf4j\2.11.2\log4j-to-slf4j-2.11.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\slf4j\jul-to-slf4j\1.7.26\jul-to-slf4j-1.7.26.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-core\5.1.7.RELEASE\spring-core-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-jcl\5.1.7.RELEASE\spring-jcl-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\yaml\snakeyaml\1.23\snakeyaml-1.23.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-web\2.1.5.RELEASE\spring-boot-starter-web-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-json\2.1.5.RELEASE\spring-boot-starter-json-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\core\jackson-databind\2.9.8\jackson-databind-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\core\jackson-core\2.9.8\jackson-core-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.9.8\jackson-datatype-jdk8-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.9.8\jackson-datatype-jsr310-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\module\jackson-module-parameter-names\2.9.8\jackson-module-parameter-names-2.9.8.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-tomcat\2.1.5.RELEASE\spring-boot-starter-tomcat-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\tomcat\embed\tomcat-embed-core\9.0.19\tomcat-embed-core-9.0.19.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\tomcat\embed\tomcat-embed-el\9.0.19\tomcat-embed-el-9.0.19.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.19\tomcat-embed-websocket-9.0.19.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\hibernate\validator\hibernate-validator\6.0.16.Final\hibernate-validator-6.0.16.Final.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\jboss\logging\jboss-logging\3.3.2.Final\jboss-logging-3.3.2.Final.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-web\5.1.7.RELEASE\spring-web-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-beans\5.1.7.RELEASE\spring-beans-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-webmvc\5.1.7.RELEASE\spring-webmvc-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-expression\5.1.7.RELEASE\spring-expression-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-aop\2.1.5.RELEASE\spring-boot-starter-aop-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-aop\5.1.7.RELEASE\spring-aop-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\aspectj\aspectjweaver\1.9.4\aspectjweaver-1.9.4.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-configuration-processor\2.1.5.RELEASE\spring-boot-configuration-processor-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\projectlombok\lombok\1.18.10\lombok-1.18.10.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\mysql\mysql-connector-java\5.1.47\mysql-connector-java-5.1.47.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\spring\boot\mybatis-spring-boot-starter\2.1.0\mybatis-spring-boot-starter-2.1.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\boot\spring-boot-starter-jdbc\2.1.5.RELEASE\spring-boot-starter-jdbc-2.1.5.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\zaxxer\HikariCP\3.2.0\HikariCP-3.2.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-jdbc\5.1.7.RELEASE\spring-jdbc-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\spring-tx\5.1.7.RELEASE\spring-tx-5.1.7.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\spring\boot\mybatis-spring-boot-autoconfigure\2.1.0\mybatis-spring-boot-autoconfigure-2.1.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\mybatis\3.5.2\mybatis-3.5.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\mybatis\mybatis-spring\2.0.2\mybatis-spring-2.0.2.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-spring-boot-starter\2.1.5\mapper-spring-boot-starter-2.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-core\1.1.5\mapper-core-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\javax\persistence\persistence-api\1.0\persistence-api-1.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-base\1.1.5\mapper-base-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-weekend\1.1.5\mapper-weekend-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-spring\1.1.5\mapper-spring-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-extra\1.1.5\mapper-extra-1.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\tk\mybatis\mapper-spring-boot-autoconfigure\2.1.5\mapper-spring-boot-autoconfigure-2.1.5.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\pagehelper\pagehelper-spring-boot-starter\1.2.12\pagehelper-spring-boot-starter-1.2.12.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\pagehelper\pagehelper-spring-boot-autoconfigure\1.2.12\pagehelper-spring-boot-autoconfigure-1.2.12.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\pagehelper\pagehelper\5.1.10\pagehelper-5.1.10.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\jsqlparser\jsqlparser\2.0\jsqlparser-2.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\commons-codec\commons-codec\1.11\commons-codec-1.11.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\apache\commons\commons-lang3\3.4\commons-lang3-3.4.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\commons-io\commons-io\2.4\commons-io-2.4.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-swagger2\2.4.0\springfox-swagger2-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\swagger\swagger-annotations\1.5.6\swagger-annotations-1.5.6.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\swagger\swagger-models\1.5.6\swagger-models-1.5.6.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-spi\2.4.0\springfox-spi-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-core\2.4.0\springfox-core-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-schema\2.4.0\springfox-schema-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-swagger-common\2.4.0\springfox-swagger-common-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-spring-web\2.4.0\springfox-spring-web-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\google\guava\guava\18.0\guava-18.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\fasterxml\classmate\1.4.0\classmate-1.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\plugin\spring-plugin-core\1.2.0.RELEASE\spring-plugin-core-1.2.0.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\springframework\plugin\spring-plugin-metadata\1.2.0.RELEASE\spring-plugin-metadata-1.2.0.RELEASE.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\io\springfox\springfox-swagger-ui\2.4.0\springfox-swagger-ui-2.4.0.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\com\github\xiaoymin\swagger-bootstrap-ui\1.6\swagger-bootstrap-ui-1.6.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\slf4j\slf4j-api\1.7.21\slf4j-api-1.7.21.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\org\slf4j\slf4j-log4j12\1.7.21\slf4j-log4j12-1.7.21.jar;D:\java\Maven\apache-maven-3.6.3\mvn_resp\log4j\log4j\1.2.17\log4j-1.2.17.jar com.laity.readWriteLock.ReadWriteLockDemo
写线程1 正在写入数据
写线程1 写入数据完毕
读线程1 正在读取数据
读线程1 读取数据完毕
value1
读线程2 正在读取数据
读线程2 读取数据完毕
value1
写线程2 正在写入数据
写线程2 写入数据完毕
写线程4 正在写入数据
写线程4 写入数据完毕
写线程3 正在写入数据
写线程3 写入数据完毕
读线程4 正在读取数据
读线程3 正在读取数据
读线程4 读取数据完毕
value3
读线程3 读取数据完毕
value3
Process finished with exit code 0
读写锁能解决哪些问题?
读写锁(ReadWriteLock)是一种并发编程中的锁机制,它能够解决一些特定类型的问题,提高多线程环境下的性能。读写锁允许多个线程同时读取共享资源,但只有一个线程能够写入共享资源。这种机制在读操作远远多于写操作的情况下,可以提供比独占锁更好的性能。
以下是读写锁能够解决的问题:
需要注意的是,读写锁并不适用于所有场景。它的优势主要体现在读操作远多于写操作的情况下。如果写操作非常频繁,读写锁的性能提升可能会受到限制。在某些情况下,精细的锁设计或其他并发数据结构可能更适合解决特定的并发问题。
但是写操作也只是一瞬间的操作啊,第二个人如果晚几秒执行接口的调用,那不是还是会覆盖吗?
读写锁并不能解决写操作本身的并发问题。它主要是为了解决读-写冲突,允许多个线程同时读取数据,但在写操作时保持独占。如果写操作非常短暂,而且多个写操作在非常短的时间内发生,那么仍然有可能发生竞争条件,导致其中一个写操作的修改被覆盖。
对于写操作短暂的情况,可以考虑使用更细粒度的锁或其他并发控制机制,以确保在写操作间的竞争条件下不会发生数据覆盖。例如,可以使用独占锁(ReentrantLock)或者一些特定的数据结构,如AtomicReference等,来保证写操作的原子性。
总体而言,读写锁在处理读-写冲突时是有帮助的,但对于写操作本身的并发问题,可能需要考虑其他机制来确保数据的一致性。
所以我的理解是:读写锁是解决同一时间同一秒的并发,而不能保障下一秒数据的覆盖
读写锁的主要优势在于对于长时间的读操作是非常有帮助的,因为它允许多个线程同时获取读锁,从而提高了并发性。但是对于短暂的写操作,即使使用读写锁,也不能完全消除写操作的并发问题。如果多个写操作在极短的时间内发生,仍然有可能导致其中一个写操作的修改被另一个写操作覆盖,这被称为"写丢失"。
读写锁是为了解决读-写冲突而设计的,而不是为了解决写操作本身的并发问题。如果对写入的一致性要求很高,可能需要考虑其他机制,比如更细粒度的锁(比如独占锁),或者使用一些并发安全的数据结构(比如AtomicReference)。
总体来说,读写锁适用于读操作远多于写操作的场景,可以提高并发性。但在写操作频繁、且写操作间隔非常短的情况下,还需要额外的考虑和措施以确保数据的一致性。
其他线程在写操作期间无法获得读锁,因此在写操作时,读操作会被阻塞。读写锁是互斥的。
通过这种方式,读写锁在一定程度上平衡了读写操作的并发性能和数据一致性。在读操作远多于写操作的场景中,读写锁能够提供更好的并发性能。
阻塞
队列
// BlockingQueue并列 List和Set Queue:阻塞队列(BlockingQueue)、非阻塞队列(AbstractQueue)、双端队列(Deque)
// BlockingQueue是一种先进先出(FIFO)的队列,队列满了之后,再往队列中添加元素,会被阻塞,直到队列有空余位置。
// BlockingQueue的特点:
// 1. 队列满了之后,再往队列中添加元素,会被阻塞,直到队列有空余位置。
// 2. 队列空了之后,再往队列中取元素,会被阻塞,直到队列有元素。
// 3. BlockingQueue提供了四个方法:
// put(E e):向队列中添加元素,如果队列满了,则调用此方法会被阻塞,直到队列有空余位置。
// take():从队列中取出一个元素,如果队列空了,则调用此方法会被阻塞,直到队列有元素。
// poll():从队列中取出一个元素,如果队列空了,则返回null。
// peek():从队列中取出一个元素,如果队列空了,则返回null。
// 4. BlockingQueue下有两个实现类:
// ArrayBlockingQueue:基于数组实现的有界阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常要高于LinkedList。
// LinkedBlockingQueue:基于链表实现的阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常要高于LinkedList。
// 5. BlockingQueue的应用场景:
// 生产者消费者模型:生产者线程将产品放入队列,消费者线程从队列中取出产品进行处理。
// 缓冲区:生产者线程将产品放入队列,消费者线程从队列中取出产品进行处理。
// 什么时候我们需要使用BlockingQueue呢?
// 在多线程的时候,A去调用B,B需要调用A,但是A和B是并行的,如果A调用B的时候,B还没有准备好,那么A就会一直等待B准备好,这就是阻塞。
四组API
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞 等待 | 超时等待 |
---|---|---|---|---|
添加 |
add | offer | put | offer(有参) |
移除 |
remove | poll(空参) | take | poll(有参) |
判断队列首 |
element | peek | - | - |
/*
抛出异常
*/
public static void test1() {
// capacity 指定了队列的大小,如果队列满了,再往队列中添加元素,会被阻塞,直到队列有空余位置。
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(2);
System.out.println(blockingQueue.add("a")); // true
System.out.println(blockingQueue.add("b")); // true
// System.out.println(blockingQueue.add("c")); // Queue full:报错信息中已经提示了"Queue full",说明队列已满,无法再添加元素。
System.out.println("=========================================");
System.out.println(blockingQueue.element()); // 查看队列中第一个元素,如果队列为空,则调用此方法会被阻塞,直到队列有元素。
System.out.println("=========================================");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove()); // java.util.NoSuchElementException 这个报错信息表示在移除一个元素时,队列中没有元素可供移除。
}
/*
不抛出异常,有返回值
*/
public static void test2() {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(2);
System.out.println(blockingQueue.offer("a")); // true
System.out.println(blockingQueue.offer("b")); // true
System.out.println(blockingQueue.offer("c")); // false
System.out.println("=========================================");
System.out.println(blockingQueue.peek()); // 查看队列中第一个元素,如果队列为空,则返回null。
System.out.println("=========================================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll()); // null
}
/*
等待、阻塞 (一直阻塞、超时阻塞)
*/
public static void test3() {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(2);
try {
// put方法无返回值
blockingQueue.put("a");
blockingQueue.put("b");
// blockingQueue.put("c"); // 队列已满,一直阻塞
System.out.println("===========================");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take()); // 队列为空,一直阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
超时等待
*/
public static void test4() {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(2);
try {
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c", 2, TimeUnit.SECONDS);
System.out.println(blockingQueue.poll(1, TimeUnit.SECONDS)); // 等待1秒,如果队列中有元素,则返回队列中的第一个元素,如果1秒内没有元素,则返回null。
System.out.println(blockingQueue.poll(1, TimeUnit.SECONDS)); // 等待1秒,如果队列中有元素,则返回队列中的第一个元素,如果1秒内没有元素,则返回null。
System.out.println(blockingQueue.poll(1, TimeUnit.SECONDS)); // 等待1秒,如果队列中有元素,则返回队列中的第一个元素,如果1秒内没有元素,则返回null。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
同步队列
SynchronousQueue是BlockingQueue实现类的一种
阻塞队列是没有容量的,必须等待取出之后,才能再往里面放一个元素:可以理解为容量为1。
package com.laity.bQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.bQueue.SynchronousQueueDemo
* @Date: 2023年11月29日 00:09
* @Description: SynchronousQueue 同步队列
* 和其他的阻塞队列不同的是,SynchronousQueue 是一个没有缓冲区的队列,
* put()方法会一直阻塞,直到有消费者消费(take)了这个值。
*/
public class SynchronousQueueDemo {
// SynchronousQueue 同步队列
public static void main(String[] args) {
test1();
}
public static void test1() {
// 同步队列
BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
// 生产者
new Thread(() -> {
try {
// 存值
System.out.println(Thread.currentThread().getName() + " 生产PUT 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + " 生产PUT 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + " 生产PUT 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者").start();
new Thread(() -> {
try {
// 取值
TimeUnit.SECONDS.sleep(2);
System.out.println(synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者").start();
}
}
Java多线程&&线程池
线程池必会:三大方法、七大参数、四种拒绝策略
package com.laity.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.pool.Demo01
* @Date: 2023年12月01日 10:54
* @Description: 线程池
* 使用了线程池之后,使用线程池来创建线程,可以避免频繁创建和销毁线程,提高性能
*/
public class Demo01 {
public static void main(String[] args) {
// Executors 类提供了四种线程池 - 就是一个线程池工具类 三大方法
// 1. newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程
// 2. newCachedThreadPool() 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
// 3. newSingleThreadExecutor() 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
// 4. newScheduledThreadPool(int corePoolSize) 创建一个定长线程池,支持定时及周期性任务执行
// 5. newWorkStealingPool(int parallelism) 创建一个支持并行执行任务的线程池,可控制线程最大并行数,超出的线程会被阻塞,直到有可用线程为止
ExecutorService executorService = Executors.newSingleThreadExecutor();// 它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
ExecutorService threadPool = Executors.newFixedThreadPool(10);// 创建固定大小的线程池
ExecutorService service = Executors.newCachedThreadPool();// 可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程(可伸缩)
try {
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池的关闭
executorService.shutdown();
threadPool.shutdown();
service.shutdown();
}
}
}
源码记录
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// Integer.MAX_VALUE是16进制的数(0x7fffffff;) 转换为10进制就是 2147483647
// 因为最大线程数过大,可能会导致请求堆积而造成OOM:java.lang.OutOfMemoryError
// java.lang.OutOfMemoryError: Java heap space
// Java 堆内存溢出, 此种情况最常见, 一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露, 需要通过内存监控软件查找程序中的泄露代码, 而堆大小可以通过虚拟机参数 - Xms,-Xmx 等修改。
// java.lang.OutOfMemoryError: PermGen space
// Java 永久代溢出, 即方法区溢出了, 一般出现于大量 Class 或者 jsp 页面, 或者采用 cglib 等反射机制的情况, 因为上述情况会产生大量的 Class 信息存储于方法区。当出现此种情况时可以通过更改方法区的大小来解决, 使用类似 - XX:PermSize=64m -XX:MaxPermSize=256m 的形式修改。注意, 过多的常量尤其是字符串也会导致方法区溢出。
// java.lang.StackOverflowError
// 不会抛 OOM error, 但也是比较常见的 Java 内存溢出。JAVA 虚拟机栈溢出, 一般是由于程序中存在死循环或者深度递归调用造成的, 栈大小设置太小也会出现此种溢出。可以通过虚拟机参数 - Xss 来设置栈的大小。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
package com.laity.gulimall.search.thread;
import java.util.concurrent.*;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.gulimall.search.thread.ThreadTest
* @Date: 2022年10月20日 18:13
* @Description: 线程回顾
*/
public class ThreadTest {
// 给线程池直接提交任务 - 保证当前系统中只有一两个,每个异步任务交给线程池
public static ExecutorService service = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 1、继承Thread
* 2、实现Runnable接口
* 3、实现Callable接口 + FutureTask(可以拿到返回值,可以处理异常) jdk1.5
* 4、线程池
* - 为什么用线程池 1、2、3,太浪费资源,尤其是高并发系统;
* - 将所有的多线程异步任务都交给线程池执行。
* - 降低资源的消耗
* - 提高响应速度
* - 提高线程的可管理性
* 1、使用:
* 1)、Executors 工具类
* public static ExecutorService service = Executors.newFixedThreadPool(10);
* service.execute(new Runnable01());
* 2)、new ThreadPoolExecutor(); 原生
* 区别:
* 1、2没有返回值
* 1、2、3不能控制资源;4可以,性能稳定。
*/
Thread01 thread01 = new Thread01();
thread01.start(); // 启动线程
Runnable01 runnable01 = new Runnable01();
new Thread(runnable01).start();
FutureTask<Integer> futureTask = new FutureTask<>(new Callable01());
new Thread(futureTask).start();
// 等待整个线程执行完成,获取的返回结果
Integer integer = futureTask.get();
System.out.println("Callable返回结果" + integer);
// 给线程池直接提交任务 - 保证当前系统中只有一两个,每个异步任务交给线程池
// service.submit()
// service.execute();
service.execute(new Runnable01());
// 原生创建线程池
/**
* 七大参数
* int corePoolSize, 核心线程数量;线程池,创建好以后就准备就绪的线程数量,就等待来接收异步任务来执行
* int maximumPoolSize, 最大线程数量;控制资源
* long keepAliveTime, 存活时间;如果当前正在运行的线程数量大于核心线程数量。
* 释放空闲的线程资源(maximumPoolSize-corePoolSize),只要线程空闲大于指定的keepAliveTime;
* TimeUnit unit, 时间单位
* BlockingQueue workQueue, 阻塞队列;如果任务有很多, 大于maximumPoolSize的任务就会放到队列里面。
* 只要有线程空闲,就会去队列里面抽取出新的任务继续执行。
* new LinkedBlockingQueue<>(); 默认是Integer的最大值
* ThreadFactory threadFactory, 线程的创建工厂。
* RejectedExecutionHandler handler 如果队列满了,按照我们指定的拒绝策略拒绝执行任务。
*
* 工作顺序:
* 运行流程:
* 1、线程池创建,准备好 core 数量的核心线程,准备接受任务
* 2、新的任务进来,用 core 准备好的空闲线程执行。
* (1) 、core 满了,就将再进来的任务放入阻塞队列中。空闲的 core 就会自己去阻塞队列获取任务执行
* (2) 、阻塞队列满了,就直接开新线程执行,最大只能开到 max 指定的数量
* (3) 、max 都执行好了。Max-core 数量空闲的线程会在 keepAliveTime 指定的时间后自动销毁。最终保持到 core 大小
* (4) 、如果线程数开到了 max 的数量,还有新任务进来,就会使用 reject 指定的拒绝策略进行处理
* 3、所有的线程创建都是由指定的 factory 创建的。
*/
// Executors.defaultThreadFactory() 默认的线程工厂
// new ThreadPoolExecutor.AbortPolicy() 丢弃拒绝策略 可自行查看源码,根据业务需求自行选用。
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 200,
10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10000),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
executor.execute(new Runnable01());
// Executors工具类可以帮我们创建的几种常见线程池
// Executors.newCachedThreadPool() // 核心线程为0,所有线程都可回收;创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
// Executors.newFixedThreadPool() // 固定大小,core=max,都不可回收;创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
// Executors.newScheduledThreadPool() // 定时任务线程池;创建一个定长线程池,支持定时及周期性任务执行。
// Executors.newSingleThreadExecutor() // 单线程的线程池,后台从队列中获取任务顺序逐一执行;创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
}
public static class Thread01 extends Thread {
@Override
public void run() {
System.out.println("Thread当前线程" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println(i);
}
}
public static class Runnable01 implements Runnable {
@Override
public void run() {
System.out.println("Runnable当前线程" + Thread.currentThread().getId());
int i = 12 / 2;
System.out.println(i);
}
}
public static class Callable01 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("Callable当前线程" + Thread.currentThread().getId());
return 14 / 2;
}
}
}
public ThreadPoolExecutor(int corePoolSize, // 核心线程数大小
int maximumPoolSize, // 最大线程数大小
long keepAliveTime, // 线程存活时间(超时了没有人去调用就会释放)
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂(创建线程使用),一般不用动
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
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() 直接运行策略,不做任何处理,直接在调用者线程中运行 main线程。
new ThreadPoolExecutor.DiscardPolicy() 丢弃策略,不做任何处理,直接丢弃任务。
new ThreadPoolExecutor.DiscardOldestPolicy() 丢弃最旧的任务,即队列中最旧的任务被丢弃,然后重新执行最新的任务。
// 使用线程池的最大线程数量为 = 最大线程数 + 阻塞queue的数量
// 当达到最大线程数量还有人访问就会执行拒绝策略 抛出异常
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
// Integer.MAX_VALUE是16进制的数(0x7fffffff;) 转换为10进制就是 2147483647
// 因为最大线程数过大,可能会导致请求堆积而造成OOM:java.lang.OutOfMemoryError
// java.lang.OutOfMemoryError: Java heap space
// Java 堆内存溢出, 此种情况最常见, 一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露, 需要通过内存监控软件查找程序中的泄露代码, 而堆大小可以通过虚拟机参数 - Xms,-Xmx 等修改。
// java.lang.OutOfMemoryError: PermGen space
// Java 永久代溢出, 即方法区溢出了, 一般出现于大量 Class 或者 jsp 页面, 或者采用 cglib 等反射机制的情况, 因为上述情况会产生大量的 Class 信息存储于方法区。当出现此种情况时可以通过更改方法区的大小来解决, 使用类似 - XX:PermSize=64m -XX:MaxPermSize=256m 的形式修改。注意, 过多的常量尤其是字符串也会导致方法区溢出。
// java.lang.StackOverflowError
// 不会抛 OOM error, 但也是比较常见的 Java 内存溢出。JAVA 虚拟机栈溢出, 一般是由于程序中存在死循环或者深度递归调用造成的, 栈大小设置太小也会出现此种溢出。可以通过虚拟机参数 - Xss 来设置栈的大小。
最大线程 maximumPoolSize
到底如何去定义?根据什么去定义?
可以通过任务管理器来查看自己本机的电脑核数(虚拟处理器):几核数就将线程数量定义为几,可以保证CPU的效率最高
// CPU密集型任务: 几核数就将线程数量定义为几,可以保证CPU的效率最高
public static void main(String[] args) {
// 获取CPU的核心数 - 放入线程池中
int i = Runtime.getRuntime().availableProcessors();
System.out.println(i);
}
IO密集型:判断你程序中十分消耗资源(IO)的线程
函数式接口
Lambda表达式
链式编程
stream流式计算
函数式接口:只有一个方法的接口
可以简化编程模型,在新版本的框架中底层大量使用
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
package com.laity.function;
import java.util.function.Function;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.function.Demo01
* @Date: 2023年12月01日 15:16
* @Description: Function 函数型接口:接收一个参数,返回一个结果
* Function 泛型:T 表示输入的参数类型,R 表示输出的结果类型
* 只要是函数式接口,都可以用 Lambda 表达式来简化实现
*/
public class Demo01 {
public static void main(String[] args) {
// 定义一个函数
Function<Integer, Integer> function = (x) -> x + 1;
System.out.println(function.apply(10));
System.out.println(function.apply(20));
}
}
package com.laity.function;
import java.util.function.Predicate;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.function.Demo02
* @Date: 2023年12月01日 15:22
* @Description: Predicate 函数式接口
* Predicate 接口中有一个方法 test(T t),返回一个布尔值。
*/
public class Demo02 {
public static void main(String[] args) {
// Predicate 接口中有一个方法 test(T t),返回一个布尔值。
Predicate<Integer> predicate = new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer > 10;
}
};
System.out.println(predicate.test(9));
// 使用Lambda表达式
Predicate<Integer> pre = (x) -> x > 10;
System.out.println(pre.test(11));
}
}
package com.laity.function;
import java.util.function.Consumer;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.function.Demo03
* @Date: 2023年12月01日 15:29
* @Description: Consumer 函数式接口
* Consumer 接口定义了一个 accept 方法,该方法接收一个参数,并不返回任何结果。
*/
public class Demo03 {
public static void main(String[] args) {
Consumer<String> consumer = (String s) -> {
System.out.println(s);
};
consumer.accept("Hello World");
// 顾名思义并不去生成,只获取
}
}
package com.laity.function;
import java.util.function.Supplier;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.function.Demo04
* @Date: 2023年12月01日 15:32
* @Description: Supplier 函数式接口
* Supplier 接口中有一个 get() 方法,该方法没有参数,返回一个 T 类型的值。
*/
public class Demo04 {
public static void main(String[] args) {
Supplier<String> supplier = () -> "Hello World";
System.out.println(supplier.get());
}
}
package com.laity.stream;
import java.util.Comparator;
import java.util.stream.Stream;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.stream.Test
* @Date: 2023年12月04日 09:44
* @Description: Stream 测试类
* 题目要求:一分钟内完成此题,只能用一行代码实现
* 现在有五个用户,筛选
* 1.Id必须是偶数
* 2.年龄必须大于23
* 3.用户名转为大写
* 4.用户名字母倒着排序
* 5.只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User user1 = new User(1, "l", 17);
User user2 = new User(2, "la", 25);
User user3 = new User(3, "laity", 26);
User user4 = new User(4, "laitylaity", 27);
User user5 = new User(5, "laitylaitylaity", 28);
// Stream流 + 链式操作 + 函数式编程 + lambda表达式
Stream.of(user1, user2, user3, user4, user5).filter(user -> user.getId() % 2 == 0)
.filter(user -> user.getAge() > 23)
.map(user -> user.getName().toUpperCase())
.sorted(Comparator.reverseOrder()) // (u1,u2) -> u2.compareTo(u1);
.limit(1)
.forEach(System.out::println);
}
}
分支合并
JDK1.7出现的
并行执行任务!提高效率,那么对于什么样的数据量才会有显著的效果呢?在大数据量时。就好比Hadoop(MapReduce)、Spark差不多,都是在大数量面前才会有显著的效率提高效果,分而治之的思想。
偷
一个线程来执行:也可以称之为互相帮助
)package com.laity.forkjoin;
import java.util.concurrent.RecursiveTask;
import static java.lang.Long.sum;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.forkjoin.ForkJoinDemo
* @Date: 2023年12月04日 10:31
* @Description: 基于ForkJoin框架的求和计算
* 如何使用ForkJoin框架进行计算
* 1. 继承RecursiveTask
* 2. 重写compute方法
* 3. 调用ForkJoinPool.invoke()方法
* 4. 调用ForkJoinPool.invokeAll()方法
* 5. 调用ForkJoinPool.invokeAny()方法
* 6. 调用ForkJoinPool.submit()方法
* - 通过调用ForkJoinPool来执行任务
* - 计算任务:调用ForkJoinPool.execute()方法
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private static Long start; // 1
private static Long end; // 19_0000_0000
// 临时变量
private static Long temp = 100000L;
public ForkJoinDemo(Long start, Long end) {
ForkJoinDemo.start = start;
ForkJoinDemo.end = end;
}
public static void main(String[] args) {
}
public void test() {
// 1. 继承RecursiveTask
ForkJoinDemo forkJoinDemo = new ForkJoinDemo(start, end);
// 2. 重写compute方法
forkJoinDemo.fork();
// 3. 调用ForkJoinPool.invoke()方法
// forkJoinDemo.invoke();
// 4. 调用ForkJoinPool.invokeAll()方法
// forkJoinDemo.invokeAll();
// 5. 调用ForkJoinPool.invokeAny()方法
// forkJoinDemo.invokeAny();
// 6. 调用ForkJoinPool.submit()方法
// forkJoinDemo.submit();
// 7. 调用ForkJoinPool.execute()方法
// forkJoinDemo.execute();
}
public void ord() {
// 普通方式
int sum = 0;
for (int i = 1; i <= 10_0000_0000; i++) {
sum += i;
}
System.out.println(sum);
}
// 计算方法
@Override
protected Long compute() {
if ((Long) (end - start) <= temp) {
Long sum = 0L;
for (long i = 1L; i <= end; i++) {
sum += i;
}
System.out.println(sum);
return sum;
} else {
// 使用ForkJoin框架
// 递归
long mid = (start + end) / 2; // 中间值
// 拆分任务
ForkJoinDemo left = new ForkJoinDemo(start, mid);
left.fork(); // 拆分任务,把任务压入线程队列(双端队列)
ForkJoinDemo right = new ForkJoinDemo(mid + 1, end);
right.fork();
// 等待子任务
return sum(left.join(), right.join());
}
}
/*
人生自信两百年,会当击水三千里
*/
}
package com.laity.forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.forkjoin.ExecuteDemo
* @Date: 2023年12月04日 10:58
* @Description: 模拟执行任务
* @Version: 1.0
*/
public class ExecuteDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
// 执行任务
test3();
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
}
// 普通程序员 491
public static void test1() {
long sum = 0L;
for (long i = 1L; i <= 10_0000_0000L; i++) {
sum += i;
}
System.out.println(sum);
}
// 使用ForkJoin
public static void test2() throws ExecutionException, InterruptedException {
ForkJoinPool joinPool = new ForkJoinPool();
// joinPool.execute(new ForkJoinDemo(1L, 10_0000_0000L)); // 同步执行任务;无法获取结果
ForkJoinTask<Long> task = joinPool.submit(new ForkJoinDemo(0L, 10_0000_0000L));// 异步执行任务
System.out.println(task.get());
/*
125000000250000000
125000000250000000
250000000500000000
耗时:2632
*/
}
// Stream流 并行流 - 最佳方案 331
public static void test3() {
long reduce = LongStream.rangeClosed(1L, 10_0000_0000L).parallel().reduce(0L, Long::sum);
System.out.println(reduce);
}
}
Future 设计的初衷:对将来的某个事件的结果进行建模
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
……
}
package com.laity.gulimall.search.thread;
import java.util.concurrent.*;
/**
* @author: Laity
* @Project: JavaLaity
* @Description: 异步
*/
public class CompletableFutureTest {
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture.runAsync(() -> {
System.out.println("runAsync当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println(i);
}, executorService);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync当前线程:" + Thread.currentThread().getId());
return 12 / 2;
}, executorService).whenComplete((t, u) -> {
// 虽然能够得到异常信息,但是无法修改返回值
System.out.println("异步任务成功完成了~");
System.out.println("结果是:" + t);
System.out.println("异常是:" + u);
}).exceptionally((e) -> {
// 能够得到异常信息,同时可以修改返回值
System.out.println("异常是:" + e);
return Integer.valueOf(e.toString());
}).handle((res, thr) -> {
// 方法执行完成后的处理
if (res != null) {
System.out.println("handle");
return res * 2; // 结果 * 2
} else {
return 0;
}
});
Integer integer = completableFuture.get();
System.out.println(integer);
// 线程串行化
/**
* thenRunAsync: 不能获取到上一步的执行结果
* thenAccept: 能接收上一步结果,但是无返回值。
* thenApplyAsync: 既能接收上一步的执行结构,又有返回值。
*/
/*thenRunAsync: 不能获取到上一步的执行结果*/
CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync当前线程:" + Thread.currentThread().getId());
return 12 / 2;
}, executorService).thenRunAsync(() -> {
System.out.println("thenRunAsync线程任务2启动~~");
}, executorService);
/*thenAccept: 能接收上一步结果,但是无返回值。*/
CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync当前线程:" + Thread.currentThread().getId());
return 12 / 2;
}, executorService).thenAcceptAsync(res -> {
System.out.println("thenAcceptAsync任务2启动了~~");
}, executorService);
/*thenApplyAsync: 既能接收上一步的执行结构,又有返回值。*/
CompletableFuture<String> runAsync = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync当前线程:" + Thread.currentThread().getId());
return 12 / 2;
}, executorService).thenApplyAsync(res -> {
System.out.println("thenApplyAsync任务2启动了" + res);
return res + "";
}, executorService);
String s = runAsync.get();
System.out.println("返回值" + s);
// 两个任务合并 - 组合合并 两个任务都完成,触发任务
/**
* thenCombine:组合两个 future,获取两个 future 的返回结果,并返回当前任务的返回值
* thenAcceptBoth:组合两个 future,获取两个 future 任务的返回结果,然后处理任务,没有返回值。
* runAfterBoth:组合两个 future,不需要获取 future 的结果,只需两个 future 处理完任务后,处理该任务。
*/
System.out.println("========================组合合并==================================");
CompletableFuture<Integer> future01 = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync当前任务1线程:" + Thread.currentThread().getId());
return 12 / 2;
}, executorService);
CompletableFuture<Integer> future02 = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync当前任务2线程:" + Thread.currentThread().getId());
return 14 / 2;
}, executorService);
future01.runAfterBothAsync(future02, () -> {
System.out.println("runAfterBothAsync任务1与任务2执行完毕~");
System.out.println("runAfterBothAsync任务3执行" + Thread.currentThread().getId());
}, executorService);
future01.thenAcceptBothAsync(future02, (f1, f2) -> {
System.out.println("thenAcceptBothAsync任务1与任务2执行完毕~");
System.out.println("thenAcceptBothAsync任务3执行" + Thread.currentThread().getId());
System.out.println("future01:" + f1 + ", future02" + f2);
}, executorService);
CompletableFuture<Integer> combineAsync = future01.thenCombineAsync(future02, Integer::sum, executorService);
Integer sum = combineAsync.get();
System.out.println("返回结构:" + sum);
// 两个任务中,任意一个future任务完毕的时候,执行任务。
/**
* applyToEither:两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值。
* acceptEither:两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值。
* runAfterEither:两个任务有一个执行完成,不需要获取 future 的结果,处理任务,也没有返回值。
*/
/*applyToEither*/
future01.applyToEitherAsync(future02, (f1) -> f1 + 2, executorService);
/*acceptEither*/
future01.acceptEitherAsync(future02, (res) -> {
System.out.println(res);
}, executorService);
/*runAfterEither*/
future01.runAfterEitherAsync(future02, () -> System.out.println("runAfterEither"), executorService);
// ExecutorCompletionService service = new ExecutorCompletionService(executorService);
// Future take = service.take();
// 多任务组合
/**
* allOf:等待所有任务完成
* anyOf:只要有一个任务完成
*/
System.out.println("==============多任务组合==============");
CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
System.out.println("查询商品的图片信息");
return "hello.jpg";
}, executorService);
CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {
System.out.println("查询商品的属性信息");
return "黑色 + 521g";
}, executorService);
CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
System.out.println("查询商品的介绍");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "三星";
}, executorService);
// futureImg.get();futureDesc.get();futureAttr.get(); // 阻塞等待太麻烦
CompletableFuture<Void> allOf = CompletableFuture.allOf(futureImg, futureDesc, futureAttr);
System.out.println("end...");
allOf.get(); // 等待所有结果完成
}
// public static CompletableFuture allOf(CompletableFuture>... cfs) {
// return andTree(cfs, 0, cfs.length - 1);
// }
}
JMM:Java内存模型,是不存在的!是概念、是约定。
关于JMM同步的约定:
立刻
刷回主存(线程中的工作内存就是CPU中的Cache不是每次刷新到主存中,而是失败的时候刷回到主存);线程分为:工作内存
、主内存
由于JVM运行时的实体是线程,每一个线程运行时都会创建一个工作内存,作为线程的私有空间,每个线程操作变量都在自己的工作内存,从主内存copy一份数据到工作内存,在工作内存中进行操作后,再将其刷新回主内存。如果是在多线程的情况下,则会出现线程安全问题。因此JMM规范规定了如下八种操作来完成数据的操作
数据同步八大原子操作
(1)lock(锁定):作用于主内存的变量,把一个变量标记为一条线程独占状态
(2)unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
(3)read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
(4)load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
(5)use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
(6)assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
(7)store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
(8)write(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
如果一个变量从主内存中复制到工作内存中,就需要按顺序的执行read、load指令,如果是工作内存刷新到主内存则需要按顺序的执行store、write操作。但JMM只保证了顺序执行,并没有保证连续执行。
Code
package com.laity.tvolatile;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.tvolatile.JMMDEMO
* @Date: 2023年12月13日 16:12
* @Description:
*/
public class JmmDemo {
private static int i = 0;
public static void main(String[] args) {
new Thread(() -> {
while (i == 0) {
}
}, "t1").start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
i = 1;
System.out.println("结束标志:" + i);
}
}
程序会不停止:但是i已经更改为了1;
需要线程t1知道主内存中的值 i 发生了变化(使用volatile:保证可见性)。
package com.laity.tvolatile;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.tvolatile.JMMDEMO
* @Date: 2023年12月13日 16:12
* @Description:
*/
public class JmmDemo {
private static volatile int i = 0;
public static void main(String[] args) {
new Thread(() -> {
while (i == 0) {
}
}, "t1").start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
i = 1;
System.out.println("结束标志:" + i);
}
}
原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程给打断。
在java中,对基本的数据类型的操作都是原子性的操作,但是要注意的是对于32位系统的操作对于long、double类型的并不是原子性操作(对于基本数据类型,byte,short,int,float,boolean,char读写是原子操作)。因为对于32位的操作系统来说,每次读写都是32位,而doubel、long则是64位存储单位。就会导致一个线程操作完前面32位后,另一个线程刚好读到后面的32位,这样一来一个64位被两个线程分别读取。
可见性指的是当一个共享变量被一个线程修改后,其他线程是否能够立即感知到。对于串行执行的程序是不存在可见性,当一个线程修改了共享变量后,后续的线程都能感知到共享变量的变化,也能读取到最新的值,所以对于串行程序来讲是不存在可见性问题。
对于多线程程序,就不一定了,前面分析过对于共享变量的操作,线程都是将主内存的变量copy到工作内存进行操作后,在赋值到主内存中。这样就会导致,一个线程改了之后还未回写到主内存,其余线程就无法感知到变量的更新,线程之间的工作内存是不可见的。另外指令重排序以及编译器优化也会导致可见性的问题。
有序性是指对于单线程的代码,我们总是认为程序是按照代码的顺序进行执行,对于单线程的场景这样理解是没有问题,但是在多线程情况下, 程序就会可能发生乱序的情况,编译器编译成机器码指令后,指令可能会被重排序,重排序的指令并不能保证与没有排序前的保持一致。
在java程序中,倘若在本线程内,所有的操作都可视为有序性,在多线程环境下,一个线程观察另外一个线程,都视为无顺序可言。
除了jvm自身提供的对基本类型的原子性操作以外,可以通过synchronized和Lock实现原子性。synchronized与lock在同一时刻始终只会存在一个线程访问对应的代码块。
volatile关键字保证了可见性。当一个共享变量被volatile修饰时,它会保证共享变量修改的值立即被其他线程可见,即修改的值立即刷新到主内存,当其它线程去需要读取变量时,从主内存中读取。synchronized和Lock也保证了可见性。因为同一时刻只有一个线程能访问同步代码块,所以是能保证可见性。
volatile关键字保证了有序性,synchronized和Lock也保证了有序性(因为同一时刻只允许一个线程访问同步代码块,自然保证了线程之间在同步代码块的有序执行)。
JMM内存模型:每个线程都有自己的工作内存。线程对变量的操作只能在工作内存中进行操作,并且线程之前的工作内存是不可见的。java内粗模型具备一定的先天有序性,happens-before 原则。如果两个操作无法推断出happens-before 原则,则无法保证程序的有序性。虚拟机可以随意的将它们进行排序。
指令重排:即只要程序的最终结果与顺序执行的结果保持一致,则虚拟机就可以进行排序,此过程就叫指令重排序,为啥需要指令重排序?jvm根据处理器特性(cpu多级缓存、多核处理器等)适当的对机器指令进行排序,使机器指令能更符合CPU的执行特性,最大的限度发挥机器性能。
as-if-serial语义
as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。
happens-before 原则
只靠sychronized和volatile关键字来保证原子性、可见性以及有序性,那么编写并发程序可能会显得十分麻烦,幸运的是,从JDK 5开始,Java使用新的JSR-133内存模型,提供了happens-before 原则来辅助保证程序执行的原子性、可见性以及有序性的问题,它是判断数据是否存在竞争、线程是否安全的依据,happens-before 原则内容如下:
Valatile 是Java虚拟机提供的轻量级的同步机制
保证可见性
不保证原子性
禁止指令重排
Code
package com.laity.tvolatile;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.tvolatile.JMMDEMO
* @Date: 2023年12月13日 16:12
* @Description: volatile 可见性的验证
*/
public class JmmDemo {
private static int i = 0;
public static void main(String[] args) {
new Thread(() -> {
while (i == 0) {
}
}, "t1").start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
i = 1;
System.out.println("结束标志:" + i);
}
}
程序会不停止:但是i已经更改为了1;
需要线程t1知道主内存中的值 i 发生了变化(使用volatile:保证可见性)。
package com.laity.tvolatile;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.tvolatile.JMMDEMO
* @Date: 2023年12月13日 16:12
* @Description: volatile 可见性的演示
*/
public class JmmDemo {
private static volatile int i = 0;
public static void main(String[] args) {
new Thread(() -> {
while (i == 0) {
}
}, "t1").start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
i = 1;
System.out.println("结束标志:" + i);
}
}
什么是原子性?不可分割就是原子性:要么成功要么失败
ACID;删除锁必须保证原子性。使用redis+Lua脚本完成;CAS
线程A在执行任务的时候,不能被打扰,也不能被分割
Code
package com.laity.tvolatile;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.tvolatile.VDemo01
* @Date: 2023年12月13日 16:28
* @Description: volatile 不保证原子性
* 总线嗅探机制:
* 1. 总线嗅探机制是一种特殊的指令,它会在CPU执行指令时,检查总线上是否有其他CPU正在执行指令,如果有,则CPU会暂停执行,直到其他CPU完成指令。
*/
public class VDemo01 {
private static volatile int count = 0;
// synchronized保证原子性
public static void add() {
count++;
}
public static void main(String[] args) {
// 理论上,count 应该是 10000,但是实际上可能是 9999,因为有可能有其他线程正在执行 add 方法,导致 count 不是 10000
for (int i = 0; i < 10000; i++) {
new Thread(VDemo01::add).start();
}
while (Thread.activeCount() > 2) {
// 让其他线程执行
// yield是让当前线程让出CPU执行权,让其他线程有机会执行
Thread.yield();
}
// while (count < 10000) { }
System.out.println(count);
}
}
如果不加 lock 与 synchronized,怎么保证原子性呢?
这些类的底层都与操作系统挂钩!在内存中修改值!Unsafe类是一个特殊的存在。
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
public class VDemo01 {
private static volatile AtomicInteger count = new AtomicInteger(0);
// synchronized保证原子性
public static void add() {
count.getAndIncrement(); // AtomicInteger + 1 方法,CAS
}
public static void main(String[] args) {
// 理论上,count 应该是 10000,但是实际上可能是 9999,因为有可能有其他线程正在执行 add 方法,导致 count 不是 10000
for (int i = 0; i < 10000; i++) {
new Thread(VDemo01::add).start();
}
while (Thread.activeCount() > 2) {
// 让其他线程执行
// yield是让当前线程让出CPU执行权,让其他线程有机会执行
Thread.yield();
}
// while (count < 10000) { }
System.out.println(count);
}
}
什么是指令重排?你写的程序,计算机执行过程中并不是按照你写的那样执行的。
处理器在进行指令重排的时候,是要考虑:数据直接的依赖性!可以看下面的例子:
int i = 1; // 1
int n = 4; // 2
i = i + 5; // 3
n = i * i; // 4
// 我们所期望的执行顺序是:1 2 3 4 但是 2 1 3 4、1 3 2 4。
// 可不可能是 4 1 2 3;这个是不可能的
可能造成影响的结果:前提:a b x y 默认都是0;
线程 A | 线程 B |
---|---|
x = a | y = b |
b = 1 | a = 2 |
正常结果是: x = 0;y=0;a=2;b=1.但是可能由于指令重排导致成诡异结果:
线程 A | 线程 B |
---|---|
b = 1 | a = 2 |
x = a | y = b |
可能由于指令重排导致成诡异结果:x=2;y=1.
而volatile关键字另一个作用就是禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象,关于指令重排优化前面已详细分析过,这里主要简单说明一下volatile是如何实现禁止指令重排优化的。先了解一个概念,内存屏障(Memory Barrier)这是一个CPU指令。
内存屏障的作用:
硬件层的内存屏障
Intel硬件提供了一系列的内存屏障,主要有:
\1. lfence,是一种Load Barrier 读屏障
\2. sfence, 是一种Store Barrier 写屏障
\3. mfence, 是一种全能型的屏障,具备ifence和sfence的能力
\4. Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。它后面可以跟ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG等指令。
不同硬件实现内存屏障的方式不同,Java内存模型屏蔽了这种底层硬件平台的差异,由JVM来为不同的平台生成相应的机器码。 JVM中提供了四类内存屏障指令:
内存屏障,又称内存栅栏,是一个CPU指令,它的作用有两个,一是保证特定操作的执行顺序,二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。Memory Barrier的另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。总之,volatile变量正是通过内存屏障实现其在内存中的语义,即可见性和禁止重排优化。下面看一个非常典型的禁止重排优化的例子DCL,如下:
public class DoubleCheckLock {
private volatile static DoubleCheckLock instance;
private DoubleCheckLock(){}
public static DoubleCheckLock getInstance(){
//第一次检测
if (instance==null){
//同步
synchronized (DoubleCheckLock.class){
if (instance == null){
//多线程环境下可能会出现问题的地方
instance = new DoubleCheckLock();
}
}
}
return instance;
}
}
上述代码一个经典的单例的双重检测的代码,这段代码在单线程环境下并没有什么问题,但如果在多线程环境下就可以出现线程安全问题。原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。
因为instance = new DoubleCheckLock();可以分为以下3步完成(伪代码)
memory = allocate();//1.分配对象内存空间
instance(memory);//2.初始化对象
instance = memory;//3.设置instance指向刚分配的内存地址,此时instance!=null
由于步骤1和步骤2间可能会重排序,如下:
memory=allocate();//1.分配对象内存空间
instance=memory;//3.设置instance指向刚分配的内存地址,此时instance!=null,但是对象还没有初始化完成!
instance(memory);//2.初始化对象
由于步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。那么该如何解决呢,很简单,我们使用volatile禁止instance变量被执行指令重排优化即可。
//禁止指令重排优化
private volatile static DoubleCheckLock instance;
前面提到过重排序分为编译器重排序和处理器重排序。为了实现volatile内存语义,JMM会分别限制这两种类型的重排序类型。
下面是保守策略下,volatile写插入内存屏障后生成的指令序列示意图
上图中StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。
这里比较有意思的是,volatile写后面的StoreLoad屏障。此屏障的作用是避免volatile写与 后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面 是否需要插入一个StoreLoad屏障(比如,一个volatile写之后方法立即return)。为了保证能正确 实现volatile的内存语义,JMM在采取了保守策略:在每个volatile写的后面,或者在每个volatile 读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM最终选择了在每个 volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个 写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里可以看到JMM 在实现上的一个特点:首先确保正确性,然后再去追求执行效率。
下图是在保守策略下,volatile读插入内存屏障后生成的指令序列示意图
上图中LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。
上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变 volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。下面通过具体的示例
代码进行说明。
class VolatileBarrierExample {
int a;
volatile int v1 = 1;
volatile int v2 = 2;
void readAndWrite() {
int i = v1; // 第一个volatile读
int j = v2; // 第二个volatile读
a = i + j; // 普通写
v1 = i + 1; // 第一个volatile写
v2 = j * 2; // 第二个 volatile写
}
}
volatile 可以保证 数据可见性,不能保证其原子性,由于内存屏障,可以保证避免指令重排的现象。
同时****针对readAndWrite()方法,编译器在生成字节码时也是可以进一步去优化的,具体文章后续会出。
饿汉式,DCL懒汉式
具体文章可以观看我的另一篇文章:设计模式 - 单例设计模式
什么是CAS?
atomicInteger.getAndIncrement(); // ++ 操作; 下图是其源码分析
而图二中有个 compareAndSwapInt 比较和交换:下面我们通过compareAndSet进一步了解它
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.cas.CasDemo
* @Date: 2023年12月14日 22:25
* @Description: CAS
*/
public class CasDemo {
// CAS 是乐观锁的一种实现方式,它允许一个线程在执行时检查另一个线程是否修改过共享变量,
// 如果没有被修改过,则执行,如果被修改过,则重试,直到成功为止。
// CAS 包含三个操作数,内存值 V、预估值 A、更新值 B。如果内存值 V 等于预估值 A,则将内存值修改为 B,否则什么都不做。
// 这三个操作数必须是原子操作,即在一个时刻只能有一个线程对它们进行操作。
// compareAndSet() 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2023);
/*
compareAndSet的源码:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
*/
// 如果我期望的值达到2023,那么我就将它修改为2021,如果不满足,就不修改。
atomicInteger.compareAndSet(2023, 2021);
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2023, 2021));
atomicInteger.getAndIncrement(); // ++ 操作
}
}
所以 !this.compareAndSwapInt(var1, var2, var5, var5 + var4) 代码意思就是 var1偏移var2后如果等于var5,那么就 + 1
while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
总结CAS: 比较并交换
比较当前工作内存中的值和主内存的值,如果这个值是期望的那么则执行操作!如果不是就自旋。
缺点:
CAS: ABA问题:狸猫换太子
原子引用解决ABA问题
那什么是ABA问题呢?
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
package com.laity.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.cas.AbaDemo
* @Date: 2023年12月15日 10:26
* @Description: 复现ABA问题并解决
*/
public class AbaDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2023);
// ===========================捣乱的线程===========================
System.out.println(atomicInteger.compareAndSet(2023, 2024));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2024, 2023));
System.out.println(atomicInteger.get());
// ===========================期望的线程===========================
System.out.println(atomicInteger.compareAndSet(2023, 2025));
System.out.println(atomicInteger.get());
}
}
假设小琳银行卡有 100 块钱余额,且假定银行转账操作就是一个单纯的 CAS 命令,对比余额旧值是否与当前值相同,如果相同则发生扣减/增加,我们将这个指令用 CAS(origin,expect) 表示。于是,我们看看接下来发生了什么:
关于钱的去向,有一种可能就是小王给小琳的 100 大洋,因为 ATM 1 网络恢复再次被转给了小李,毕竟小琳尝试了两次转账,出现这种情况虽不合理,但情有可原。假设我们作为银行系统设计者和开发者,不接受这种情况存在,那我们就需要着手处理这种 ABA 问题了。
引入原子引用:AtomicStampedReference reference = new AtomicStampedReference<>(1, 1);
对应思想:乐观锁!
package com.laity.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.cas.AbaDemo
* @Date: 2023年12月15日 10:26
* @Description: 复现 CAS-ABA问题并 解决(乐观锁原理)
*/
public class AbaDemo {
public static void main(String[] args) {
// AtomicInteger atomicInteger = new AtomicInteger(2023);
// 使用原子引用
// new AtomicReference<>() // 基本的原子引用
// AtomicStampedReference(V initialRef, int initialStamp) 初始值和初始时间戳
// 携带时间戳的原子引用 这个时间戳就相当于版本号,每次修改值的时候,时间戳都会加1
// TODO: Integer使用了对象缓存机制,默认范围是-128到127,超过范围会自动装箱,导致引用不同,导致交换失败
// 推荐使用静态工厂方法valueOf()获取对象实例,而不是new 因为valueOf()使用缓存,而new一定会创建新的对象分配新的内存空间
// new AtomicStampedReference<>(2023, 123) 将2023改小些就不会出现问题了。正常业务操作比较的是一个对象
AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(1, 1);
// 自动装箱导致引用不同,导致交换失败
new Thread(() -> {
int stamp = reference.getStamp(); // 获取时间戳
System.out.println("t1获取时间戳:" + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(reference.compareAndSet(1, 2, reference.getStamp(), reference.getStamp() + 1));
System.out.println("t1修改后的时间戳:" + reference.getStamp());
System.out.println(reference.compareAndSet(2, 1, reference.getStamp(), reference.getStamp() + 1));
System.out.println("t1最后修改后的时间戳:" + reference.getStamp());
}, "t1").start();
new Thread(() -> {
int stamp = reference.getStamp(); // 获取时间戳
System.out.println("t2获取时间戳:" + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(reference.compareAndSet(1, 3, reference.getStamp(), reference.getStamp() + 1));
System.out.println("t2值:" + reference.getStamp());
}, "t2").start();
// // ===========================捣乱的线程===========================
// System.out.println(atomicInteger.compareAndSet(2023, 2024));
// System.out.println(atomicInteger.get());
//
// System.out.println(atomicInteger.compareAndSet(2024, 2023));
// System.out.println(atomicInteger.get());
//
// // ===========================期望的线程===========================
// System.out.println(atomicInteger.compareAndSet(2023, 2025));
// System.out.println(atomicInteger.get());
}
}
ReentrantLock reentrantLock = new ReentrantLock();
// 源码
public ReentrantLock() {
sync = new NonfairSync();
}
ReentrantLock reentrantLock = new ReentrantLock(true);
// 源码
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
当我们要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发的发生。
为什么叫做悲观锁呢?因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。
数据库中的行锁,表锁,读锁,写锁,以及 synchronized 实现的锁均为悲观锁。
顾名思义,就是比较悲观的锁,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁,只有到数据提交的时候才通过一种机制来验证数据是否存在冲突。
乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。
乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行 +1 操作,否则就执行失败。
反之,总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
我们知道悲观锁和乐观锁是用来控制并发下数据的顺序变动问题的。那么我们就模拟一个需要加锁的场景,来看不加锁会出什么问题,并且怎么利用悲观锁和乐观锁去解决。
我们以商品为例,现在 线程A 和线程 B 都想吃红薯,但是红薯数量只有 1 个了。在不加锁的情况下,如果A,B同时下单,就有可能导致超卖。
利用悲观锁的解决思路是,我们认为数据修改产生冲突的概率比较大,所以在更新之前,我们显示的对要修改的记录进行加锁,直到自己修改完再释放锁。加锁期间只有自己可以进行读写,其他事务只能读不能写。
此时线程 A 下单前先给红薯这行数据(id=C001)加上悲观锁(行锁)。此时这行数据只能 A 来操作,也就是只有 A 能买。B 想买就必须一直等待。当 A 买好后,B 再想去买的时候会发现库存数量已经为 0,那么 B 看到后就会放弃购买。
那怎么样给这行数据加上悲观锁呢?当然是在select给这行数据加上锁,如下所示:
select num from commodity where id = C001 for update
悲观锁图解:
下面我们利用乐观锁来解决该问题。上面乐观锁的介绍中,我们提到了,乐观锁是通过版本号 version 来实现的。所以,我们需要给 commodity 表加上 version 字段。
我们认为数据修改产生冲突的概率并不大,多个线程在修改数据的之前先查出版本号,在修改时把当前版本号作为修改条件,只会有一个线程可以修改成功,其他线程则会失败。
A 和 B 同时将红薯(id=C001)的数据查出来,然后 A 先买,A 将 id=C001 和 version=0 作为条件进行数据更新,即将数量 -1,并且将版本号+1。
此时版本号变为 1。A 此时就完成了商品的购买。最后 B 开始买,B 也将 id=C001 和 version=0 作为条件进行数据更新,但是更新完后,发现更新的数据的库存为 0,此时就说明已经有人修改过数据,此时就应该提示用户重新查看最新数据购买。
乐观锁图解:
1.版本号机制:
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。就是通过version版本号作为一个标识,标识这个字段所属的数据是否被改变。
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
2.CAS算法:
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
这里推荐篇文章【面试必备之深入理解自旋锁】:https://blog.csdn.net/qq_34337272/article/details/81252853
1 ABA 问题
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
2 循环时间长开销大
自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3 只能保证一个共享变量的原子操作
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。
青春好比吸烟。烟在飞扬。烟灰在坠落。我是Laity,正在前行的Laity。
面试八股文: https://github.com/Snailclimb/JavaGuide
注意:ReetrantLock锁的就是ReetrantLock对象,ReetrantLock中有AQS,AQS可以判断两次lock方法是否都是同一个线程。
锁必须配对,否则就会出现死锁。
CAS中的自旋锁
package com.laity.lock;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: Laity
* @Project: JavaLaity
* @Package: com.laity.lock.SpinlockDemo
* @Date: 2023年12月15日 17:44
* @Description: 实现自旋锁
*/
public class SpinlockDemo {
// int 0
// boolean false
// Thread null
AtomicReference<Thread> atomicReference = new AtomicReference<>();
// 创建锁
public void myLock() {
Thread thread = Thread.currentThread();
// 如果我的线程是空,则把我的线程丢进去,设置当前线程为锁
// 当前线程不为空,则等待
System.out.println("当前线程:" + thread.getName() + "获取锁");
// do {
// // 自旋
// System.out.println("当前线程:" + thread.getName() + "获取锁");
// } while (!atomicReference.compareAndSet(null, thread));
while (!atomicReference.compareAndSet(null, thread)){
}
}
// 释放锁
public void myUnlock() {
Thread thread = Thread.currentThread();
System.out.println("当前线程:" + thread.getName() + "释放锁");
// 释放锁
atomicReference.compareAndSet(thread, null);
}
public static void main(String[] args) throws InterruptedException {
// 底层使用的是CAS自旋锁
SpinlockDemo spinlockDemo = new SpinlockDemo();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
spinlockDemo.myLock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinlockDemo.myUnlock();
}, "t1").start();
}
Thread.sleep(3000);
for (int i = 0; i < 2; i++) {
new Thread(() -> {
spinlockDemo.myLock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinlockDemo.myUnlock();
}, "t2").start();
}
}
}
死锁(Deadlock)情况是指:两个或两个以上的线程持有不同系统资源的锁,线程彼此都等待获取对方的锁来完成自己的任务,但是没有让出自己持有的锁,线程就会无休止等待下去。线程竞争的资源可以是:锁、网络连接、通知事件,磁盘、带宽,以及一切可以被称作“资源”的东西
概念性的东西读起来可能有点抽象,那我举个比较容易理解的例子:假设有两个线程A 和 B,两把锁 LockA和LockB,线程A持有LockA锁,线程B持有LockB锁 , 在双方不释放锁的情况下,再尝试去获取对方持有的锁,也就是说,线程A 手里拿着LockA锁不放,又去获取LockB锁(此时LockB锁在线程B手里),而线程B手里拿着LockB锁不放,又去获取LockA锁(此时LockA锁在线程A手里),在这种情况下,就会陷入无限等待的死锁状态。
知道了什么是死锁,下一步就是当遇到死锁问题时,如何利用工具去排查和检测死锁问题,下面就介绍两款好用的工具来帮助我们检测死锁。
Jstack 工具
使用Jconsole 工具
其实产生死锁也不是那么容易的,需要满足四个条件,才会产生死锁。
我们只需要让其中一个条件不成立,那么就可以避免死锁问题的产生。一般最常见的解决方式就是使用资源有序分配法,来使环路等待条件不成立。
环路等待就是我们刚开始代码演示的那种情况,两个线程互相尝试获取对方的锁,但是他们两个都不会释放自己的锁,这样就会陷入一个无限循环等待,这种情况就是环路等待。
资源有序分配法其实很简单,就是**把线程获取资源的顺序调整为一致的即可,**资源可以理解为代码示例中的锁。
那么我们只需要修改线程A或者线程B的代码即可。线程B原本是先获取LockB再获取LockA,改成和线程A一样的获取顺序,先获取LockA,再获取LockB:
总结
谋生的路上不抛弃良知,谋爱的路上不放弃尊严,做一个自尊自爱的人,如此安好,所爱任性,所作随心……我是Laity,正在前进的Laity。