自学内容网 自学内容网

Java核心技术【二十四】Java多线程同步:核心技术与实战指南

遇见即是缘分,关注🌟、点赞👍、收藏📚,让这份美好延续下去!
🌟 晋升管理 请通过文末二维码,关注 技术管理修行 ,定期分享技术管理知识与经验

在现代软件开发中,多线程编程是一个重要的概念,尤其在处理并发任务、提高程序性能和响应速度方面。然而,多线程编程也引入了新的复杂性,比如数据共享和线程安全问题。本文将深入探讨Java中的多线程同步技术,通过示例代码详细讲解关键概念,并探讨实际应用场景。

一、多线程同步基础

1. 线程安全问题

在多线程环境中,当多个线程访问共享资源时,可能会产生线程安全问题。例如,两个线程同时修改同一个共享变量,可能会导致数据不一致或数据损坏。

2. 同步机制

Java提供了多种同步机制,用于解决线程安全问题。主要机制包括:

  • synchronized关键字:可以用于方法或代码块,确保同一时刻只有一个线程可以执行某个方法或代码块。
  • volatile关键字:确保变量的可见性和顺序性,但不保证原子性。
  • 重入锁(ReentrantLock):比synchronized更灵活的锁机制,支持公平锁和非公平锁。
  • 信号量(Semaphore):用于控制对共享资源的访问数量。
  • 其他并发工具:如CountDownLatch、CyclicBarrier、Exchanger等。

二、synchronized关键字

1. synchronized方法

synchronized可以修饰非静态方法、静态方法和代码块。修饰非静态方法时,锁定的是调用该方法的对象;修饰静态方法时,锁定的是该类的Class对象;修饰代码块时,锁定的是括号里配置的对象。

1)示例代码:

public class Counter {  
    private int count = 0;  
  
    public synchronized void increment() {  
        count++; // 增加操作,需要同步  
    }  
  
    public synchronized int getCount() {  
        return count;  
    }  
}

2)测试代码:

package java_core_24;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test24 {

    private static final int THREAD_COUNT = 100; // 线程数量
    private static final int INCREMENT_COUNT = 1000; // 每个线程执行的次数

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

        for (int i = 0; i < THREAD_COUNT; i++) {
            executor.submit(() -> {
                for (int j = 0; j < INCREMENT_COUNT; j++) {
                    counter.increment();
                }
                latch.countDown(); // 计数器减一
            });
        }

        latch.await(); // 等待所有线程执行完毕
        executor.shutdown();

        int expectedCount = THREAD_COUNT * INCREMENT_COUNT;
        int actualCount = counter.getCount();

        System.out.println("Expected count: " + expectedCount);
        System.out.println("Actual count: " + actualCount);

        if (actualCount == expectedCount) {
            System.out.println("Test passed: Synchronization works correctly.");
        } else {
            System.out.println("Test failed: Synchronization does not work correctly.");
        }
    }
}

在这个测试中,我们在 Counter 类中的 increment()getCount() 方法都正确地使用了 synchronized 关键字。如果 synchronized 关键字没有正确地实现同步,那么多个线程同时执行 increment() 方法时,计数器的最终值很可能会小于预期值。如果测试通过,说明 synchronized 关键字有效地保护了对共享资源的访问。

3)测试结果:

使用 synchronized 关键字声明后的结果:

Expected count: 100000
Actual count: 100000
Test passed: Synchronization works correctly.

去掉synchronized 关键字声明后的结果:

Expected count: 100000
Actual count: 88162
Test failed: Synchronization does not work correctly.

2. synchronized代码块

在某些情况下,可能只需要对方法中的部分代码进行同步,这时可以使用synchronized代码块。

public void updateObject() {  
    synchronized (this) {  
        // 只有这部分代码是同步的  
        // 修改共享资源  
    }  
    // 其他非同步代码  
}

synchronized 代码块中,括号里配置的对象是锁对象。当多个线程访问同一个锁对象的 synchronized 代码块时,这些线程会被阻塞,直到锁被释放。

三、volatile关键字

volatile关键字用于确保变量的可见性和顺序性,但不保证原子性。当一个变量被volatile修饰时,任何线程对该变量的修改都会立即被其他线程看到。

1. 示例代码:

public class FlagRunner implements Runnable {  
    private volatile boolean flag = false;  
  
    public void run() {  
        int i = 0;  
        while (!flag) {  
            i++;  
        }  
        System.out.println("Stopped");  
    }  
  
    public void stopRunning() {  
        flag = true;  
    }  
}

在这个例子中,flag 变量被volatile修饰,确保在 stopRunning() 方法中修改 flag 的值时,其他线程能够立即看到这一变化。

2. 测试代码:

为了测试 FlagRunner 类中的 volatile 关键字是否正确地实现了线程间的可见性,我们需要编写一个测试方法来模拟多线程环境下的行为。在这个测试中,我们将创建一个 FlagRunner 实例,并在一个线程中运行它,同时在另一个线程中改变 flag 的值。这样我们就可以验证当 flag 被修改时,线程是否能够正确地检测到这一变化。下面是具体的测试代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FlagRunnerTest {

    public static void main(String[] args) throws InterruptedException {
        FlagRunner flagRunner = new FlagRunner();
        
        // 创建一个线程池来执行线程
        ExecutorService executor = Executors.newSingleThreadExecutor();

        // 提交一个线程来运行 FlagRunner
        executor.submit(flagRunner::run);

        // 等待一段时间,确保 FlagRunner 的线程已经开始运行
        Thread.sleep(1000);

        // 停止 FlagRunner 的线程
        flagRunner.stopRunning();

        // 关闭线程池
        executor.shutdown();
    }
}

3. 测试结果:

线程感知到了 flag 值的变化,同时打印了如下信息:

Stopped

四、重入锁(ReentrantLock)

ReentrantLock提供了比synchronized更灵活的锁机制。它支持公平锁和非公平锁,还支持条件变量(Condition)。

import java.util.concurrent.locks.ReentrantLock;  
  
public class Counter {  
    private final ReentrantLock lock = new ReentrantLock();  
    private int count = 0;  
  
    public void increment() {  
        lock.lock();  
        try {  
            count++;  
        } finally {  
            lock.unlock();  
        }  
    }  
  
    public int getCount() {  
        lock.lock();  
        try {  
            return count;  
        } finally {  
            lock.unlock();  
        }  
    }  
}

在这个例子中,使用 ReentrantLock 来确保线程安全。与 synchronized 不同,使用 ReentrantLock 时必须在 finally 块中释放锁,以避免死锁。

五、总结

多线程同步技术是并发编程中的重要概念,Java提供了多种同步机制来解决线程安全问题。synchronized 关键字可以用于方法或代码块,确保同一时刻只有一个线程可以执行特定代码。volatile 关键字用于确保变量的可见性和顺序性,但不保证原子性。ReentrantLock 提供了比 synchronized 更灵活的锁机制,支持公平锁和非公平锁,还支持条件变量。

在实际应用中,多线程同步技术广泛应用于线程安全的计数器、单例模式和队列等场景。通过合理使用这些同步机制,可以确保多线程程序的正确性和性能。


原文地址:https://blog.csdn.net/wcblog/article/details/140820664

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!