Java多线程是什么

简介: Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。

Java多线程

线程池的类型

  • Executors.newCachedThreadPool:
  • 解释:创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这种线程池适合执行很多短期异步的小程序或者负载较轻的服务器。
  • 应用场景:Executors.newCachedThreadPool:这种线程池可以创建无限多个线程,适合执行很多短期异步的小任务,或者是负载较轻的服务器。例如,一个 Web 服务器接收到大量并发请求时,可以使用这种线程池来处理每个请求。

Java

  • 代码解读
  • 复制代码
// 创建一个固定大小为 5 的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 向线程池提交 10 个任务
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
    System.out.println("CurrentThread name:" + Thread.currentThread().getName());
});
}
// 关闭线程池
executorService.shutdown();
  • Executors.newFixedThreadPool:
  • 解释:创建一个固定大小的线程池,每次提交一个任务就创建一个线程,直到达到线程池的最大大小。这种线程池可以控制并发的线程数,超出的线程会在队列中等待。这种线程池适合执行长期的稳定和固定的任务。
  • 应用场景:这种线程池可以创建固定数量的线程,适合执行长期的稳定和固定的任务,或者是有资源限制的场景。例如,一个数据库服务器需要控制并发访问数时,可以使用这种线程池来分配连接。
  • Executors.newScheduledThreadPool:
  • 解释:创建一个定长的线程池,支持定时及周期性任务执行。这种线程池适合执行延迟或者定时的任务。
  • 应用场景:这种线程池可以创建固定数量的线程,并支持定时和周期性任务执行。适合执行一些需要在指定时间或者周期性地执行的任务。例如,一个定时任务调度器需要按照时间表执行不同的任务时,可以使用这种线程池来安排任务。
  • Executors.newSingleThreadExecutor:
  • 解释:创建单个后台线程来执行任务,如果该后台进程异常结束会有另一个取代它。这种线程池保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。这种线程池适合需要按顺序执行各个任务,并且在任意时间点只能有一个任务被执行。
  • 应用场景:这种线程池只有一个工作线程,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。适合执行一些需要按顺序执行各个任务,并且在任意时间点只能有一个任务被执行的场景。例如,一个消息队列需要按照消息到达顺序处理消息时,可以使用这种线程池来消费消息12。

Java锁

Java synchronized 示例

java

代码解读

复制代码

public class Counter {
  private int counter;
  public synchronized void increment() {
    counter++;
  }
  public int read() {
    return counter;
  }
}

Java CAS 实现:

CAS(Compare And Swap)是一种利用处理器提供的特殊指令来实现对内存地址上的值进行比较和替换的操作12。CAS指令接收三个参数:内存地址(M)、期望值(A)和更新值(B)。它会将内存地址上的当前值与期望值进行比较,如果相等,则将内存地址上的值替换为更新值,并返回true;如果不相等,则不做任何修改,并返回false12。

Java中没有直接实现CAS,而是通过sun.misc.Unsafe类提供了一些底层方法来调用CAS指令34。Unsafe类是一个非常危险的类,它可以直接操作内存地址和数据,绕过Java的安全机制和访问控制。因此,它只能在受信任的代码中使用,一般用户无法直接获取Unsafe类的实例34。

Unsafe类提供了多个方法来执行CAS操作,例如compareAndSwapInt()、compareAndSwapLong()、compareAndSwapObject()等。这些方法都需要传入一个对象、一个偏移量和两个期望值或更新值作为参数,并尝试将对象中偏移量位置上的值从期望值改为更新值。如果成功,则返回true;否则返回false。

例如,在AtomicInteger类中,就使用了Unsafe类提供的compareAndSwapInt()方法来实现原子地增加或减少变量值:

Java

代码解读

复制代码

public class AtomicInteger {
  private volatile int value;
  private static final Unsafe unsafe = Unsafe.getUnsafe();
  private static final long valueOffset;

  static {
    try {
      valueOffset = unsafe.objectFieldOffset
          (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
  }

  public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
  }

  public final int incrementAndGet() {
    for (;;) {
      int current = get();
      int next = current + 1;
      if (compareAndSet(current, next))
        return next;
    }
  }

  public final int decrementAndGet() {
    for (;;) {
      int current = get();
      int next = current - 1;
      if (compareAndSet(current, next))
        return next;
    }
  }
}

在这个例子中,首先使用反射获取value变量在AtomicInteger对象中的偏移量,并保存在valueOffset属性中。然后在compareAndSet()方法中,调用unsafe.compareAndSwapInt(this, valueOffset, expect, update)方法来执行CAS操作。在incrementAndGet()和decrementAndGet()方法中,使用循环不断地尝试获取当前值、计算新值并调用compareAndSet()方法来更新变量值。

Java锁的类型:

1.可重入锁  java.util.concurrent.locks.ReentrantLock

可重入锁是一种锁,它允许一个线程多次获取同一个锁,而不会造成死锁。每次获取锁时,锁的持有计数会加一,每次释放锁时,持有计数会减一。当持有计数为零时,锁被完全释放。可重入锁可以实现线程对共享资源的独占访问,同时也支持线程在同一个方法或者不同方法中递归地获取同一个锁。

Java中提供了ReentrantLock类来实现可重入的互斥锁24。ReentrantLock类实现了Lock接口,并提供了一些扩展功能,比如公平性、可中断性、条件变量等。使用ReentrantLock类的一般步骤如下:

创建一个ReentrantLock实例,根据需要选择不同的构造参数 在访问共享资源之前,调用lock()方法获取锁,如果锁不可用,则线程会阻塞直到获取到锁 在try-finally块中访问共享资源,并在finally块中调用unlock()方法释放锁 根据需要使用其他ReentrantLock提供的方法和特性 例如:

Java

代码解读

复制代码

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
  private int count = 0;
  private ReentrantLock lock = new ReentrantLock();

  public void increment() {
    lock.lock(); // 获取锁
    try {
      count++; // 访问共享资源
    } finally {
      lock.unlock(); // 释放锁
    }
  }

  public int read() {
    return count;
  }
}

这个例子中,使用了ReentrantLock作为锁对象,它是一个可重入的互斥锁。在increment()方法中,在对count变量进行加一操作之前,先调用lock.lock()方法获取锁。如果其他线程已经持有了这个锁,则当前线程会阻塞直到获得这个锁。然后在try-finally块中访问count变量,并在finally块中调用lock.unlock()方法释放锁。这样可以保证只有一个线程能够修改count的值。

2.读写锁 java.util.concurrent.locks.ReadWriteLock

  • Java读写锁是一种可以实现读写分离的同步机制,它允许多个线程同时获取读锁,但只能有一个线程获取写锁1。当有线程持有写锁时,其他线程不能获取读锁或写锁。
  • Java读写锁的一个实现类是 ReentrantReadWriteLock,它基于AQS(队列同步器)的独占和共享模式来完成功能。它使用一个int变量来表示同步状态,将高16位用于共享状态(读状态),低16位用于独占状态(写状态)。
  • ReentrantReadWriteLock支持公平和非公平两种模式,在非公平模式下,允许写锁插队,也允许读锁插队,但是读锁插队的前提是队列中的头节点不能是想获取写锁的线程。在公平模式下,都是严格按照请求锁顺序进行的。
  • ReentrantReadWriteLock提供了ReadLock和WriteLock两个内部类,分别代表读锁和写锁对象。它们都有lock()和unlock()方法来加解锁。在加解锁过程中,会通过CAS操作更新同步状态,并维护相关的计数器来记录重入次数和持有线程信息。

Java

代码解读

复制代码

// 导入相关类
import java.util.concurrent.locks.ReentrantReadWriteLock;

// 创建一个ReentrantReadWriteLock对象
ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();

// 获取读锁和写锁对象
ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();

// 定义一个方法用于读操作
public static void read() {
  // 尝试获取读锁
  readLock.lock();
  try {
    // 执行读操作,打印线程名和信息
    System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");
    // 模拟耗时操作,睡眠1秒
    Thread.sleep(1000);
 } catch (Exception e) {
    e.printStackTrace();
  } finally {
    // 释放读锁,并打印线程名和信息
    readLock.unlock();
    System.out.println(Thread.currentThread().getName() + "释放读锁");
  }
}

// 定义一个方法用于写操作
public static void write() {
  // 尝试获取写锁
  writeLock.lock();
  try {
    // 执行写操作,打印线程名和信息
    System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");
    // 模拟耗时操作,睡眠1秒
     Thread.sleep(1000);
  } catch (Exception e) {
    e.printStackTrace();
  } finally {
    // 释放写锁,并打印线程名和信息
    writeLock.unlock();
    System.out.println(Thread.currentThread().getName() + "释放写锁");
  }
}

// 在main方法中创建四个线程分别进行读和写操作,并启动它们
public static void main(String[] args) {
  new Thread(() -> read(), "Thread1").start();
  new Thread(() -> read(), "Thread2").start();
  new Thread(() -> write(), "Thread3").start();
  new Thread(() -> write(), "Thread4").start();
}

输出结果如下,可以看到线程1和线程2可以同时获取读锁,而线程3和线程4只能依次获取写锁,因为线程4必须等待线程3释放写锁后才能获取到锁:

代码解读

复制代码

Thread1获取读锁,开始执行 
Thread2获取读锁,开始执行   
Thread1释放读锁   
Thread2释放读锁   
Thread3获取写锁,开始执行   
Thread3释放写锁   
Thread4获取写锁,开始执行   
Thread4释放写锁   
`


转载来源:https://juejin.cn/post/7301873618492588047

相关文章
|
2天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
4天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
4天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
20 3
|
4天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
50 2
|
12天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
43 6
|
25天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
21天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
21天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
44 3
|
22天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
25天前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
29 2