[从零开始学习JAVA ] 深入多线程

前言:

当今软件开发领域中,多线程编程已成为一项至关重要的技能。然而,要编写出高效、可靠的多线程程序并不容易。多线程编程面临着许多挑战,如线程安全性、资源共享、死锁等问题。因此,对于初学者来说,深入理解Java多线程的工作原理和机制是至关重要的。只有通过掌握多线程的核心概念、了解常见问题和解决方案,我们才能写出健壮且高性能的多线程应用。

        本文将为大家逐步深入介绍Java多线程的重要概念和机制。我们将从线程的创建和启动开始,讨论如何使用线程池管理线程,并探讨线程间的通信和同步技术。我们还将介绍一些常用的多线程设计模式和最佳实践,帮助读者更好地应用多线程技术解决实际问题。

1.线程的寿命周期 

线程的生命周期描述了一个线程从创建到终止的整个过程,一般包含以下几个阶段:

新建状态(New)

  • 当线程对象被创建后,它处于新建状态。
  • 此时,线程还未被启动,即尚未调用start()方法。

可运行状态(Runnable)

  • 当线程调用start()方法后,进入可运行状态。
  • 线程处于此状态时,可能正在执行,也可能正在等待系统资源。

运行状态(Running):

  • 可运行状态中的线程被系统调度执行,处于运行状态。
  • 线程执行run()方法中的任务代码。

阻塞状态(Blocked):

  • 阻塞状态指线程因为某些原因暂时停止执行,例如等待某个资源、等待锁的释放等。
  • 当满足特定条件时,线程会进入阻塞状态,等待条件满足后被唤醒。

无限期等待状态(Waiting):

  • 线程在某些条件下调用无参数的wait()方法,会进入无限期等待状态。
  • 线程执行run()方法中的任务代码。

限期等待状态(Timed Waiting):

  • 线程在某些条件下调用具有超时参数的wait()、sleep()、join()或LockSupport.parkNanos()等方法,会进入限期等待状态。
  • 时间一过,或者收到特定事件的通知,该线程将会被唤醒

终止状态(Terminated):

  • 线程执行完run()方法中的任务代码,或者线程发生异常而提前结束,都会进入终止状态。
  • 一旦线程进入终止状态,就不能再切换到其他状态。

需要注意的是,线程的状态可以相互切换,具体的转换由Java的线程调度器和操作系统决定。线程的生命周期和状态的转换对于多线程编程非常重要,合理地管理线程的状态可以提高程序的性能和并发能力。


2.线程的安全问题

我们用一个案例来说明:

现在我们要开设三个窗口来买票,一共有100张票,请你利用多线程的知识完成。

 
class MyThread extends Thread {static int  tick=0;public void run() {// 定义线程要执行的任务while(true){if(tick<100){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}tick++;System.out.println(getName()+"正在卖第"+tick+"张票");}else{break;}}}
}

我们在测试类中测试 

 
public class test05 {public static void main(String[] args) {MyThread  t1 = new MyThread();MyThread  t2 = new MyThread();MyThread  t3 = new MyThread();t1.start();t2.start();t3.start();}
}

很多同学在第一时间就会写出这样一个简单的多线程,但是当我们运行之后,就会有一个明显的问题:出现了一张票卖了两次这种情况 ,也会出现了卖超了的这种现象。

我们来详细解释一下为什么 线程1和线程2和线程3都在抢夺cpu调度,假设线程1抢到之后,那么他先进入if语句,但if语句中有一个sleep,执行到这里后,线程1就会被阻塞睡眠,此时线程2和线程3重新抢夺cpu调度,线程2抢到资源之后进入if语句也会睡眠,然后就是线程3进入资源,也会睡眠。随着这三个的睡眠周期结束,就又会执行if中的代码。当tick还没来得及打印的时候,线程2醒来又会抢夺cpu资源,如果抢到了,就又会执行一次tick++,接下来又是线程3.如此这样循环,就会造成卖出两张票并且可能卖超的结果。

通过这个案例我们可以看出多线程在执行的时候,有一个重要的隐患:

线程的执行具有随机性

那么我们最简单的思路就是:

设计一种方法,这个方法使得  如果一个线程正在执行代码,那么其他的线程必须等待,只有当这个线程执行完之后,其他的线程才可以抢占CPU资源。这就是我们下面要介绍的东西 

3.锁 

同步代码块

把代码块用锁锁起来

synchronized(锁)
{操作共享数据的代码
}

特点:

  •         锁是默认打开的,如果有一个进程进去了,锁就会自动关闭。
  •         里面的代码全部执行完毕,线程出来,锁自动打开

因此我们尝试一下用锁来改进一下

 
class MyThread extends Thread {static int  tick=0;static Object oj = new Object();public void run() {// 定义线程要执行的任务while(true){try {Thread.sleep(10);} catch (InterruptedException e){throw new RuntimeException(e);}synchronized (oj){if(tick<10000){tick++;System.out.println(getName()+"正在卖第"+tick+"张票");}else{break;}}}}
}

锁的注意点:

锁的粒度:要在保证线程安全的前提下,尽量减小锁的范围。过大的锁粒度可能导致不必要的线程阻塞,影响性能。可以考虑使用细粒度锁或者使用并发集合类来提高并发性能。

锁的公平性:锁可以是公平的或非公平的。公平锁会按照线程请求锁的顺序依次获取锁,而非公平锁则不保证线程获取锁的先后顺序。在选择锁时,根据具体情况选择公平或非公平锁。

死锁情况:死锁是指两个或多个线程相互等待对方释放持有的锁,从而导致所有线程无法继续执行的情况。为避免死锁,需要谨慎设计锁的获取顺序,并尽量避免嵌套锁的情况。

锁的释放:在使用锁时,需要保证锁的正确释放,以免出现资源泄漏或线程饥饿等问题。一般可以使用try-finally块来确保在发生异常时仍能正确释放锁。

锁的性能:锁的竞争会带来一定的性能开销,过多的锁竞争可能会影响应用的并发性能。可以考虑使用读写锁、无锁数据结构或并发集合类等替代方案,来降低锁竞争带来的性能开销。

死锁检测和避免:一旦发生死锁,所有线程都将无法继续执行。为了避免死锁,可以使用工具进行死锁检测,并合理设计锁的获取和释放顺序,避免潜在的死锁情况。

同步方法

 把方法用  锁  锁起来

修饰符   synchronized  返回值类型  方法名  (方法参数){...}

特点:

  • 同步方法是锁住方法里面的所有代码
  • 锁对象不能自己指定

非静态:this

静态:当前类的字节码文件

则我们可把前面的改写为:

class MyThread   extends Thread  {static int  tick=0;static final Object oj = new Object();public synchronized void run() {// 定义线程要执行的任务while(true){if (tick < 100) {tick++;System.out.println(getName() + "正在卖第" + tick + "张票");} else {break;}}}}

死锁:

死锁是指在多线程编程中,两个或多个线程互相持有对方需要的资源,导致它们都无法继续执行,称为死锁现象。

死锁的发生通常需要满足以下四个条件,也称为死锁的必要条件:

互斥条件:至少有一个资源同时只能被一个线程持有。
请求与保持条件:一个线程在持有某个资源的同时,又请求获取其他线程持有的资源。
不可剥夺条件:已经分配给一个线程的资源不能被强制性地剥夺,只能由持有该资源的线程显式释放。
循环等待条件:多个线程之间形成循环等待一系列资源,而每个线程都在等待下一个线程所持有的资源。
当以上四个条件都满足时,就可能出现死锁。在死锁发生时,这些线程将无法继续执行下去,需要通过一些策略进行解决,如避免死锁的产生、检测死锁、解除死锁等。

解决死锁的方法一般有以下几种:

避免死锁:通过破坏死锁的必要条件之一,如避免循环等待,确保资源分配的顺序性。
检测与恢复:通过资源分配图、银行家算法等方法检测死锁的发生,然后采取相应的策略进行恢复,如终止某些线程、回收资源等。
预防死锁:通过一些算法和策略在设计阶段预防死锁的发生,如资源有序分配法、资源剥夺等。
忽略死锁:对于一些系统来说,死锁的发生概率较低且解决代价较高,可以选择忽略死锁。当发生死锁时,通过系统重启或人工介入恢复正常。

 

public class DeadlockExample {public static void main(String[] args) {final Object resource1 = new Object();final Object resource2 = new Object();Thread thread1 = new Thread(() -> {synchronized (resource1) {System.out.println("Thread 1 acquired lock on resource1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (resource2) {System.out.println("Thread 1 acquired lock on resource2");}}});Thread thread2 = new Thread(() -> {synchronized (resource2) {System.out.println("Thread 2 acquired lock on resource2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (resource1) {System.out.println("Thread 2 acquired lock on resource1");}}});thread1.start();thread2.start();// 等待两个线程执行完毕try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Execution completed");}
}

在上述代码中,两个线程 thread1 和 thread2 分别尝试获取 resource1 和 resource2 的锁。但是它们获取锁的顺序是相反的,即 thread1 先获取 resource1 的锁,再获取 resource2 的锁;而 thread2 先获取 resource2 的锁,再获取 resource1 的锁。

这种情况下,如果两个线程同时启动,则 thread1 获取了 resource1 的锁并等待 resource2 的锁释放,而 thread2 获取了 resource2 的锁并等待 resource1 的锁释放。由于两个线程相互等待对方所持有的锁,它们将处于死锁状态,无法继续执行下去。

4.生产者和消费者模式(等待唤醒机制)

在Java中,生产者-消费者模式是一种常见的多线程协作模式,用于解决生产者和消费者之间的数据交换和同步问题。

我们在以前的多线程中,会发现每条线程是都执行都是随机的,可能会是

A A A A B B A A B A 

而等待唤醒机制可以是线程的交替变得有规律,变为

A B A B A B A B A B A

 生产者是生成数据的线程,而消费者是消耗数据的线程。下面是对Java中生产者和消费者的详细介绍:

 生产者  

  • 生产者负责生产数据,并将其放入共享的缓冲区或队列中,以供消费者使用。
  • 生产者线程通常会循环执行,生成数据并将其添加到缓冲区中。
  • 当缓冲区已满时,生产者会等待,直到有足够的空间来存放新的数据。

 消费者  

  • 消费者负责从缓冲区中获取数据,并进行消费或处理。
  • 消费者线程通常会循环执行,从缓冲区中取出数据并进行相应的处理操作。
  • 当缓冲区为空时,消费者会等待,直到有新的数据可供消费。

 共享缓冲区  

  • 生产者和消费者之间的数据交换通常通过共享的缓冲区或队列来进行。
  • 缓冲区可以是一个数组、一个队列或其他数据结构,用来存放生产者生成的数据,供消费者取出。
  • 缓冲区的大小是有限的,当缓冲区已满时,生产者必须等待;当缓冲区为空时,消费者必须等待。
  • 生产者将数据添加到缓冲区的末尾,而消费者从缓冲区的前端消费数据

为了实现生产者-消费者模式,可以使用以下方法之一:

wait() 和 notify():

使用对象的 wait() 和 notify() 方法来实现线程的等待和唤醒操作。
生产者在缓冲区已满时调用 wait() 方法进行等待,并在生产数据后调用 notify() 方法唤醒消费者。
消费者在缓冲区为空时调用 wait() 方法进行等待,并在消费数据后调用 notify() 方法唤醒生产者。

Condition 和 Lock:

使用 java.util.concurrent.locks.Condition 和 java.util.concurrent.locks.Lock 接口来实现线程的等待和唤醒操作。
生产者和消费者分别使用不同的条件变量来等待和唤醒。
使用Lock对象来保护共享数据的访问,通过条件变量的 await() 和 signal() 方法进行线程的等待和唤醒操作。

生产者-消费者模式可以帮助解决多线程并发情况下的数据同步和数据交换问题,确保生产者和消费者之间的协调运行。这种模式在许多并发编程场景中都有应用,如线程池、消息队列、生产者-消费者问题等。

生产者与消费者模式的意义:

解耦生产者和消费者:

生产者-消费者模式将数据的生产和消费过程解耦,使得生产者和消费者可以独立进行操作。
生产者只需关注生成数据并将其放入缓冲区,而不需要关心数据如何被消费。
消费者只需关注从缓冲区中获取数据并进行相应处理,而不需要关心数据的生成过程。
提高系统的并发性和吞吐量:

生产者和消费者可以并行地执行,从而提高系统的并发性能。

生产者不必等待消费者完成对数据的处理,而可以继续生产新的数据。
消费者不必等待生产者生成新的数据,而可以并行地处理已有的数据。
缓冲区平衡生产和消费速度:

生产者和消费者之间通过共享的缓冲区进行数据交换和同步。

缓冲区充当了生产者和消费者之间的中介,平衡了它们之间的生产和消费速度。
当生产者速度快于消费者时,数据会被存储在缓冲区中,以供消费者使用。
当消费者速度快于生产者时,消费者可以从缓冲区中获取数据,而不必等待生产者生成。
实现线程间的通信和同步:

生产者-消费者模式为线程间的通信和同步提供了一种有效的方式。

生产者和消费者可以利用等待和唤醒机制来实现线程的同步和协作。
生产者在缓冲区已满时等待,直到有可用空间;消费者在缓冲区为空时等待,直到有可供消费的数据。
当生产者生成新的数据或消费者消耗了数据时,它们可以相互通知和唤醒对方。

 综上所述,生产者-消费者模式是一种重要的多线程编程模式,它能够提高系统的并发性、吞吐量和效率,实现生产者和消费者之间的解耦和协作,确保数据交换和同步的正确性和可靠性。在并发编程和异步系统中广泛应用。

import java.util.LinkedList;class Producer implements Runnable {private LinkedList<Integer> buffer;private int maxSize;public Producer(LinkedList<Integer> buffer, int maxSize) {this.buffer = buffer;this.maxSize = maxSize;}@Overridepublic void run() {for (int i = 1; i <= 10; i++) {try {produce(i);} catch (InterruptedException e) {e.printStackTrace();}}}private void produce(int value) throws InterruptedException {synchronized (buffer) {while (buffer.size() == maxSize) {System.out.println("缓冲区已满,生产者等待...");buffer.wait();}buffer.add(value);System.out.println("生产者生产: " + value);buffer.notifyAll();}}
}class Consumer implements Runnable {private LinkedList<Integer> buffer;public Consumer(LinkedList<Integer> buffer) {this.buffer = buffer;}@Overridepublic void run() {while (true) {try {consume();} catch (InterruptedException e) {e.printStackTrace();}}}private void consume() throws InterruptedException {synchronized (buffer) {while (buffer.size() == 0) {System.out.println("缓冲区为空,消费者等待...");buffer.wait();}int value = buffer.removeFirst();System.out.println("消费者消费: " + value);buffer.notifyAll();}}
}public class ProducerConsumerExample {public static void main(String[] args) {LinkedList<Integer> buffer = new LinkedList<>();int maxSize = 5;Producer producer = new Producer(buffer, maxSize);Consumer consumer = new Consumer(buffer);Thread producerThread = new Thread(producer);Thread consumerThread = new Thread(consumer);producerThread.start();consumerThread.start();}
}

这个示例中,生产者线程通过 produce() 方法在 buffer 中生产数据,而消费者线程通过 consume() 方法从 buffer 中消费数据。其中,buffer 是一个共享的缓冲区,采用了等待和唤醒机制来实现线程的同步。

注意,在示例中使用了 LinkedList 作为缓冲区,但这只是一种示例使用的数据结构,实际上可以使用其他线程安全的数据结构,如 ArrayBlockingQueue 或 LinkedBlockingQueue 来实现更高效的生产者-消费者模式。

运行代码示例后,你可以观察到生产者逐个生成数据并放入缓冲区,而消费者逐个从缓冲区中取出数据消费,它们之间的执行是交替进行的。当缓冲区已满时,生产者线程会等待;当缓冲区为空时,消费者线程会等待。这样,生产者和消费者之间的数据交换和同步就实现了。

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/43323.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python NetworkX】图结构 图绘制

【Python NetworkX】图结构 & 图绘制 1. 简介 & 安装1.1 简介1.2 安装1.3 导入 2. 图2.1 无向图2.2 有向图2.3 重边无向图2.4 重边有向图2.5 图属性 3. 节点3.1 添加节点3.2 移除节点3.3 节点属性3.4 检查节点状态 4. 边4.1 添加边4.2 移除边4.3 边属性4.4 检查边状态 …

Kubernetes》k8s》Containerd 、ctr 、cri、crictl

containerd ctr crictl ctr 是 containerd 的一个客户端工具。 crictl 是 CRI 兼容的容器运行时命令行接口&#xff0c;可以使用它来检查和调试 k8s 节点上的容器运行时和应用程序。 ctr -v 输出的是 containerd 的版本&#xff0c; crictl -v 输出的是当前 k8s 的版本&#x…

【湖北工业大学2025年ACM校赛(同步赛)】题解

比赛链接 A. 蚂蚁上树 题目大意 给定一棵 n n n 个结点的树&#xff0c;根结点为 1 1 1。每个 叶结点 都有一只蚂蚁&#xff0c;每过 1 1 1 秒钟&#xff0c;你可以选一些蚂蚁往其 父结点 走一步&#xff0c;但是要求任意两只蚂蚁都不能在同一个 非根结点 上。 问至少要…

CS2 DEMO导入blender(慢慢更新咯)

流程&#xff1a;cs2-sourcefilmmaker-blender 工具&#xff1a;cs2tools&#xff0c;cs2manager&#xff0c;blender&#xff0c;blender插件sourceio&#xff0c;source2viewer 导入sfm 工具界面 选择这个 sourceio插件 sourceIO其中新版本导入相机路径不见了&#xff0c…

一周学会Flask3 Python Web开发-SQLAlchemy数据迁移migrate

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 模型类(表)不是一成不变的&#xff0c;当你添加了新的模型类&#xff0c;或是在模型类中添加了新的字段&#xff0c;甚至是修改…

Postman CORS 测试完全指南:轻松模拟跨域请求,排查 CORS 相关问题

在使用 Postman 进行 API 测试时&#xff0c;通常不会遇到跨域问题&#xff0c;因为 Postman 是一个独立的客户端应用程序&#xff0c;不同于在浏览器中运行的 JavaScript 代码&#xff0c;它没有同源策略&#xff08;SOP&#xff09;的限制。跨域资源共享&#xff08;CORS&…

【图像处理基石】什么是refocus?

1. Refocus 的定义 Refocus&#xff08;重新对焦&#xff09;是一种通过算法调整图像或视频焦点的技术&#xff0c;允许用户在拍摄后选择焦点&#xff0c;实现类似光场相机的“先拍照后对焦”效果。其核心是通过多视角信息或深度估计&#xff0c;生成不同焦平面的图像&#xff…

kettle从入门到精通 第九十三课 ETL之kettle kettle 调用web service接口5种方法,一文彻底搞懂

场景&#xff1a;群里有小伙伴向我求助如何调用web service接口&#xff0c;趁着周末时间&#xff0c;给兄弟们搞demo。 1、本次使用的web service服务接口地址是http://ws.webxml.com.cn/WebServices/WeatherWS.asmx?opgetSupportCityDataset&#xff0c; 此接口根据用户输入…

电子电气架构 --- 域控架构下,汽车连接器的挑战和变化

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 周末洗了一个澡,换了一身衣服,出了门却不知道去哪儿,不知道去找谁,漫无目的走着,大概这就是成年人最深的孤独吧! 旧人不知我近况,新人不知我过…

[MySQL] 库的操作 表的操作

1.库的操作 1.创建数据库 这里就是一个创建数据库的例子&#xff0c;框内的东西可以不填&#xff0c;因为有默认设置&#xff0c;而这些东西是什么呢&#xff1f; 2.字符集和校验规则 2.1查看字符集校验规则 show variables like ‘character_set_database’; show variable…

Let’s Encrypt 宣布推出短期证书与 IP 地址支持,推动 Web 安全迈向新高度

2025 年 1 月 16 日&#xff0c;全球领先的免费 SSL/TLS 证书颁发机构 Let’s Encrypt 正式宣布两项重大功能更新计划&#xff1a;推出六天有效期证书&#xff08;Short-Lived Certificates&#xff09;及支持以 IP 地址为主体的证书申请。两项功能将于 2025 年起陆续开放&…

十二、Cluster集群

目录 一、集群简介1、现状问题2、集群作用 二、集群结构设计1、集群存储设2、消息通信设计 三、Cluster集群三主三从结构搭建1、redis.conf配置文件可配置项2、配置集群3、链接集群4、命令客户端连接集群并使用 四、集群扩容1、添加节点2、槽位分配3、添加从节点 五、集群缩容1…

Linux进程管理之子进程的创建(fork函数)、子进程与线程的区别、fork函数的简单使用例子、子进程的典型应用场景、父进程等待子进程结束后自己再结束

收尾 进程终止&#xff1a;子进程通过exit()或_exit()终止&#xff0c;父进程通过wait()或waitpid()等待子进程终止&#xff0c;并获取其退出状态。&#xff1f;其实可以考虑在另一篇博文中来写 fork函数讲解 fork函数概述 fork() 是 Linux 中用于创建新进程的系统调用。当…

【AI论文】挑战推理的边界:大型语言模型的数学基准测试

摘要&#xff1a;近年来&#xff0c;大型推理模型的迅猛发展导致现有用于评估数学推理能力的基准测试趋于饱和&#xff0c;这凸显出迫切需要更具挑战性和严谨性的评估框架。为填补这一空白&#xff0c;我们推出了OlymMATH&#xff0c;这是一项全新的奥林匹克级数学基准测试&…

典范硬币系统(Canonical Coin System)→ 贪心算法

【典范硬币系统】 ● 典范硬币系统&#xff08;Canonical Coin System&#xff09;是指使用贪心算法总能得到最少硬币数量解‌的货币面值组合‌。 ● 给定一个硬币系统 &#xff0c;若使其为典范硬币系统&#xff0c;则要求其各相邻面值比例 &#xff0c;及各开区间 内各金额…

Android7 Input(二)Linux 驱动层输入事件管理

概述 在Linux系统中&#xff0c;将键盘&#xff0c;鼠标&#xff0c;触摸屏等这类交互设备交由Linux Input子系统进行管理&#xff0c;Linux Input驱动子系统由于具有良好的和用户空间交互的接口。因此Linux Input驱动子系统&#xff0c;不止于只管理输入类型的设备。也可以将其…

高清壁纸一站式获取:海量分类,免费无弹窗

软件介绍 在如今这个追求个性化与高品质视觉体验的时代&#xff0c;一款出色的壁纸应用无疑能为我们的电子设备增添别样魅力。此刻&#xff0c;要给大家重磅推荐的便是Wallpaper这款应用&#xff0c;它犹如一个绚丽多彩的壁纸宝库&#xff0c;全方位满足你的审美需求。 海量壁…

Linux安装Cmake (Centos 7.9)

cmake安装 这个虽然已经更新到了4.0.0版本了&#xff0c;但是我们要用3.5版本的&#xff0c;因为这个比较稳定 官方地址&#xff1a;https://github.com/Kitware/CMake/releases/tag/v3.5.0&#xff0c;选择那个cmake-3.5.0-Linux-x86_64.tar.gz下载&#xff0c; 首先解压文…

Centos7,tar包方式部署rabbitmq-3.7.6

1. 环境准备 安装编译工具和依赖包 yum -y install make gcc gcc-c glibc-devel m4 perl openssl openssl-devel ncurses-devel ncurses-devel xz xmlto perl 2. Erlang环境搭建 版本对应&#xff1a;https://www.rabbitmq.com/docs/which-erlang 解压到指定目录 tar -xv…

【MySQL篇】事务管理,事务的特性及深入理解隔离级别

目录 一&#xff0c;什么是事务 二&#xff0c;事务的版本支持 三&#xff0c;事务的提交方式 四&#xff0c;事务常见操作方式 五&#xff0c;隔离级别 1&#xff0c;理解隔离性 2&#xff0c;查看与设置隔离级别 3&#xff0c;读未提交&#xff08;read uncommitted&a…