线程同步:确保多线程环境中的数据一致性!

全文目录:

    • 开篇语
      • 前言
        • [35.1 同步代码块和同步方法](#35.1 同步代码块和同步方法)
          • [35.1.1 同步方法](#35.1.1 同步方法)
          • [35.1.2 同步代码块](#35.1.2 同步代码块)
        • [35.2 Lock 接口和 ReentrantLock](#35.2 Lock 接口和 ReentrantLock)
          • [35.2.1 Lock 接口](#35.2.1 Lock 接口)
          • [35.2.2 ReentrantLock](#35.2.2 ReentrantLock)
        • [35.3 读写锁(ReadWriteLock)](#35.3 读写锁(ReadWriteLock))
          • [35.3.1 读写锁的工作原理](#35.3.1 读写锁的工作原理)
        • [35.4 条件变量(Condition)](#35.4 条件变量(Condition))
          • [35.4.1 使用 `Condition` 的基本步骤](#35.4.1 使用 Condition 的基本步骤)
        • [35.5 volatile 关键字](#35.5 volatile 关键字)
          • [35.5.1 `volatile` 的使用场景](#35.5.1 volatile 的使用场景)
      • 小结
    • 文末

开篇语

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

在并发编程中,多个线程同时访问共享资源时,可能会导致数据不一致或线程安全问题。线程同步 (Thread Synchronization)是一种机制,确保在多线程环境下,多个线程对共享资源的访问是安全的。Java 提供了多种同步方式来保证线程之间的互斥和协作。今天,我们将深入探讨同步代码块、同步方法、Lock 接口、ReentrantLock、读写锁、条件变量以及 volatile 关键字。

35.1 同步代码块和同步方法

在 Java 中,可以通过 同步代码块同步方法 来确保对共享资源的访问是线程安全的。

35.1.1 同步方法

同步方法是通过 synchronized 关键字修饰的方法,它确保在同一时刻只能有一个线程执行该方法。这种方式是通过 锁定 当前对象来实现的。对于静态方法,锁定的是 类的字节码对象

java 复制代码
class Counter {
    private int count = 0;

    // 同步方法
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
        // 创建多个线程操作同一个对象
        Thread t1 = new Thread(() -> counter.increment());
        Thread t2 = new Thread(() -> counter.increment());

        t1.start();
        t2.start();
    }
}

在上面的例子中,increment() 方法是同步的,确保了多个线程不会同时修改 count,从而避免了线程安全问题。

35.1.2 同步代码块

同步代码块是通过 synchronized 关键字来修饰某个特定的代码块。它允许我们在方法中对某一部分代码进行同步,而不是整个方法。同步代码块通常用于提高程序的并发性能,因为它可以减少锁的粒度。

java 复制代码
class Counter {
    private int count = 0;

    public void increment() {
        synchronized (this) {  // 同步代码块
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

同步代码块允许我们选择性地同步某些部分,而不是整个方法,从而减少锁的竞争,提高程序的执行效率。

35.2 Lock 接口和 ReentrantLock

在 Java 中,synchronized 是一种内置的同步机制,但它存在一些限制,比如无法尝试获取锁、没有超时机制等。为了解决这些问题,Java 引入了 Lock 接口和 ReentrantLock 类。

35.2.1 Lock 接口

Lock 接口提供了一些比 synchronized 更灵活的锁机制。与 synchronized 相比,Lock 可以更加精确地控制锁的获取和释放。

  • lock():获取锁。
  • unlock():释放锁。
  • tryLock() :尝试获取锁,如果锁不可用,则返回 false
  • lockInterruptibly():可以响应中断的锁获取方法。
35.2.2 ReentrantLock

ReentrantLockLock 接口的一个实现类,支持可重入锁,即同一个线程可以多次获取该锁,避免了死锁的发生。它还支持公平锁和非公平锁。

java 复制代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Counter {
    private int count = 0;
    private Lock lock = new ReentrantLock();  // 使用ReentrantLock

    public void increment() {
        lock.lock();  // 获取锁
        try {
            count++;
        } finally {
            lock.unlock();  // 确保释放锁
        }
    }

    public int getCount() {
        return count;
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1 = new Thread(() -> counter.increment());
        Thread t2 = new Thread(() -> counter.increment());

        t1.start();
        t2.start();
    }
}

在上面的代码中,ReentrantLock 提供了更加细粒度的控制,我们可以通过调用 lock() 来获取锁,unlock() 来释放锁,确保多线程环境中的数据一致性。

35.3 读写锁(ReadWriteLock)

在多线程环境中,如果有多个线程频繁地读取共享资源,而写操作较少,使用 读写锁ReadWriteLock)可以提高程序的并发性能。读写锁允许多个线程同时读取资源,但写操作是互斥的。Java 提供了 ReadWriteLock 接口及其实现类 ReentrantReadWriteLock

35.3.1 读写锁的工作原理
  • 读锁:多个线程可以同时获取读锁。
  • 写锁:只有一个线程能获取写锁,且写锁期间没有任何线程可以获取读锁或写锁。
java 复制代码
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class Data {
    private int value = 0;
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        lock.readLock().lock();  // 获取读锁
        try {
            System.out.println("Read value: " + value);
        } finally {
            lock.readLock().unlock();  // 释放读锁
        }
    }

    public void write(int newValue) {
        lock.writeLock().lock();  // 获取写锁
        try {
            value = newValue;
            System.out.println("Written value: " + value);
        } finally {
            lock.writeLock().unlock();  // 释放写锁
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Data data = new Data();
        Thread t1 = new Thread(() -> data.read());
        Thread t2 = new Thread(() -> data.write(42));

        t1.start();
        t2.start();
    }
}

在上面的例子中,ReentrantReadWriteLock 实现了读写锁,使得多个线程可以并发地读取资源,而写入操作则是互斥的,从而提高了程序的并发性。

35.4 条件变量(Condition)

在多线程编程中,我们经常需要某些线程等待某些条件的发生。条件变量Condition)是通过 Lock 接口提供的机制,它允许线程在满足某个条件之前挂起,直到条件被其他线程满足并发出通知。

35.4.1 使用 Condition 的基本步骤
  1. 使用 Condition 对象来实现等待和通知机制。
  2. 线程通过 await() 方法等待条件的满足。
  3. 条件满足时,通过 signal()signalAll() 方法通知其他线程。
java 复制代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;

class SharedResource {
    private int data = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void produce() {
        lock.lock();
        try {
            while (data > 0) {
                condition.await();  // 等待消费者消费
            }
            data++;
            System.out.println("Produced data: " + data);
            condition.signalAll();  // 通知消费者
        } finally {
            lock.unlock();
        }
    }

    public void consume() {
        lock.lock();
        try {
            while (data == 0) {
                condition.await();  // 等待生产者生产
            }
            data--;
            System.out.println("Consumed data: " + data);
            condition.signalAll();  // 通知生产者
        } finally {
            lock.unlock();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();

        // 创建生产者线程
        Thread producer = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                resource.produce();
            }
        });

        // 创建消费者线程
        Thread consumer = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                resource.consume();
            }
        });

        producer.start();
        consumer.start();
    }
}

在上面的代码中,生产者和消费者使用 Condition 来协调工作,确保生产和消费按照特定顺序进行。

35.5 volatile 关键字

volatile 是 Java 中的一种轻量级的同步机制,它保证了变量的修改对其他线程可见。使用 volatile 修饰的变量不会被线程的本地缓存所缓存,每次读取该变量时都会直接从主内存中获取。

35.5.1 volatile 的使用场景

volatile 适用于简单的状态标志变量,确保线程间的可见性,但它并不提供原子性,因此不适用于需要复合操作的场景。

java 复制代码
class SharedFlag {
    private volatile boolean flag = false;

    public void setFlagTrue() {
        flag = true;
    }

    public boolean getFlag() {
        return flag;
    }
}

使用 volatile 可以确保变量的状态在多线程环境下正确同步,但如果需要保证复合操作的原子性,应该使用 synchronizedLock

小结

线程同步是确保多线程程序正确执行的关键。通过 同步代码块同步方法 ,我们可以控制线程的执行顺序,避免竞争条件。Lock 接口和 ReentrantLock 提供了比 synchronized 更细粒度的锁控制,读写锁允许我们在高并发环境下提高程序的性能,Condition 为线程间的协作提供了更加灵活的等待和通知机制,而 volatile 提供了一种轻量级的线程间通信机制。掌握这些同步机制,将帮助我们编写高效、可靠的多线程程序。

... ...

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

... ...

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。

⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

相关推荐
后端小张2 小时前
【JAVA进阶】Docker 2025完全指南:从容器入门到企业级实践
java·运维·开发语言·spring·docker·容器·springboot
Hui Baby2 小时前
K8S蓝绿发布
java·容器·kubernetes
盛世宏博北京2 小时前
弹药库房 “感知 - 传输 - 平台 - 应用” 四层架构温湿度监控方案
java·大数据·人工智能·弹药库房温湿度监控
深兰科技2 小时前
坦桑尼亚与新加坡代表团到访深兰科技,促进AI在多领域的应用落地
java·人工智能·typescript·scala·perl·ai大模型·深兰科技
a努力。2 小时前
阿里Java面试被问:如何分析Full GC的原因?jmap -histo和jmap -dump区别?
java·开发语言·后端·面试·架构
我笔记3 小时前
.net4和core的差异与iis部署差异
java
白宇横流学长4 小时前
基于SpringBoot实现的垃圾分类管理系统
java·spring boot·后端
45288655上山打老虎11 小时前
C++完美转发
java·jvm·c++
Seven9711 小时前
查找算法
java