7.1Java多线程安全和同步

示例:线程不安全的计数器

以下代码模拟多个线程同时对共享计数器进行自增操作:

public class ThreadUnsafeExample {
    private static int counter = 0; // 共享计数器

    public static void main(String[] args) throws InterruptedException {
        int threadCount = 100;
        Thread[] threads = new Thread[threadCount];

        // 创建并启动 100 个线程
        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter++; // 非线程安全操作
                }
            });
            threads[i].start();
        }

        // 等待所有线程完成
        for (Thread t : threads) {
            t.join();
        }

        // 预期结果应为 100,000,但实际输出通常小于该值
        System.out.println("Counter: " + counter); // 可能输出 99873、99952 等
    }
}

问题分析

1. 线程不安全的根本原因

counter++ 看似是原子操作,实际上包含三个步骤:

  1. 读取:从主内存读取 counter 的当前值到线程工作内存。
  2. 修改:在工作内存中对值加 1。
  3. 写入:将修改后的值刷新回主内存。
2. 可能的竞态条件

假设两个线程同时执行 counter++

  • 时间点 1:线程 A 读取 counter 值为 100。
  • 时间点 2:线程 B 读取 counter 值为 100(此时 A 尚未写入)。
  • 时间点 3:线程 A 加 1 后写入 101。
  • 时间点 4:线程 B 加 1 后写入 101(覆盖 A 的更新)。

最终结果:两次自增仅使 counter 增加 1,导致计数丢失。

3. 线程同步与安全

3.1 线程安全问题

当多个线程同时访问和修改共享资源时,可能会导致数据不一致或错误的结果,这就是线程安全问题。例如,多个线程同时对一个共享的计数器进行自增操作,可能会出现计数不准确的情况。

3.2 同步机制
  • synchronized 关键字
    • 同步方法:在方法声明中使用 synchronized 关键字,保证同一时刻只有一个线程能够访问该方法。
class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

  • 同步代码块:使用 synchronized 关键字修饰代码块,指定要同步的对象。
class Counter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

  • Lock 接口java.util.concurrent.locks 包中的 Lock 接口提供了更灵活的锁机制,如可重入锁 ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

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

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}

4. 线程间通信

4.1 wait()notify() 和 notifyAll() 方法

这三个方法是 Object 类的方法,用于实现线程间的通信。wait() 方法使当前线程进入等待状态,notify() 方法唤醒在此对象监视器上等待的单个线程,notifyAll() 方法唤醒在此对象监视器上等待的所有线程。

class SharedResource {
    private boolean flag = false;

    public synchronized void producer() throws InterruptedException {
        while (flag) {
            wait();
        }
        System.out.println("生产者生产数据");
        flag = true;
        notifyAll();
    }

    public synchronized void consumer() throws InterruptedException {
        while (!flag) {
            wait();
        }
        System.out.println("消费者消费数据");
        flag = false;
        notifyAll();
    }
}

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

        Thread producerThread = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    sharedResource.producer();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumerThread = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    sharedResource.consumer();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producerThread.start();
        consumerThread.start();
    }
}
4.2 await()signal() 和 signalAll() 方法

Lock 接口的 Condition 对象提供了 await()signal() 和 signalAll() 方法,功能类似于 wait()notify() 和 notifyAll() 方法,但使用起来更加灵活。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class SharedResourceWithLock {
    private boolean flag = false;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void producer() throws InterruptedException {
        lock.lock();
        try {
            while (flag) {
                condition.await();
            }
            System.out.println("生产者生产数据");
            flag = true;
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void consumer() throws InterruptedException {
        lock.lock();
        try {
            while (!flag) {
                condition.await();
            }
            System.out.println("消费者消费数据");
            flag = false;
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

5. 线程池

5.1 线程池的概念和优势

线程池是一种管理线程的机制,它预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完毕后线程不会销毁,而是返回线程池等待下一个任务。使用线程池的优势包括:

  • 减少线程创建和销毁的开销:避免了频繁创建和销毁线程带来的性能损耗。
  • 提高响应速度:任务提交后可以立即从线程池中获取线程执行,无需等待线程创建。
  • 便于线程管理:可以控制线程的数量、线程的生命周期等。
5.2 创建线程池

Java 提供了 ExecutorService 接口和 Executors 工具类来创建线程池。常见的线程池类型有:

  • 固定大小线程池Executors.newFixedThreadPool(int nThreads)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                System.out.println("任务 " + taskId + " 正在执行,线程名: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }
}

  • 单线程线程池Executors.newSingleThreadExecutor()
  • 缓存线程池Executors.newCachedThreadPool()
  • 定时任务线程池Executors.newScheduledThreadPool(int corePoolSize)

不过,从 Java 7 开始,更推荐使用 ThreadPoolExecutor 类来手动创建线程池,以避免使用 Executors 工具类可能带来的一些问题,如内存溢出等。

import java.util.concurrent.*;

public class ThreadPoolExecutorExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, // 核心线程数
                5, // 最大线程数
                60, // 线程空闲时间
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10) // 任务队列
        );

        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("任务 " + taskId + " 正在执行,线程名: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executor.shutdown();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chxii

小小打赏,大大鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值