多线程高并发JUC编程
「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战」。
关于作者
- 作者介绍
🍓 博客主页:作者主页
🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨💻
什么是JUC
源码+官方文档
JUC是 java util concurrent
面试高频问JUC~!
java.util 是Java的一个工具包
业务:普通的线程代码 Thread
Runnable: 没有返回值、效率相比于Callable 相对较低!
线程和进程
进程:一个程序,允许一个java程序会进程里面会出现一个java.exe;数据+代码+pcb
一个进程可以包含多个线程,至少包含一个线程!
Java默认有几个线程?2个线程! main线程、GC线程
线程:开了一个进程qq,聊天打字,消息提示(线程负责的)
对于Java而言:Thread、Runable、Callable进行开启线程的。
JAVA真的可以开启线程吗? 开不了的! 原因Java没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。
并发、并行
并发: 多线程操作同一个资源。
- CPU 只有一核,模拟出来多条线程,那么我们就可以使用CPU快速交替,来模拟多线程。
并行: 多个人并排行走。
- CPU多核,多个线程可以同时执行。
public class Test {
public static void main(String[] args) {
//获取cpu的核数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源!
线程的6个状态
public enum State {
//创建
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
面试题:谈一谈wait和sleep区别?
| 区别 | wait | sleep |
|---|---|---|
| 操作的类 | Object | Thread |
| 锁的释放 | 会释放锁 | 抱着锁睡觉 |
| 范围 | 同步代码块中 | 任何地方 |
| 异常捕获 | 不需要捕获异常 | 需要捕获异常 |
Lock锁(重点)
synchronized锁问题
package com.zmz.day01;
/**
* @ProjectName: Juc
* @Package: com.zmz.day01
* @ClassName: TicketTest
* @Author: 张晟睿
* @Date: 2021/9/5 14:01
* @Version: 1.0
*/
//资源类 属性 + 方法 oop
class Ticket{
private int num = 50;
//卖票方式 synchronized 本质:队列 锁
public synchronized void sale(){
if(num > 0){
System.out.println(Thread.currentThread().getName()+ " 卖出了第"+ num +" 张票,剩余:"+ --num +" 张票");
}
}
}
public class TicketTest {
public static void main(String[] args) {
//多线陈操作
//并发:多个线程操作同一个资源ticket
Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口 jdk1.8之后 lambda表达式
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"C").start();
}
}
Lock接口
公平锁: 公平,必须先来后到~;
非公平锁: 不公平,可以插队;(默认为非公平锁)
使用Lock进行操作
-
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition。 -
锁是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如
ReadWriteLock的读锁。 -
使用
synchronized方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当获取多个锁时,它们必须以相反的顺序被释放,并且所有的锁都必须被释放在与它们相同的词汇范围内。 -
虽然
synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。 例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。 所述的实施方式中Lock接口通过允许获得并在不同的范围释放的锁,并允许获得并以任何顺序释放多个锁使得能够使用这样的技术。 -
随着这种增加的灵活性,额外的责任。 没有块结构化锁定会删除使用
synchronized方法和语句发生的锁的自动释放。 在大多数情况下,应使用以下惯用语:
```
Lock l = ...; l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
```
-
当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁定。
-
Lock实现提供了使用synchronized方法和语句的附加功能,通过提供非阻塞尝试来获取锁(tryLock()),尝试获取可被中断的锁(lockInterruptibly()) ,以及尝试获取可以超时(tryLock(long, TimeUnit))。 -
一个
Lock类还可以提供与隐式监视锁定的行为和语义完全不同的行为和语义,例如保证排序,非重入使用或死锁检测。 如果一个实现提供了这样的专门的语义,那么实现必须记录这些语义。 -
请注意,
Lock实例只是普通对象,它们本身可以用作synchronized语句中的目标。 获取Lock实例的监视器锁与调用该实例的任何lock()方法没有特定关系。 建议为避免混淆,您不要以这种方式使用Lock实例,除了在自己的实现中。
除非另有说明,传递任何参数的`null`值将导致`NullPointerException`被抛出。
### 内存同步
所有`Lock`实施*必须*执行与内置监视器锁相同的内存同步语义,如The Java Language Specification (17.4 Memory Model) [所述](http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4) :
- 成功的`lock`操作具有与成功*锁定*动作相同的内存同步效果。
- 成功的`unlock`操作具有与成功*解锁*动作相同的内存同步效果。
不成功的锁定和解锁操作以及重入锁定/解锁操作,不需要任何内存同步效果。
实施注意事项
-
锁定采集(可中断,不可中断和定时)的三种形式在性能特征,排序保证或其他实施质量方面可能不同。 此外,在给定的
Lock课程中,中断正在获取锁的能力可能不可用。 因此,不需要实现对所有三种形式的锁获取完全相同的保证或语义,也不需要支持正在进行的锁获取的中断。 需要一个实现来清楚地记录每个锁定方法提供的语义和保证。 它还必须遵守此接口中定义的中断语义,只要支持锁获取的中断,即全部或仅在方法输入。 -
由于中断通常意味着取消,并且检查中断通常是不频繁的,所以实现可以有利于通过正常方法返回来响应中断。 即使可以显示中断发生在另一个动作可能已经解除了线程之后,这是真的。 一个实现应该记录这个行为。
package com.zmz.day01;/**
* @ProjectName: Juc
* @Package: com.zmz.day01
* @ClassName: TicketTest2
* @Author: 张晟睿
* @Date: 2021/9/5 16:15
* @Version: 1.0
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*@ClassName TicketTest2
*@Description
*@Author 张晟睿
*@Date 2021/9/5
**/
class Ticket2{
/*
* 加锁三步
* 1.实例化lock对象
* 2.lock加锁
* 3.unlock解锁
* */
Lock l = new ReentrantLock();
private int num = 50;
//卖票方式 synchronized 本质:队列 锁
public void sale(){
//加锁
l.lock();
try {
//业务代码
if(num > 0){
System.out.println(Thread.currentThread().getName()+ " 卖出了第"+ num +" 张票,剩余:"+ --num +" 张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
l.unlock();
}
}
}
public class TicketTest2 {
public static void main(String[] args) {
//多线陈操作
//并发:多个线程操作同一个资源ticket
Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口 jdk1.8之后 lambda表达式
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"C").start();
}
}
| 区别 | synchronized | lock |
|---|---|---|
| 名称 | 属于关键字 | 属于对象 |
| 状态 | 不可以获取锁的状态 | 可以获取锁的状态 |
| 锁的管理 | 自动释放锁 | 需要手动加锁以及释放锁 |
| 线程 | 自己抱着锁 | 等待 |
| 可重入锁,不可以中断的,非公平的 | 可重入的,可以判断锁,可以自己设置公平锁和非公平锁 | |
| 代码同步 | 适合少量的代码同步 | 适合大量的代码同步 |
生产者消费者问题
synchronized版
package com.zmz.day01;
/**
* @ProjectName: Juc
* @Package: com.zmz.day01
* @ClassName: TicketTest3
* @Author: 张晟睿
* @Date: 2021/9/5 16:35
* @Version: 1.0
*/
/**
*@ClassName TicketTest3
*@Description
*@Author 张晟睿
*@Date 2021/9/5
**/
public class TicketTest3 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待 业务 唤醒
class Data{
private int number = 0;
// +1操作
public synchronized void increment() throws InterruptedException {
if(number != 0 ){
this.wait();
}
number++;
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();
}
}
问题存在,A线程B线程,现在如果我有四个线程A B C D!该怎么去解决问题
if判断改为While判断就可以解决虚假唤醒的问题。
package com.zmz.day01;
/**
* @ProjectName: Juc
* @Package: com.zmz.day01
* @ClassName: TicketTest3
* @Author: 张晟睿
* @Date: 2021/9/5 16:35
* @Version: 1.0
*/
/**
*@ClassName TicketTest3
*@Description
*@Author 张晟睿
*@Date 2021/9/5
**/
//线程之间的通讯问题:生产者和消费者的问题! 等待唤醒,通知唤醒
//线程交替执行 A B操作同一个资源
public class TicketTest3 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待 业务 唤醒
class Data{
private int number = 0;
// +1操作
public synchronized void increment() throws InterruptedException {
while(number != 0 ){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
// -1操作
public synchronized void decrement() throws InterruptedException{
while (number == 0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
}
JUC版本的解决A B C D多线程的问题
package com.zmz.day01;
/**
* @ProjectName: Juc
* @Package: com.zmz.day01
* @ClassName: JucTest1
* @Author: 张晟睿
* @Date: 2021/9/5 19:34
* @Version: 1.0
*/
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*@ClassName JucTest1
*@Description
*@Author 张晟睿
*@Date 2021/9/5
**/
public class JucTest1 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{for(int i=0;i<10;i++) {
data.increment();
}
},"A").start();
new Thread(()->{for(int i=0;i<10;i++) {
data.decrement();
}},"B").start();
new Thread(()->{for(int i=0;i<10;i++) {
data.increment();
}
},"C").start();
new Thread(()->{for(int i=0;i<10;i++) {
data.decrement();
}
},"D").start();
}
}
class Data2{
private int number = 0;
//lock锁
Lock l = new ReentrantLock();
Condition condition = l.newCondition();
public void increment() {
l.lock();
try {
//业务
while (number!=0){
//等待操作
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我+1完毕了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
public void decrement() {
l.lock();
try {
//业务
while (number==0){
//等待操作
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我-1完毕了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}
Condition的优势:精准通知、唤醒的线程
package com.zmz.day01;
/**
* @ProjectName: Juc
* @Package: com.zmz.day01
* @ClassName: JucTest2
* @Author: 张晟睿
* @Date: 2021/9/5 19:52
* @Version: 1.0
*/
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*@ClassName JucTest2
*@Description
*@Author 张晟睿
*@Date 2021/9/5
**/
public class JucTest2 {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printA();
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printB();
}
},"B").start();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printC();
}
},"C").start();
}
}
class Data3{
private Lock l = new ReentrantLock();
Condition condition1 = l.newCondition();
Condition condition2 = l.newCondition();
Condition condition3 = l.newCondition();
private int flag = 1;
public void printA(){
l.lock();
//判断 -> 执行 -> 通知
try {
while(flag != 1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "->A" );
flag = 2;
//唤醒指定线程
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
public void printB(){
l.lock();
//判断 -> 执行 -> 通知
try {
while(flag != 2){
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "->BB" );
flag = 3;
//唤醒指定线程
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
public void printC(){
l.lock();
//判断 -> 执行 -> 通知
try {
while(flag != 3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "->CCC" );
flag = 1;
//唤醒指定线程
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}
后语
厂长写博客目的初衷很简单,希望大家在学习的过程中少走弯路,多学一些东西,对自己有帮助的留下你的赞赞👍或者关注➕都是对我最大的支持,你的关注和点赞给厂长每天更文的动力。
对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!