前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 多线程系列Ⅶ

Java 多线程系列Ⅶ

作者头像
终有救赎
发布2024-02-27 09:08:18
1300
发布2024-02-27 09:08:18
举报
文章被收录于专栏:多线程多线程

前言

JUC 是 Java 并发包(java.util.concurrent)的简称,该包在 Java 5.0 版本引入,提供了在并发编程中常用的工具类。这些工具类包括用于多线程同步的锁(如 ReentrantLock),用于线程池的 ExecutorService,用于原子操作的 AtomicInteger 等。这些类和接口都是非阻塞的,设计目标是在多线程环境下提供高性能和可扩展性。

JUC 包中的类和接口主要分为三类:

  1. 同步工具类:例如 Lock、Semaphore、CountDownLatch、CyclicBarrier、Exchanger 等,这些类提供了多种同步机制,可以灵活地控制多个线程之间的执行顺序和协作。
  2. 线程池类:例如 ThreadPoolExecutor、ScheduledThreadPoolExecutor 等,这些类可以方便地创建和管理线程池,有效地利用系统资源,避免线程过多或者过少的问题。
  3. 原子操作类:例如 AtomicInteger、AtomicLong、AtomicReference 等,这些类提供了原子操作,可以在多线程环境下保证数据的一致性和安全性。

一、Callable

1、介绍

Callable接口是Java中的一个泛型接口,它允许在并发环境中定义可以返回结果的任务。与Runnable接口不同的是,Callable接口可以返回执行结果,并且可以抛出异常。

2、Callable的使用

使用Callable接口需要实现Callable接口,并重写call()方法。call()方法定义了要执行的任务,并返回执行结果。

代码语言:javascript
复制
public class MyCallable implements Callable<Integer> {  
    @Override  
    public Integer call() throws Exception {  
        // 执行任务,并返回结果  
        return 42;  
    }  
}

在上述代码中,MyCallable实现了Callable接口,并返回一个Integer类型的结果。call()方法定义了要执行的任务,并返回执行结果。

3、具体例子

使用线程池ExecutorService执行一个Callable任务,并获取返回结果:

代码语言:javascript
复制
import java.util.concurrent.*;  
  
public class Example {  
    public static void main(String[] args) {  
        ExecutorService executor = Executors.newFixedThreadPool(1);  
        Callable<Integer> task = new MyCallable();  
        Future<Integer> future = executor.submit(task);  
        try {  
            Integer result = future.get(); // 获取返回结果  
            System.out.println("Result: " + result);  
        } catch (InterruptedException | ExecutionException e) {  
            e.printStackTrace();  
        } finally {  
            executor.shutdown();  
        }  
    }  
}

例子解析:Example创建了一个线程池ExecutorService,并提交了一个MyCallable任务。通过调用Future的get()方法获取任务的返回结果。如果任务执行成功,则返回结果;如果任务执行失败,则抛出ExecutionException异常。最后,Example关闭线程池。

4、Callable与Runnable比较

  1. Callable接口可以返回执行结果,而Runnable接口不能。
  2. Callable接口可以抛出异常,而Runnable接口不能。
  3. Callable接口的实现类需要实现call()方法,而Runnable接口的实现类需要实现run()方法。
  4. Runnable接口通常用于实现简单的后台任务,而Callable接口通常用于需要返回结果的复杂任务。
  5. Runnable和Callable的设计目标不同:Runnable设计目标是在多线程环境下提供简单的执行代码的机制;Callable设计目标是在多线程环境下提供具有返回值的任务执行机制。
  6. 在使用线程池时,通常使用Callable接口,因为线程池可以更好地管理线程资源,并允许在任务完成后自动关闭线程。而Runnable通常直接提交给Thread对象来执行。
  7. Callable可以在任务执行前进行初始化操作;而Runnable没有这样的机制。比如:可以在任务执行前对局部变量进行初始化;或者在任务执行前进行资源的预分配等。这种特性在某些场景下非常有用。比如:一个任务需要大量计算和内存消耗,为了减少垃圾回收的频率,可以在任务执行前进行内存的大量分配。但是请注意:大量使用这种方式可能会导致内存的浪费和内存溢出等问题。因此在使用时需要谨慎考虑其性能和内存消耗问题。

当然Callable接口主要是创建线程的新方式,目前我们学过的创建线程的方式总结如下: 1、继承Thread类 2、实现Runnable接口 3、实现Callable接口 4、线程池

二、ReentrantLock

1、介绍

ReentrantLock是一种可重入锁,它允许一个线程多次获取同一个锁,而不会产生死锁。ReentrantLock在加锁时采用了公平锁策略,即等待时间最长的线程将获得锁。与内置的synchronized锁不同,ReentrantLock是非阻塞的,它允许线程在等待锁的过程中执行其他任务。

2、ReentrantLock的使用

通常,我们使用ReentrantLock时需要先创建一个ReentrantLock对象,然后在需要加锁的地方调用lock()方法获取锁,执行任务后调用unlock()方法释放锁。

代码语言:javascript
复制
import java.util.concurrent.locks.ReentrantLock;  
  
public class MyClass {  
    private final ReentrantLock lock = new ReentrantLock();  
  
    public void doSomething() {  
        lock.lock(); // 加锁  
        try {  
            // 执行任务  
        } finally {  
            lock.unlock(); // 释放锁  
        }  
    }  
}

代码解析:MyClass创建了一个ReentrantLock对象,并在doSomething()方法中进行了加锁和释放锁的操作。使用try-finally语句块确保在任务执行完毕后释放锁。

3、具体例子

可以使用ReentrantLock实现线程安全的计数器:

代码语言:javascript
复制
import java.util.concurrent.locks.ReentrantLock;  
  
public class Counter {  
    private int count = 0;  
    private final ReentrantLock lock = new ReentrantLock();  
  
    public void increment() {  
        lock.lock(); // 加锁  
        try {  
            count++;  
        } finally {  
            lock.unlock(); // 释放锁  
        }  
    }  
  
    public int getCount() {  
        return count;  
    }  
}

通常,我们使用的计数器只支持我们在单线程下使用,如果想要在多线程下使用,通常需要加锁,Counter创建了一个ReentrantLock对象和一个计数器变量count。increment()方法用于对计数器进行加1操作,并使用ReentrantLock进行同步。getCount()方法返回计数器的当前值。由于使用了ReentrantLock,这个计数器可以在多线程环境下安全地使用。

4、ReentrantLock和Synchronized的区别

1、底层实现:synchronized 是JVM层面的锁,是Java关键字,通过monitor对象来完成(monitorenter与monitorexit),ReentrantLock 是从jdk1.5以来(java.util.concurrent.locks.Lock)提供的API层面的锁。

2、实现原理:synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向OS申请重量级锁;ReentrantLock实现则是通过利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile保证数据可见性以实现锁的功能。

3、是否可手动释放: synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用; ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象

4、是否可中断synchronized是不可中断类型的锁,除非加锁的代码中出现异常或非正常执行完成; ReentrantLock则可以中断,可通过trylock(long timeout,TimeUnit unit)设置超时方法或者将lockInterruptibly()放到代码块中,调用interrupt方法进行中断。

5、是否公平锁synchronized为非公平锁 ReentrantLock则即可以选公平锁也可以选非公平锁,通过构造方法new ReentrantLock时传入boolean值进行选择,为空默认false非公平锁,true为公平锁,公平锁性能非常低。

三、Atomic 原子类

1、介绍

在多线程环境下,多个线程可能会同时访问和修改同一个共享变量,这时就需要一种机制来保证操作的原子性,以避免出现竞争条件和数据不一致的问题。Java中的Atomic原子类提供了一种高效的方式来解决这个问题。Atomic原子类通过使用CPU的原子指令来保证操作的原子性,避免了使用锁的开销和竞争。

2、Atomic 使用

代码语言:javascript
复制
import java.util.concurrent.atomic.AtomicInteger;  
  
public class MyApp {  
    private static AtomicInteger counter = new AtomicInteger(0);  
  
    public static void main(String[] args) {  
        // 增加计数器值  
        counter.incrementAndGet();  
        // 获取计数器值  
        int value = counter.get();  
        System.out.println("Counter value: " + value);  
    }  
}

3、具体例子

也是和上面一样实现一个线程安全的计数器:

代码语言:javascript
复制
import java.util.concurrent.atomic.AtomicLong;  
  
public class AtomicCounter {  
    private AtomicLong counter = new AtomicLong(0);  
  
    public void increment() {  
        counter.incrementAndGet();  
    }  
  
    public long get() {  
        return counter.get();  
    }  
}

代码解析:我们创建了一个AtomicLong对象counter,并使用incrementAndGet()方法增加计数器的值。然后使用get()方法获取计数器的值。由于AtomicLong提供了原子操作,因此即使多个线程同时访问和修改计数器,也不会出现竞争条件和数据不一致的问题。

4、Atomic特点

  1. 原子性:Atomic原子类提供了一种在多线程环境下进行原子操作的方式,保证了操作的原子性。
  2. 非阻塞性:Atomic原子类避免了使用锁的开销和竞争,从而提高了程序的响应能力和吞吐量。
  3. 高效性:Atomic原子类通过使用CPU的原子指令来保证操作的原子性,避免了出现竞争条件和数据不一致的问题。
  4. 可移植性:Atomic原子类的实现与Java虚拟机规范保持一致,因此可以在不同的Java虚拟机上使用。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-02-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、Callable
    • 1、介绍
      • 2、Callable的使用
        • 3、具体例子
          • 4、Callable与Runnable比较
          • 二、ReentrantLock
            • 1、介绍
              • 2、ReentrantLock的使用
                • 3、具体例子
                  • 4、ReentrantLock和Synchronized的区别
                  • 三、Atomic 原子类
                    • 1、介绍
                      • 2、Atomic 使用
                        • 3、具体例子
                          • 4、Atomic特点
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档