文章目录
一、认识程序、进程和线程
1. 程序
程序是一组计算机指令的集合,它是一个静态的概念,存储在磁盘等存储介质上。比如,你编写的一个 C++ 程序代码文件(.cpp 文件)或者 Java 程序的代码文件(.java 文件),这些文件包含了程序员编写的一系列指令,用于完成特定的任务,如数据处理、图像显示等。
2. 进程
进程是程序在计算机中的一次执行过程,是操作系统分配资源和调度的基本单位。当你双击一个程序图标,操作系统就会为这个程序创建一个进程,这个进程会占用一定的系统资源(如内存、CPU 时间等)来执行程序中的指令。
程序是静态的代码,而进程是动态的活动。程序就像剧本,进程则是按照剧本进行的一场演出。
3. 线程
线程是进程内部的一个执行单元,是 CPU 调度和执行的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源。
- 可以把进程比作一个工厂。一个工厂(进程)有自己独立的资源,如厂房(内存空间)、设备(文件句柄、网络端口等资源),并且有自己完整的一套管理体系(进程控制块)来维持工厂的正常运转。
- 线程则像是工厂里的工人。多个工人(线程)在这个工厂(进程)里工作,他们共享工厂的资源。例如,所有工人都可以使用工厂里的工具和设备,就像线程共享进程的内存空间和文件句柄一样。
- 工厂(进程)的主要职责是提供一个工作环境和资源,让工人(线程)能够进行生产活动(执行任务)。而且,就像一个工厂可以有多个不同分工的工人一样,一个进程也可以有多个执行不同任务的线程。比如,一个工厂里有负责生产零件的工人、负责组装的工人、负责质检的工人,在一个软件进程中,可能有一个线程负责数据输入、一个线程负责数据处理、一个线程负责数据输出。
4. 并发和并行
- 并发是在宏观上同时执行,微观上(实际上)交替执行
- 并行是在同一时刻真正地同时执行多个任务
二、多线程的实现方式
1. 继承Thread类【最简单,但扩展性差】
继承Thread类,重写 run()
方法,创建Thread子类对象,调用start方法,即可实现多线程
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(i);
}
}
}
public class Main
{
public static void main( String[] args )
{
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
// mt1.run();
// mt2.run();
// 注释的部分执行结果是mt1先执行完run方法后,mt2再执行run方法,不会交替执行
mt1.start();
mt2.start();
}
}
run()方法和start()方法的区别?
- run() 方法封装线程执行的代码,直接调用,相当于普通方法的调用
- start()方法启动线程,然后由JVM调用此线程的run()方法
2. 实现Runnable接口【扩展性好,可以继承其他类】
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" is running");
}
}
}
public class Main
{
public static void main( String[] args )
{
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
//...
// Thread-0 is running
// Thread-1 is running
// Thread-0 is running
// Thread-0 is running
// Thread-1 is running
// Thread-0 is running
// Thread-1 is running
// Thread-1 is running
可以给线程起名字
public class Main
{
public static void main( String[] args )
{
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"线程1");
Thread t2 = new Thread(mr,"线程2");
t1.start();
t2.start();
}
}
//...
// 线程1 is running
// 线程1 is running
// 线程2 is running
// 线程2 is running
// 线程2 is running
3. 实现Callable接口【可以拿到线程执行结果】
Callable<String>
里的泛型代表返回结果的类型,
FutureTask是 Java 中java.util.concurrent包下的一个类。它实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable和Future接口。这意味着
FutureTask对象既可以作为一个Runnable被线程执行
,又可以作为一个Future来获取异步计算的结果。
- 写好线程中要做的事(重写 Callable 接口的 call() 方法)
- 获取一个 Runnable 对象,FutureTask< String > ft = new FutureTask<>(mc);
- 接下来像第二种方式一样就好了,只不过第二种没办法获取线程执行结果
- 如果想获取执行结果,就用 ft.get() 来接收
第 2 步描述的不准确,但这样有助于理解和记忆,就当线程就两种创建方式,一是直接继承 Thread 的子类,二是给 Thread 类一个Runnable 对象和一个线程名字【可选】
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+": "+i);
}
return "OK";
}
}
public class Main
{
public static void main( String[] args ) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<String> ft = new FutureTask<>(mc);
Thread t = new Thread(ft);
t.start();
String s = ft.get();
System.out.println(s);
}
}
//...
//Thread-0: 97
//Thread-0: 98
//Thread-0: 99
//OK
4. 设置和获取线程名称
方法 | 作用 |
---|---|
void setName(String name) | 将线程名称设置为name |
String getName() | 返回此线程的名称 |
Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
public class MyThread extends Thread {
@Override
public void run()
{
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"你好");
}
}
}
public class Main
{
public static void main( String[] args ) throws ExecutionException, InterruptedException {
MyThread t1= new MyThread();
MyThread t2= new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
System.out.println("当前线程:"+Thread.currentThread().getName());
}
}
// 线程2你好
// 线程1你好
// 线程2你好
// 当前线程:main
// 线程2你好
// 线程2你好
// 线程1你好
// 线程1你好
// 线程1你好
5. 线程休眠
方法 | 作用 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
需要在哪休眠就在哪写 Thread.sleep() ,main方法中也可以,因为 main 方法也是一个线程
public class MyThread extends Thread {
@Override
public void run()
{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"你好");
}
}
}
6. 线程的优先级
Java使用的是抢占式调度模型,线程之间谁先抢到CPU谁就用,如果设置优先级高一些的话,抢到的概率会更大
方法 | 作用 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改此线程的优先级线,程默认优先级是5;线程优先级的范围是:1-10 |
public class Main
{
public static void main( String[] args ) throws ExecutionException, InterruptedException {
MyThread t1= new MyThread();
MyThread t2= new MyThread();
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println("当前线程:"+Thread.currentThread().getName());
}
}
//5
//5
//当前线程:main
public class Main
{
public static void main( String[] args ) throws ExecutionException, InterruptedException {
MyThread t1= new MyThread();
MyThread t2= new MyThread();
t1.setPriority(Thread.MAX_PRIORITY);
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println("当前线程:"+Thread.currentThread().getName());
}
}
//10
//5
//当前线程:main
7. 守护线程
方法 | 作用 |
---|---|
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
可以看到当主线程运行完之后,守护线程无论有没有运行完,都会退出
public class MyThread1 extends Thread {
@Override
public void run()
{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
public class MyThread2 extends Thread{
public void run()
{
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
public class Main
{
public static void main( String[] args ) throws ExecutionException, InterruptedException {
MyThread1 t1= new MyThread1();
MyThread1 t2= new MyThread1();
t1.setName("主线程");
t2.setName("守护线程");
t2.setDaemon(true);
t1.start();
t2.start();
}
}
//控制台
主线程0
主线程1
主线程2
主线程3
主线程4
主线程5
守护线程0
主线程6
守护线程1
主线程7
守护线程2
守护线程3
守护线程4
守护线程5
守护线程6
主线程8
主线程9
守护线程7
守护线程8
守护线程9
三、线程同步
1. 卖票问题
三个窗口卖100张票,以下代码会出现什么问题?
- 可能会卖重复票
如果【线程1】在执行完if (count <= 0) break
之后被【线程2】抢走cpu控制权,【线程2】执行完System.out.println(Thread.currentThread().getName() + "正在卖第" + count + "张票")
还没来得及减去票数,就又被【线程1】抢走cpu,【线程1】紧接着也执行打印票数的代码,这时候就重复卖票了 - 可能会超卖
假设还有最后一张票,【线程1】进来了,刚打印完正在卖第1张票,【线程2】抢走了cpu,刚执行完if (count <= 0) break
判定cout还是大于0的,又被【线程3】抢走了cpu,【线程3】也是刚执行完if (count <= 0) break
判定cout还是大于0的,这时候又被【线程1】抢走了,最后【线程1】执行count--
,被【线程2】抢走了cpu,【线程2】打印正在卖第0张票,然后对count -1 ,此时count已经为-1,被【线程3】抢走,打印正在卖第-1张票
public class MyRunnable implements Runnable {
private int count = 100;
@Override
public void run() {
while(true){
if (count <= 0) break;
System.out.println(Thread.currentThread().getName() + "正在卖第" + count + "张票");
count--;
}
}
}
public class Main
{
public static void main( String[] args ) throws ExecutionException, InterruptedException {
MyRunnable mr= new MyRunnable();
Thread t1= new Thread(mr,"窗口1");
Thread t2= new Thread(mr,"窗口2");
Thread t3= new Thread(mr,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
2. synchronized 关键字
1. 同步代码块
安全问题出现的条件
-
是多线程环境
-
有共享数据
-
有多条语句操作共享数据
使用同步代码块对卖票操作加锁
synchronized(任意对象) {
多条语句操作共享数据的代码
}
public class MyRunnable implements Runnable {
private int count = 100;
@Override
public void run() {
while(true){
synchronized (MyRunnable.class){
if (count <= 0) break;
System.out.println(Thread.currentThread().getName() + "正在卖第" + count + "张票");
count--;
}
}
}
}
synchronized(任意对象),这里的任意对象要是唯一的,比如如果我们是继承Thread类实现的多线程,就不能写synchronized ( this ),因为每次this都是不一样的对象,但是用实现Runnable接口方式实现的多线程,可以写this,因为MyRunnable对象我们在main函数就创建了一个,一般就写当前类的字节码对象就ok了
2. 同步方法
如果一个方法里面全是同步代码块,不如定义成同步方法了
- 同步方法的锁对象是this
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
- 同步静态方法的锁对象是类名.class
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
public class MyRunnable implements Runnable {
private int count = 100;
@Override
public void run() {
while (true) {
boolean sellOut = sell();
if (sellOut)
break;
}
}
public synchronized boolean sell() {
if (count <= 0)
return true;
System.out.println(Thread.currentThread().getName() + "正在卖第" + count + "张票");
count--;
return count<=0;
}
}
3. Lock锁
实例化锁对象:private ReentrantLock lock = new ReentrantLock()
上锁:lock()
解锁:unlock()
public class MyRunnable implements Runnable {
private int count = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();
if (count <= 0) break;
System.out.println(Thread.currentThread().getName() + "正在卖第" + count + "张票");
count--;
lock.unlock();
}
}
}
4. 死锁问题【补充】
当两个或多个线程被阻塞,每个线程都在等待其他线程释放它所需要的资源,并且这种等待关系形成了一个环形等待链时,就会发生死锁。
1. 死锁产生的原因
- 互斥条件
线程同步中使用的许多资源(如锁)具有互斥性。这意味着在同一时刻,一个资源只能被一个线程所占有。 - 请求与保持条件
一个线程在持有一个资源的同时又请求其他资源。 - 不可剥夺条件
一个线程所获得的资源在它自己没有主动释放之前,其他线程是不能强行剥夺的。 - 循环等待条件
这是死锁的一个关键特征,也是线程同步过程中资源请求顺序不合理导致的结果。当多个线程之间形成了一个环形的资源等待关系时,就会发生死锁。
2. 解决死锁
- 资源有序分配策略
在多个线程需要访问多个资源的情况下,对资源进行排序,然后要求所有线程按照相同的顺序请求资源。例如,对资源进行编号,如果所有线程都按照资源编号从小到大的顺序请求资源,就可以避免循环等待的情况,从而防止死锁。 - 银行家算法(资源分配算法)
这是一种用于检测和避免死锁的算法。它通过模拟银行系统的资源分配过程,在每次资源分配前,判断这种分配是否会导致系统进入不安全状态(可能导致死锁的状态)。如果会导致不安全状态,就不进行资源分配。这种算法需要对线程的资源请求和现有资源情况进行详细的记录和分析,也是基于线程同步过程中的资源分配机制来实施的。
四、等待唤醒机制
等待唤醒机制是线程同步的一种实现方式,以生产者、消费者模型和阻塞队列为例来讲解等待唤醒机制
1.生产者、消费者模型
假设有2个厨师,2个吃货,1张桌子。同一时刻桌子只能由一个厨师或者一个吃货使用,吃货们总共可以吃100碗鸡汤,厨师们是吃货的守护进程,吃货在吃的时候,厨师在争取把鸡汤做好端上桌,吃货吃完之后厨师也不做了,要保证桌子上有鸡汤吃货才可以吃,如果桌子被吃货占了,但是没有鸡汤,吃货要唤醒厨师去做鸡汤
public class Desk{
public static int count = 100;
public static int flag = 0;
public static Object lock =new Object();
}
public class Cook implements Runnable{
@Override
public void run() {
while (true){
synchronized (Desk.lock){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+":鸡汤来喽");
Desk.flag++;
Desk.lock.notifyAll();
}
}
}
}
public class Eat implements Runnable{
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count==0)
break;
if(Desk.flag==0){
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
Desk.count--;
Desk.flag--;
System.out.println(Thread.currentThread().getName()+"正在吃,桌子上还剩"+Desk.flag+"碗,吃货们总共还能吃"+Desk.count+"碗");
}
}
}
}
}
public class Main
{
public static void main( String[] args ) throws ExecutionException, InterruptedException {
Cook cook = new Cook();
Eat eat = new Eat();
Thread cook1 = new Thread(cook);
Thread cook2 = new Thread(cook);
Thread eat1 = new Thread(eat);
Thread eat2 = new Thread(eat);
cook1.setName("厨师1");
cook2.setName("厨师2");
eat1.setName("吃货1");
eat2.setName("吃货2");
cook1.setDaemon(true);
cook2.setDaemon(true);
eat1.start();
eat2.start();
cook1.start();
cook2.start();
}
}
2. 阻塞队列
这里没有加打印语句,因为打印语句其实是在锁的外面,在打印信息时容易造成代码写错了的假象,理解一下意思即可
在多线程环境下,ArrayBlockingQueue提供了线程安全的队列操作。它内部使用了锁(ReentrantLock)来确保多个线程在对队列进行添加(put方法)、删除(take方法)等操作时的安全性。
在没有锁的情况下,当多个线程同时调用put方法向ArrayBlockingQueue添加元素时,可能会出现数据覆盖的情况。例如,假设队列容量为 5,已经有 4 个元素,两个线程同时检测到队列未满并尝试添加元素。如果没有锁来协调,可能会出现一个元素被另一个元素覆盖的情况,导致数据丢失。
在生产者 - 消费者场景中,ArrayBlockingQueue的阻塞功能非常有用。当生产者生产数据的速度快于消费者消费数据的速度时,如果队列已满,生产者线程会在调用put方法时被阻塞,直到消费者从队列中取出一些元素,为队列腾出空间。
public class Cook extends Thread{
private ArrayBlockingQueue<String> bd;
public Cook(ArrayBlockingQueue<String> bd){
this.bd = bd;
}
@Override
public void run(){
while (true){
try {
Thread.sleep(100);
bd.put("大碗鸡汤");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Eat extends Thread{
private ArrayBlockingQueue<String> bd;
private int count = 10;
public Eat(ArrayBlockingQueue<String> bd)
{
this.bd = bd;
}
public void run()
{
while(true)
{
if(count<=0)
break;
try {
bd.take();
count--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main
{
public static void main( String[] args ) throws ExecutionException, InterruptedException {
ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<String>(10);
Cook cook = new Cook(bd);
Eat eat = new Eat(bd);
cook.setName("cook");
eat.setName("eat");
cook.setDaemon(true);
cook.start();
eat.start();
}
}
五、线程池
1. 创建线程池
方法 | 作用 |
---|---|
static ExecutorService newCachedThreadPool() | 创建一个默认的线程池 |
static newFixedThreadPool(int nThreads) | 创建一个指定最多线程数量的线程池 |
这两个方法在底层创建的都是 ThreadPoolExecutor 的对象
【补充】submit 函数接收的参数类型
(1)newCachedThreadPool ( )
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Main
{
public static void main( String[] args ) {
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
//如果是在服务器上运行的程序,线程池一般不销毁
//threadPool.shutdown();
}
}
// 控制台
pool-1-thread-1
pool-1-thread-5
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-6
由于池子是无上线的,如果想看到复用线程的效果,可以让每个线程执行前睡一段时间,归还线程之后就能看到复用效果了
public class Main
{
public static void main( String[] args ) throws InterruptedException {
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.submit(new MyRunnable());
Thread.sleep(1000);
threadPool.submit(new MyRunnable());
Thread.sleep(1000);
threadPool.submit(new MyRunnable());
Thread.sleep(1000);
threadPool.submit(new MyRunnable());
Thread.sleep(1000);
threadPool.submit(new MyRunnable());
Thread.sleep(1000);
threadPool.submit(new MyRunnable());
threadPool.shutdown();
}
}
// 控制台
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
(2)newFixedThreadPool (int nThreads)
可以指定线程池里的最大线程数
public class Main
{
public static void main( String[] args ) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
threadPool.submit(new MyRunnable());
threadPool.shutdown();
}
}
// 控制台
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-1
2. 自定义线程池
1. 核心线程和临时线程
核心线程即使没有任务也不会销毁,临时线程没有任务时过一段时间会销毁
2. 什么时候创建临时线程【易误解】
核心线程全部在忙,并且等待队伍已满,这时候再来新任务才会创建临时线程
自己设置这些参数,就能创建一个线程池了,还是用submit 提交任务,和之前一样
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 时间单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null
六、volatile
看下面一个案例
public class Value {
public static int value = 1;
}
public class MyThread1 extends Thread {
@Override
public void run() {
while(Value.value == 1){
}
System.out.println("value值已改变");
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Value.value = 0;
}
}
public class Main
{
public static void main( String[] args ) throws InterruptedException {
MyThread1 t1 = new MyThread1();
t1.setName("线程1");
t1.start();
MyThread2 t2 = new MyThread2();
t2.setName("线程2");
t2.start();
}
}
运行之后,【线程1】 将一直卡在while循环
因为【线程1】的 Value.value是【线程1】本地缓存中的旧值,而不是从主内存中读取最新的值
- 在 Java 中,volatile是一个关键字,用于修饰变量。它主要用于确保变量在多个线程之间的可见性。
- 当一个变量被声明为volatile时,Java 虚拟机(JVM)会保证这个变量的值在一个线程中被修改后,会立即更新到主内存中。并且,其他线程在读取这个变量时,会直接从主内存中读取最新的值,而不是使用线程本地缓存中的旧值。
public class Value {
//加上volatile关键字即可解决这个问题
public static volatile int value = 1;
}
//执行main函数---控制台
//value值已改变
//Process finished with exit code 0
1. 缓存刷新时机
- 同步操作(synchronized 关键字)时刷新
- 当一个线程进入一个synchronized块或者方法时,它会清空自己工作内存中共享变量的值,然后从主内存重新读取这些共享变量的值。
- 当线程退出synchronized块或者方法时,它会将自己工作内存中修改后的共享变量的值刷新到主内存中。
- 显式使用锁(如 ReentrantLock)的 lock、unlock 操作时刷新
- 线程结束时刷新
- volatile 变量赋值后刷新
public class Value {
public static int value = 1;
}
public class MyThread1 extends Thread {
@Override
public void run() {
while(true){
synchronized (MyThread1.class){
if(Value.value!=1){
System.out.println("value值已改变");
break;
}
}
}
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Value.value = 0;
}
}
public class Main
{
public static void main( String[] args ) throws InterruptedException {
MyThread1 t1 = new MyThread1();
t1.setName("线程1");
t1.start();
MyThread2 t2 = new MyThread2();
t2.setName("线程2");
t2.start();
}
}
七、悲观锁和乐观锁
1.悲观锁
悲观锁是一种并发控制机制,它假设在数据处理过程中很可能会发生冲突,所以在操作数据之前就先对数据进行加锁。这就好比一个人进入一个房间后,立刻把门锁上,防止其他人进入,直到自己完成任务离开房间才开锁。
在编程语言层面,像 Java 中的synchronized关键字和ReentrantLock等机制也可以看作是悲观锁的实现。以synchronized为例,当一个方法被synchronized修饰时,在同一时间只有一个线程可以访问这个方法所对应的代码块,其他线程需要等待锁的释放。
2.乐观锁
乐观锁则是假设在数据处理过程中不会发生冲突或者发生冲突的概率很低。它不会在操作数据之前加锁,而是在更新数据的时候检查数据是否被其他操作修改过。如果没有被修改,就正常更新;如果被修改了,就根据具体的业务逻辑来决定是重试还是抛出异常。这就好像一个人进入房间后,并不锁门,在离开房间时才检查房间里的东西是否被别人动过。
CAS (Compare - And - Swap)是一种用于实现并发控制的乐观锁技术。CAS 操作包含三个操作数:内存位置(V)、旧的预期值(A)和新值(B)。在执行 CAS 操作时,处理器会比较内存位置 V 的值是否等于预期值 A,如果相等,则将内存位置 V 的值更新为新值 B;如果不相等,则说明该内存位置的值已经被其他线程修改过了,不会进行更新操作。
标签:25,多线程,Thread,void,class,线程,new,最新版,public From: https://blog.csdn.net/fim77/article/details/143956047
- 以 Java 中的AtomicInteger类为例来解释 CAS 的工作原理。AtomicInteger是一个原子类,它提供了原子性的整数操作。假设我们有一个AtomicInteger对象atomicInt,初始值为 5。
- 当一个线程想要将atomicInt的值增加 1 时,它首先会获取atomicInt的当前值(假设这个线程读取到的值为 5),这就是预期值 A。然后线程会计算出增加后的新值 B(在这里是 6)。接着线程会执行 CAS 操作,比较atomicInt的当前实际值(内存位置V)和预期值 A(5)。如果相等,就将atomicInt的值更新为新值B(6);如果不相等,说明有其他线程已经修改了atomicInt的值,此时这个线程就不会进行更新操作,可能会重试或者采取其他策略。