赞
踩
引言
在现代软件开发中,多线程并发编程是提升系统性能和效率的关键技术之一。Java语言自1.5版本开始引入了强大的并发工具类库,并提供了丰富的API来支持多线程编程。本文将深入探讨Java中的多线程并发编程概念、原理以及实战技巧。
在Java中,创建线程主要有两种方式:
1. 继承Thread类
示例代码:
- public class MyThread extends Thread {
- private String threadName;
-
- public MyThread(String name) {
- this.threadName = name;
- }
-
- @Override
- public void run() {
- // 在这里定义线程执行的任务
- System.out.println("线程 " + threadName + " 正在运行...");
- performTask();
- }
-
- private void performTask() {
- // 具体的线程任务逻辑
- for (int i = 0; i < 10; i++) {
- System.out.println(threadName + ": 执行任务 - " + i);
- }
- }
-
- public static void main(String[] args) {
- MyThread thread1 = new MyThread("线程1");
- MyThread thread2 = new MyThread("线程2");
-
- thread1.start(); // 启动线程1
- thread2.start(); // 启动线程2
- }
- }
详细解释:
2. 实现Runnable接口
示例代码:
- public class RunnableTask implements Runnable {
- private String taskName;
-
- public RunnableTask(String name) {
- this.taskName = name;
- }
-
- @Override
- public void run() {
- // 线程执行的任务
- System.out.println("任务 " + taskName + " 正在运行...");
- executeTask();
- }
-
- private void executeTask() {
- for (int i = 0; i < 10; i++) {
- System.out.println(taskName + ": 执行任务 - " + i);
- }
- }
-
- public static void main(String[] args) {
- RunnableTask task1 = new RunnableTask("任务1");
- RunnableTask task2 = new RunnableTask("任务2");
-
- Thread thread1 = new Thread(task1);
- Thread thread2 = new Thread(task2);
-
- thread1.start();
- thread2.start();
- }
- }
详细解释:
Java多线程并发编程中,线程同步是一种控制多个线程对共享资源的访问,以确保线程安全的方法。下面通过一个示例代码来详细解释线程同步的使用。
假设有一个银行账户类BankAccount,其中包含一个共享资源(账户余额),需要多个线程进行操作(存款和取款)。为了确保线程安全,我们需要对共享资源进行同步控制。
- public class BankAccount {
- private int balance;
-
- public BankAccount(int initialBalance) {
- this.balance = initialBalance;
- }
-
- // 存款方法
- public synchronized void deposit(int amount) {
- this.balance += amount;
- System.out.println(Thread.currentThread().getName() + " 存款成功,当前余额:" + this.balance);
- }
-
- // 取款方法
- public synchronized void withdraw(int amount) {
- if (this.balance >= amount) {
- this.balance -= amount;
- System.out.println(Thread.currentThread().getName() + " 取款成功,当前余额:" + this.balance);
- } else {
- System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
- }
- }
- }
在上述示例代码中,我们通过在deposit()和withdraw()方法上添加synchronized关键字来实现线程同步。这意味着在同一时刻,只有一个线程可以执行这两个方法中的任意一个。
接下来,我们创建一个测试类BankAccountTest,用于模拟多个线程对账户进行并发操作。
- public class BankAccountTest {
- public static void main(String[] args) {
- BankAccount account = new BankAccount(1000);
-
- Thread t1 = new Thread(() -> account.deposit(500));
- Thread t2 = new Thread(() -> account.withdraw(200));
- Thread t3 = new Thread(() -> account.withdraw(300));
-
- t1.start();
- t2.start();
- t3.start();
- }
- }
在这个测试类中,我们创建了一个BankAccount对象,并启动了三个线程,分别执行存款、取款操作。由于我们在线程同步中使用了synchronized关键字,因此这些操作将会按顺序执行,确保了线程安全。
Java中的等待/通知机制是线程间通信的一种方式,通过调用Object类的wait()、notify()和notifyAll()方法来实现。下面是一个使用这些方法进行线程同步的示例代码,并对其进行详细解释
- public class BoundedBuffer {
- private final Object lock = new Object();
- private final int capacity;
- private int count = 0;
- private int in = 0, out = 0;
- private final Object[] buffer;
-
- public BoundedBuffer(int capacity) {
- this.capacity = capacity;
- this.buffer = new Object[capacity];
- }
-
- // 生产者放入元素
- public void put(Object item) throws InterruptedException {
- synchronized (lock) {
- while (count == capacity) { // 如果缓冲区已满
- lock.wait(); // 生产者线程进入等待状态
- }
-
- buffer[in] = item; // 将元素放入缓冲区
- in = (in + 1) % capacity; // 更新入指针
- count++; // 增加计数
-
- lock.notifyAll(); // 唤醒所有等待的消费者线程
- }
- }
-
- // 消费者取出元素
- public Object take() throws InterruptedException {
- synchronized (lock) {
- while (count == 0) { // 如果缓冲区为空
- lock.wait(); // 消费者线程进入等待状态
- }
-
- Object item = buffer[out]; // 取出缓冲区中的元素
- buffer[out] = null; // 清除缓冲区位置
- out = (out + 1) % capacity; // 更新出指针
- count--; // 减少计数
-
- lock.notifyAll(); // 唤醒所有等待的生产者线程
- return item;
- }
- }
- }
-
- // 使用示例:
- public class Main {
- public static void main(String[] args) {
- BoundedBuffer buffer = new BoundedBuffer(3);
-
- Thread producer = new Thread(() -> {
- for (int i = 0; i < 10; i++) {
- try {
- buffer.put("Item " + i);
- System.out.println("Produced: Item " + i);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
-
- Thread consumer = new Thread(() -> {
- for (int i = 0; i < 10; i++) {
- try {
- Object item = buffer.take();
- System.out.println("Consumed: " + item);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
-
- producer.start();
- consumer.start();
- }
- }
详细解释:
在这个BoundedBuffer类中,我们模拟了一个容量有限的缓冲区,它可以由多个生产者线程向其中添加元素,同时也可以由多个消费者线程从中移除元素。
反之,当缓冲区为空时,消费者线程会在take()方法中调用lock.wait()进入等待状态,而生产者线程在添加新元素后则会调用lock.notifyAll()唤醒所有等待的消费者线程。
这样就实现了生产者线程和消费者线程之间的协调工作,确保了多线程环境下的正确数据交换,避免了竞态条件和死锁等问题。
Java并发工具类库java.util.concurrent(JUC)提供了一系列高效的线程同步和并发控制工具,下面列举几个常用的并发工具类并给出示例代码及详细解释:
1. CountDownLatch
**作用:**允许一个或多个线程等待其他线程完成操作。
示例代码:
- import java.util.concurrent.CountDownLatch;
-
- public class CountDownLatchExample {
- public static void main(String[] args) throws InterruptedException {
- int threadCount = 5;
- final CountDownLatch startSignal = new CountDownLatch(1);
- final CountDownLatch doneSignal = new CountDownLatch(threadCount);
-
- for (int i = 0; i < threadCount; ++i) { // 创建并启动5个工作线程
- Thread worker = new Thread(new Worker(startSignal, doneSignal));
- worker.start();
- }
-
- System.out.println("主线程开始执行...");
- startSignal.countDown(); // 主线程调用countDown()方法,发出启动信号
-
- doneSignal.await(); // 主线程等待所有工作线程完成任务
- System.out.println("所有工作线程已完成任务,主线程继续执行...");
- }
-
- static class Worker implements Runnable {
- private final CountDownLatch startSignal;
- private final CountDownLatch doneSignal;
-
- Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
- this.startSignal = startSignal;
- this.doneSignal = doneSignal;
- }
-
- @Override
- public void run() {
- try {
- startSignal.await(); // 工作线程等待启动信号
- doWork();
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- } finally {
- doneSignal.countDown(); // 完成工作后,调用countDown()
- }
- }
-
- private void doWork() {
- // 模拟耗时任务
- System.out.println(Thread.currentThread().getName() + " 开始工作...");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + " 工作结束.");
- }
- }
- }
详细解释:
2. Semaphore
**作用:**限制同时访问特定资源的线程数量。
示例代码:
- import java.util.concurrent.Semaphore;
-
- public class SemaphoreExample {
- private static final Semaphore semaphore = new Semaphore(3); // 只允许3个线程同时访问资源
-
- public static void main(String[] args) {
- for (int i = 0; i < 10; i++) {
- new Thread(() -> {
- try {
- semaphore.acquire(); // 获取许可
- accessResource();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } finally {
- semaphore.release(); // 释放许可
- }
- }).start();
- }
- }
-
- private static void accessResource() {
- System.out.println(Thread.currentThread().getName() + " 正在访问共享资源...");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + " 访问结束.");
- }
- }
详细解释:
3. CyclicBarrier
**作用:**多个线程到达某个屏障点时会被阻塞,直到最后一个线程到达,然后所有线程才会一起继续执行。
示例代码:
- import java.util.concurrent.BrokenBarrierException;
- import java.util.concurrent.CyclicBarrier;
-
- public class CyclicBarrierExample {
- private static final int THREAD_COUNT = 5;
- private static final CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT);
-
- public static void main(String[] args) {
- for (int i = 0; i < THREAD_COUNT; i++) {
- new Thread(() -> {
- try {
- processTask();
- barrier.await(); // 等待所有线程都到达障碍点
- System.out.println("所有线程都已经到达,现在一起继续执行");
- } catch (BrokenBarrierException | InterruptedException e) {
- e.printStackTrace();
- }
- }).start();
- }
- }
-
- private static void processTask() {
- System.out.println(Thread.currentThread().getName() + " 开始处理任务...");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + " 完成任务...");
- }
- }
详细解释:
在Java并发编程中,原子类(Atomic Classes)是位于java.util.concurrent.atomic包下的一个重要的工具集,它们提供了对基本数据类型和引用类型的原子操作。这些类的实例可以在高并发环境下保证线程安全地更新变量值,而不需要使用synchronized关键字或锁。
以下是一些常用的原子类及其示例:
1.AtomicInteger
- import java.util.concurrent.atomic.AtomicInteger;
-
- public class AtomicIntegerExample {
- private AtomicInteger counter = new AtomicInteger(0);
-
- public void incrementCounter() {
- // 使用原子操作自增
- counter.incrementAndGet();
- }
-
- public int getCounter() {
- return counter.get();
- }
-
- public static void main(String[] args) throws InterruptedException {
- AtomicIntegerExample example = new AtomicIntegerExample();
-
- Thread t1 = new Thread(() -> {
- for (int i = 0; i < 1000; i++) {
- example.incrementCounter();
- }
- });
-
- Thread t2 = new Thread(() -> {
- for (int i = 0; i < 1000; i++) {
- example.incrementCounter();
- }
- });
-
- t1.start();
- t2.start();
-
- // 确保两个线程都完成计数
- t1.join();
- t2.join();
-
- System.out.println("最终计数值: " + example.getCounter());
- }
- }
详细解释:
2.AtomicReference
- import java.util.concurrent.atomic.AtomicReference;
-
- public class AtomicReferenceExample {
- private AtomicReference<String> valueHolder = new AtomicReference<>("初始值");
-
- public void updateValue(String newValue) {
- // 使用原子方式设置新值
- valueHolder.set(newValue);
- }
-
- public String getValue() {
- return valueHolder.get();
- }
-
- public static void main(String[] args) {
- AtomicReferenceExample example = new AtomicReferenceExample();
- example.updateValue("新的值");
- System.out.println("获取到的最新值为: " + example.getValue());
- }
- }
详细解释:
Java中的线程局部变量是通过java.lang.ThreadLocal类来实现的。它为每个线程创建一个单独的变量副本,使得在并发执行时,每个线程可以拥有独立的变量值,从而避免了线程间共享资源带来的同步问题。
示例代码:
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
-
- public class ThreadLocalExample {
- // 创建一个ThreadLocal实例
- public static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
-
- public static void main(String[] args) {
- ExecutorService executor = Executors.newFixedThreadPool(2);
-
- // 提交任务到线程池
- for (int i = 0; i < 2; i++) {
- int threadId = i + 1;
- executor.submit(() -> {
- // 设置当前线程对应的ThreadLocal变量
- THREAD_LOCAL.set("Value from Thread " + threadId);
- System.out.println("In Thread " + Thread.currentThread().getName() + ": " + THREAD_LOCAL.get());
-
- // 执行一些操作...
-
- // 清除当前线程对应的ThreadLocal变量(可选)
- THREAD_LOCAL.remove();
- });
- }
-
- // 关闭线程池
- executor.shutdown();
- }
- }
详细解释:
这样,每个线程都有自己独立的ThreadLocal存储空间,当在一个线程中修改ThreadLocal的值时,其他线程的ThreadLocal值不受影响,这在处理每个线程特有的状态信息、或者在多线程环境中需要隔离数据的情况下非常有用。例如,在Web应用中,ThreadLocal经常用于存储与请求相关的会话信息,确保不同请求之间的数据互不干扰。
Java线程有五种基本状态,分别是:新建(New)、运行(Runnable)、阻塞(Blocked)、等待(Waiting)和终止(Terminated)。下面通过示例代码来说明如何管理线程状态以及状态之间的转换。
1.线程状态转换 - Runnable到Blocked
- import java.util.concurrent.locks.LockSupport;
-
- public class ThreadStateExample {
-
- public static void main(String[] args) {
- Thread t = new Thread(() -> {
- System.out.println("线程开始执行");
- // 模拟进入Blocked状态,调用park方法让线程挂起
- LockSupport.park();
- System.out.println("线程恢复执行");
- });
-
- t.start();
-
- try {
- // 线程启动后,主线程休眠2秒,确保t线程有机会开始执行并被挂起
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- // 唤醒t线程
- LockSupport.unpark(t);
-
- try {
- t.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
详细解释:
2.线程状态转换 - Runnable到Waiting
- import java.util.concurrent.TimeUnit;
-
- public class ThreadStateExample2 {
- private final Object lock = new Object();
-
- public static void main(String[] args) {
- ThreadStateExample2 example = new ThreadStateExample2();
- Thread t = new Thread(example::waitMethod);
-
- t.start();
-
- try {
- TimeUnit.SECONDS.sleep(1); // 主线程等待1秒
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- synchronized (example.lock) { // 通知等待的线程
- example.lock.notifyAll();
- }
- }
-
- public void waitMethod() {
- synchronized (lock) {
- System.out.println("线程进入等待状态");
- try {
- lock.wait(); // 进入Waiting状态
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("线程从等待状态恢复");
- }
- }
- }
详细解释:
在Java中,线程调度主要由Java虚拟机(JVM)和底层操作系统共同完成。Java提供了两种不同的优先级设置来影响线程调度,但请注意,尽管设置了线程优先级,具体的调度策略仍然很大程度上取决于操作系统的实现。
示例代码:线程优先级
- public class ThreadPriorityExample {
- public static void main(String[] args) {
- Thread thread1 = new Thread(() -> {
- System.out.println("Thread 1 started, priority: " + Thread.currentThread().getPriority());
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("Thread 1 finished");
- });
-
- // 设置线程优先级,范围是1-10,默认为5
- thread1.setPriority(Thread.MAX_PRIORITY); // 设置为最高优先级
-
- Thread thread2 = new Thread(() -> {
- System.out.println("Thread 2 started, priority: " + Thread.currentThread().getPriority());
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("Thread 2 finished");
- });
-
- thread2.start();
- thread1.start();
- }
- }
详细解释: 在这个示例中,我们创建了两个线程thread1和thread2。其中thread1的优先级被设置为最大值(Thread.MAX_PRIORITY),这意味着在所有条件相同的情况下,操作系统将更倾向于调度优先级高的线程执行。
然而,实际的调度结果可能会受到很多因素的影响,包括但不限于:
因此,虽然可以设置线程优先级,但在编写多线程程序时,应尽量避免依赖于优先级来进行同步控制,而是更多地依靠并发工具类如synchronized、volatile关键字、java.util.concurrent包下的工具类等来进行正确的并发控制和通信。
死锁是指两个或多个线程相互等待对方持有的资源而造成的僵局。在Java多线程并发编程中,死锁是一个需要特别注意的问题。下面通过一个示例来演示死锁的发生以及如何检测和避免。
示例代码:死锁的演示
- public class DeadlockExample {
- static Object resource1 = new Object();
- static Object resource2 = new Object();
-
- public static void main(String[] args) {
- Thread thread1 = new Thread(() -> {
- synchronized (resource1) {
- System.out.println("Thread 1: Acquired resource 1");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (resource2) {
- System.out.println("Thread 1: Acquired resource 2");
- }
- }
- });
-
- Thread thread2 = new Thread(() -> {
- synchronized (resource2) {
- System.out.println("Thread 2: Acquired resource 2");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (resource1) {
- System.out.println("Thread 2: Acquired resource 1");
- }
- }
- });
-
- thread1.start();
- thread2.start();
- }
- }
详细解释:
在这个示例中,我们定义了两个线程thread1和thread2,它们分别试图获取两个资源resource1和resource2的锁。线程thread1首先尝试获取resource1的锁,然后获取resource2的锁;而线程thread2的顺序相反。如果线程thread1在获取resource1的锁之后,线程thread2立即获取了resource2的锁,然后线程thread1尝试获取resource2的锁时将被阻塞,线程thread2尝试获取resource1的锁时也将被阻塞,这时就形成了一个死锁。
死锁的检测
Java提供了一个工具来检测死锁,即jstack命令。通过运行jstack命令并指定进程ID,我们可以获得线程的堆栈信息,从而查看是否有线程处于死锁状态。
死锁的避免
以下是一些常见的策略来避免死锁的发生:
在Java中,非阻塞同步机制主要包括原子变量(Atomic Variables)和循环CAS(Compare and Swap)等技术。其中,java.util.concurrent.atomic包下的原子类就是实现非阻塞同步的一种重要手段。
示例代码:使用AtomicInteger进行非阻塞同步
- import java.util.concurrent.atomic.AtomicInteger;
-
- public class NonBlockingSyncExample {
- // 使用AtomicInteger作为共享计数器
- private final AtomicInteger counter = new AtomicInteger(0);
-
- public void increment() {
- // 使用compareAndSet方法实现原子性的递增操作
- while (true) {
- int current = counter.get();
- int next = current + 1;
- // 如果当前值等于预期的旧值,则更新为新值
- if (counter.compareAndSet(current, next)) {
- break; // 更新成功,退出循环
- }
- }
- }
-
- public static void main(String[] args) {
- NonBlockingSyncExample example = new NonBlockingSyncExample();
- Thread t1 = new Thread(example::increment);
- Thread t2 = new Thread(example::increment);
- Thread t3 = new Thread(example::increment);
-
- t1.start();
- t2.start();
- t3.start();
-
- // 等待所有线程完成任务
- try {
- t1.join();
- t2.join();
- t3.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- System.out.println("最终计数值: " + example.counter.get());
- }
- }
详细解释:
总结
Java多线程并发编程是一项需要深入理解和熟练掌握的技术,只有合理地设计和使用多线程,才能真正发挥其在提升系统性能方面的巨大潜力。随着对Java并发机制及工具类库的深入学习和应用,开发者可以更好地构建高效、稳定的并发系统。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。