《java并发编程实战》总结(二)

简介: 《java并发编程实战》总结(二)

第7章 取消与关闭


很重要,因为我看不懂(我好菜啊)


第8章 线程池的使用


在一些任务中,需要拥有或排除某种特定的执行策略。如果某些任务依赖其他的任务,那么会要求线程池足够大,从而确保它们依赖的任务不会被放入等待队列中或被拒绝,而采用线程封闭机制的任务需要串行执行。通过将这些需求写入文档,将来的代码维护人员就不会由于使用了某种不合适的执行策略而破坏安全性或活跃性。


每当提交了一个由依赖性的Executor任务时,要清楚的知道可能会出现线程“饥饿”死锁,因此需要在代码或配置Executor的配置中心记录线程池的大小限制或配置限制。


第10章 避免活跃性危险


在安全性与活跃性之间通常存在着某种制衡。我们使用加锁机制来确保线程安全,但是如果过度的使用加锁,则可能导致顺序死锁。同样,我们使用线程池和信号量来限制资源的使用,但这些限制的行为可能会导致资源死锁。


10.1死锁


线程A等待线程B所占有的资源,而线程B等待线程A所占有的资源,如果在图中形成一个环路,那么就存在一个死锁。


10.1.1锁顺序死锁


如下图所示,一个线程拥有left锁,去尝试right锁,而一个线程拥有right锁,去尝试left锁,就产生死锁。


4.png


class LeftRighrDeadLock {
    private final Object left = new Object();
    private final Object right = new Object();
    public void leftRight() {
        synchronized (left) {
            synchronized (right) {
                System.out.println();
            }
        }
    }
    public void rightLeft() {
        synchronized (right) {
            synchronized (left) {
                System.out.println();
            }
        }
    }
}


10.1.2动态的锁顺序死锁


有的时候我们并不清楚是否在锁顺序上有足够的控制权来避免死锁的发生。正如转账所示,所有的线程都似乎安装相同的顺序来获得锁,但是事实上锁的顺序取决于参数顺序,如下面的代码所示。


    transferMoney(myAccount,yourAccount,10);
    transferMoney(myourAccount,myAccount,,20);


要解决上面的问题,必须定义锁顺序,该方法将返回由Object.hashcode返回的值定义锁的顺序。虽然增加了额一些代码,但是消除了发生死锁的可能性。如果Account中包含一个唯一的,不可变的,并且具备可比性的键值,那就不需要例如下面代码中“加时赛”锁作用的lock。


    private final Object lock = new Object();
    public void transferMoney(Object fromAcct, Object toAcct, int num) {
        int fromHash = fromAcct.hashCode();
        int toHash = toAcct.hashCode();
        if (fromHash < toHash) {
            synchronized (fromAcct) {
                synchronized (toAcct) {
                    System.out.println("do something...");
                }
            }
        } else if (toHash < fromHash) {
            synchronized (toHash) {
                synchronized (fromHash) {
                    System.out.println("do something...");
                }
            }
        } else {
            synchronized (lock) {
                synchronized (toHash) {
                    synchronized (fromHash) {
                        System.out.println("do something...");
                    }
                }
            }
        }
    }


10.1.3 在协作对象直接发生的死锁


如下面代码所示,线程A调用car.carMethod方法时,拥有自己锁并且尝试carList的锁。如果此时线程B调用carList.carListMethod方法时,拥有自己的锁,并且尝试car的锁时,就发生了死锁。


class Car {
    private String name;
    private CarList carList;
    public synchronized void carMethod(String name) {
        this.name = name;
        carList.carListMethod(this);
    }
}
class CarList {
    private List list = new ArrayList();
    public synchronized void carListMethod(Car car) {
        boolean contains = list.contains(car);
        if (!contains) {
            car.carMethod();
        }
    }
}


10.1.4 开放调用


如果在调用某个方法时不需要持有锁,那么这种调用方法就是开放调用。


    //开放调用的反例
   public synchronized void method(){
       otherClassInstence.synchronizedMethod();
   }
   //开放调用
    public  void method(){
       synchronized (this){
           doSomeThing();
       }
        otherClassInstence.synchronizedMethod();
    }


有的时候会丢失原子性。


在程序中应尽量使用开放调用。与那些在持有锁的时候调用外部方法的程序相比,更容易对依赖开放调用的程序进行死锁分析。


10.2死锁的避免与诊断


10.2.1 支持定时的锁


 Lock lock = new ReentrantLock();
        try {
            lock.tryLock(10, TimeUnit.SECONDS);
            //logic
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


10.2.2通过线程转储信息来分析死锁


10.3其他活跃性危险


10.3.1饥饿


要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级


10.3.3活锁


 活锁(Livelock)是另一种形式的活跃性问题,该问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且,总会失败。 活锁通常发生在处理事务消息的应用程序中:如果不能成功地处理某个消息,那么消息处理机制将回滚整个事务,并将它重新放到队列的开头。如果消息处理器在处理某种特定类型的消息时存在错误并导致它失败,那么每当这个消息从队列中取出并传递到存在错误的处理器时,都会发生事务回滚。由于这条消息又被放回到队列开头,因此处理器将被反复调用,并返回相同的结果。(有时候也被称为毒药消息,Poison Message. )虽然处理消息的线程并没有阻塞,但也无法继续执行下去。这种形式的活锁通常是由过度的错误恢复代码造成的,因为它错误地将不可修复的错误作为可修复的错误。


第11章 性能与可伸缩性


11.1 对性能的思考


要想通过并发来获得更好的性能,需要努力做好两件事情:更有效地利用现有的处理资源,以及在出现新的处理资源时使程序尽可能地利用这些新资源。


11.1.1 性能与可伸缩性


应用程序的性能可以采用多个指标来衡量,例如服务时间、延迟时间、吞吐率、效率、可伸缩性以及容量等。其中一些指标(服务时间、等待时间)用于衡量程序的“运行速度”,即某个指定的任务单元需要“多快”才能处理完成。另一些指标(生产量、吞吐量)用于程序的“处理能力”,即在计算资源一 定的情况下,能完成“多少”工作。


可伸缩性指的是:当增加计算资源时(例如CPU、内存、存储容量或1/O带宽),程序的吞吐量或者处理能力能相应地增加。


11.1.2 评估各种性能权衡因素


避免不成熟的优化。首先使程序正确,然后在提高运行速度-----如果它还运行的不够快。

以测试为基准,不要猜测。


11.2 Amdahl定律


Amdahl定律:在增加计算资源的情况下,程序理论上能够实现最高加速比,取决于程序中可并行组件与串行组件所占的比重。


如下面代码所示,串行化的部分是queue.take()


class WorkerThread extends Thread {
    private final BlockingQueue<Runnable> queue;
    public WorkerThread(BlockingQueue<Runnable> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                Runnable task = queue.take();
                task.run();
            } catch (Exception e) {
                break;
            }
        }
    }
}


在所有的并发程序中都包含一些串行化部分。如果你认为在你的程序中不存在串行部分,那么可以再仔细的检查一遍。


11.3 线程引入的开销


  对于为了提升性能而引入的线程来说,并行带来的性能提升必须超过并发导致的开销。


11.3.1 上下文切换


切换上下文需要一定的开销,而在线程调度过程中需要访问由操作系统和JVM共享的数据结构。应用程序、操作系统以及JVM都使用一组相同的CPU。在JVM和操作系统的代码中消耗越多的CPU时钟周期,应用程序的可用CPU时钟周期就越少。但上下文切换的开销并不只是包含JVM和操作系统的开销。当一个新的线程被切换进来时,它所需要的数据可能不在当前处理器的本地缓存中,因此上下文切换将导致一一些缓存缺失,因而线程在首次调度运行时会更加缓慢。这就是为什么调度器会为每个可运行的线程分配一一个最小执行时间,即使有许多其他的线程正在等待执行:它将上下文切换的开销分摊到更多不会中断的执行时间上,从而提高整体的吞吐量(以损失响应性为代价)。


11.3.2 内存同步


现代的JVM能通过优化去掉一些不会发生竞争的锁,从而减少不必要的同步开销。如果一个锁对象只能由当前线程访问,那么JVM就可以通过优化去掉这个锁获取操作,因为另一个线程无法与当前线程在这个锁上发生同步。例如,JVM通常会去掉下面代码中的锁获取操作。


//没有作用的同步(不要这么做)
        synchronized (new Object()){
            //do
        }


JVM也可以执行锁粗化(Lock Coarsening)操作,将临近的同步代码块用一个锁合并起来。如下面代码所示,3个add和1个toString调用合并为单个锁获取/释放操作。


    public String getNames(){
        List list = new Vector();
        list.add("张三1");
        list.add("张三2");
        list.add("张三3");
        return list.toString();
    }


11.4 减少锁的竞争


在并发程序中,对可伸缩性的最主要微威胁就是独占方式的资源锁


11.4.1 缩小锁的范围("快进快出")


降低发生竞争可能性的一种有效方法是尽可能的缩短锁的持有时间。


class Demo {
    private final Map map = new HashMap();
    //错误案例
    public synchronized void putIfAbsent(String name, String val) {
        String key = "users." + name + ".location";
        if (!map.containsKey(key)) {
            map.put(key, val);
        }
    }
    //正确案例
    public void putIfAbsent(String name, String val) {
        String key = "users." + name + ".location";
        synchronized (this){
            if (!map.containsKey(key)) {
                map.put(key, val);
            }
        }
    }
}


11.4.2 减小锁的粒度


锁分解前


//使用一个锁
class Demo {
    private final Map map = new HashMap();
    private final Set set = new HashSet();
    public synchronized void putIfAbsentMap(String name, String val) {
        String key = "users." + name + ".location";
        synchronized (this){
            if (!map.containsKey(key)) {
                map.put(key, val);
            }
        }
    }
    public synchronized void putIfAbsentSet(String name) {
        String key = "users." + name + ".location";
        synchronized (this){
            if (!set.contains(key)) {
                set.add(key);
            }
        }
    }
}


锁分解后


//锁分解
class Demo {
    private final Map map = new HashMap();
    private final Set set = new HashSet();
    public void putIfAbsentMap(String name, String val) {
        String key = "users." + name + ".location";
        synchronized (map) {
            if (!map.containsKey(key)) {
                map.put(key, val);
            }
        }
    }
    public void putIfAbsentSet(String name) {
        String key = "users." + name + ".location";
        synchronized (set) {
            if (!set.contains(key)) {
                set.add(key);
            }
        }
    }
}


11.4.3 锁分段


每一段使用一个锁。可以看jdk1.8以前(不包括jdk1.8)的ConcurrentHashMap中的分段锁。


//锁分段
class Demo {
    //比如我设置16个锁
    Object[] locks = new Object[16];
    private int[] data = new int[32];
    public void updateData(int index, int val) {
        synchronized (locks[index % 16]) {
            data[index] = val;
        }
    }
}


    锁分段的一个劣势在于:与采用单个锁来实现独占访问相比,要获取多个锁来实现独占访问将更加困难并且开销更高。通常,在执行一个操作时最多只需要获得一个锁,但是在某些情况下需要加锁整个容器,例如当ConcurrentHashMap需要扩展映射范围以及重新计算键值的散列值要分布到更大的桶集合中时,就需要获取分段所集合中所有的锁。


11.4.5 一些替代独占锁的方法


并发容器、读-写锁、不可变对象以及原子变量。


11.4.7  向对象池说“不”


通常,对象分配操作的开销比同步的开销更低。


小结


  由于使用线程常常是为了充分利用多个处理器的计算能力,因此在并发程序性能的讨论中,通常更多地将侧重点放在吞吐量和可伸缩性上,而不是服务时间。Amdahl定律告诉我们,程序的可伸缩性取决于在所有代码中必须被串行执行的代码比例。因为Java程序中串行操作的主要来源是独占方式的资源锁,因此通常可以通过以下方式来提升可伸缩性:减少锁的持有时间,降低锁的粒度,以及采用非独占的锁或非阻塞锁来代替独占锁。


第13章 显式锁


13.1 Lock与ReentrantLock


Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有的加锁和解锁的方法都是显示的。ReentrantLock实现了Lock接口,并且提供了与synchronized相同的互斥性和内存可见性。


  • 轮询锁与定时锁
  • 可中断的锁获取操作
  • 非块结构的加锁

Lock接口的标准使用形式如下:


        Lock lock = new ReentrantLock();
        lock.lock();
        try {
            //do logic
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


13.2 性能考虑因素


性能是一个不断变化的指标,如果在昨天的测试基准中发现X比Y更快,那么在今天可能已经过时了。


13.3 公平性


 在ReentrantLock的构造函数中提供了两种公平性选择:创建一 个非公平的锁(默认)或者一个公平的锁。在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许“插队”:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。(在Semaphore中同样可以选择采用公平的或非公平的获取顺序。)非公平的ReentrantLock并不提倡“插队”行为,但无法防止某个线程在合适的时候进行“插队”。在公平的锁中,如果有另一个线程持有这个锁或者有其他线程在队列中等待这个锁,那么新发出请求的线程将被放入队列中。在非公平的锁中,只有当锁被某个线程持有时,新发出的请求的线程才会被放入队列中。

      等执行加锁操作时,公平性将由于在挂起线程和恢复线程时存在的开销而极大的降低性能。在大多数情况下,非公平性锁的性能要高于公平性锁。


      在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:在恢复一个被挂的线程与该线程真正开始运行之间存在着严重的延迟。假设线程A持有一个铺,并且线程B青求这个锁。由于这个锁已被线程A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因北会再次尝试获取锁。 与此同时,如果C也请求这个锁,那么C很可能会在B被完全嗅醒之前获得、使用以及释放这个锁。这样的情况是一种“双赢”的局面:B获得锁的时刻并没有推迟,C更早的获得锁,并且吞吐量也获得了提高。


13.4 在synchronized和ReentrantLock之间进行选择


      在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的、可轮询的与可中断的锁获取操作,公平队列已经非块结构的锁。否则,还是应该优先使用synchronized。


13.5 读写锁


当访问以读操作为主的数据结构时,它能提高程序的可伸缩性。


        ReadWriteLock lock = new ReentrantReadWriteLock();
        Lock writeLock = lock.writeLock();
        Lock readLock = lock.readLock();
        writeLock.lock();
        try {
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }


第14章 构建自定义的同步工具


14.1 状态依赖的管理


通常,如果线程在休眠或者被阻塞时持有一个锁,那么这通常是一种不好的做法,因为只要线程不释放这个锁,有些条件就永远无法成真。


“条件队列”这个名字来源:它使得一组线程(称之为等待线程的集合)能够通过某种方式来等待特定的条件变为真。传统队列的元素是一个个数据,而与之不同的是,条件队列中的元素是一个个正在等待相关条件的线程。


14.2 使用条件队列


14.2.1 条件谓语


条件谓语是使某个操作称为状态依赖操作的前提条件。在阻塞队列中,只有当队列不为空 时,take方法才能执行,否则必须等待。对take方法来说,它的条件谓词就是“队列不空”。


每一次wait调用都会隐式的域特定的条件谓语关联起来。当调用某个特定条件谓词的wait时,调用者必须已经持有与条件队列相关的锁,并且这个锁必须保护这构成条件谓词的状态变量。



14.2.2 过早唤醒


当使用条件等特时(例如Object.wait或Condition.await):


●通常都有一个条件谓词一 包括一 些对象状态的测试,线程在执行前必须首先通过这些测试。

●在调用wait之前测试条件谓词,并且从wait中返回时再次进行测试。

●在一个循环中调用wait。

●确保使用与条件队列相关的锁来保护构成条件谓词的各个状态变量。

●当调用wait、 notify或notifyAll等方法时,一定要持有与条件队列相关的锁。

●在检查条件谓词之后以及开始执行相应的操作之前,不要释放锁。


14.2.3 丢失的信号


只有同时满足以下两个条件时,才能使用单一的notify而不是notifyAll:


所有等待线程的类型相同。只有一个条件谓语与条件队列相关,并且每个线程在从wait返回后将执行相同的操作。


单进单出。 在条件变量上每次通知,最多只能唤醒一个线程来执行。


14.3 显式的Condition对象


在Condition对象中,与wait、notify和notifyAll方法对应的分别是await、signal和signalAll。但是,Condition对Object进行可扩展,因此也包含wait、notify和notifyAll方法。一定要使用正确的版本。


14.5 AbstractQueuedSynchronizer(AQS)(☆☆☆☆☆)


很重要,但是书中将的很粗略


14.6 java.util.concurrent同步器类中的AQS(☆☆☆☆☆)


ReentrantLock

Semaphore与CountDownLatch

FutureTask

ReentrantReadWriteLock


第15章 原子变量与非阻塞同步机制


15.1 锁的劣势


1)如果有多个线程同时请求锁,那么一些线程将被挂起并且稍后恢复运行。当线程恢复时,必须等待其他线程执行完他们的时间片以后,才能被调度使用。在挂起和恢复线程等过程中存在着很大的开销,并且通常存在着较长时间的中断。


2)当一个线程正在等待锁时,它不能做任何事情。如果一个持有锁的线程被延迟执行(例如发生了缺页错误、调度延迟等),那么所有需要这个锁的线程都无法执行下去。


3)如果被阻塞的线程的优先级较高,而持有锁的线程的优先级较低,那么这将是一个严重的问题-----优先级反转。


15.2 硬件对并发的支持


CAS的主要缺点:使调用者处理竞争问题(通过重试、回退、放弃),而在锁中能自动处理竞争问题(线程在获得锁之前一直阻塞)。


在大多数处理器上,在无竞争的锁获取和释放的“快速代码路径”上的开销,大约是CAS开销的两倍。


15.3 原子变量类


AtomicInteger、AtomicLong、AtomicReference、AtomicReferenceFieldUpdater、AtomicStampedReference、AtomicMarkableReference


锁与原子变量在不同竞争程度上的性能差异很好得说明了各自的优势和劣势。在中低程度的竞争下,原子变量能提供更高的可伸缩性。在高强度的竞争下,锁能干有效的避免竞争。


15.4 非阻塞算法


创建非阻塞算法的关键在于:找出如何将原子修改的范围缩小到单个变量上,同时还要维护数据的一致性。


第16章 Java内存模型(JMM)


此内容参考《深入理解java虚拟机》


1.jpg


Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能得到一致效果的机制及规范。目的是解决由于多线程通过共享内存进行通信时,存在的原子性、可见性(缓存一致性)以及有序性问题。


原子性


线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。所以在多线程场景下,就会发生原子性问题。因为线程在执行一个读改写操作时,在执行完读改之后,时间片耗完,就会被要求放弃CPU,并等待重新调度。这种情况下,读改写就不是一个原子操作。即存在原子性问题。


缓存一致性(可见性)


在多核CPU,多线程的场景中,每个核都至少有一个L1 缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行,则每个核心都会在各自的caehe中保留一份共享内存的缓冲。由于多核是可以并行的,可能会出现多个线程同时写各自的缓存的情况,而各自的cache之间的数据就有可能不同。


在CPU和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核CPU中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致。


有序性


除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如load->add->save 有可能被优化成load->save->add 。这就是有序性问题。


Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。


总结


1)总结的不是很到位,有些没看懂就省略了。


2)学习知识是一个潜移默化的过程,学完后也许看不出成果。


3)年轻人的世界没有容易的,每天进步,足矣。


参考


《java并发编程实战》


2.jpg



目录
相关文章
|
3月前
|
Java 编译器 开发者
深入理解Java内存模型(JMM)及其对并发编程的影响
【9月更文挑战第37天】在Java的世界里,内存模型是隐藏在代码背后的守护者,它默默地协调着多线程环境下的数据一致性和可见性问题。本文将揭开Java内存模型的神秘面纱,带领读者探索其对并发编程实践的深远影响。通过深入浅出的方式,我们将了解内存模型的基本概念、工作原理以及如何在实际开发中正确应用这些知识,确保程序的正确性和高效性。
|
1月前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
40 0
|
3月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
108 2
|
2天前
|
存储 缓存 Java
Java中的分布式缓存与Memcached集成实战
通过在Java项目中集成Memcached,可以显著提升系统的性能和响应速度。合理的缓存策略、分布式架构设计和异常处理机制是实现高效缓存的关键。希望本文提供的实战示例和优化建议能够帮助开发者更好地应用Memcached,实现高性能的分布式缓存解决方案。
29 9
|
1月前
|
Java
Java基础却常被忽略:全面讲解this的实战技巧!
本次分享来自于一道Java基础的面试试题,对this的各种妙用进行了深度讲解,并分析了一些关于this的常见面试陷阱,主要包括以下几方面内容: 1.什么是this 2.this的场景化使用案例 3.关于this的误区 4.总结与练习
|
2月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
259 6
|
1月前
|
Java 程序员
Java基础却常被忽略:全面讲解this的实战技巧!
小米,29岁程序员,分享Java中`this`关键字的用法。`this`代表当前对象引用,用于区分成员变量与局部变量、构造方法间调用、支持链式调用及作为参数传递。文章还探讨了`this`在静态方法和匿名内部类中的使用误区,并提供了练习题。
47 1
|
2月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
85 7
|
2月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
3月前
|
存储 消息中间件 安全
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
【10月更文挑战第9天】本文介绍了如何利用JUC组件实现Java服务与硬件通过MQTT的同步通信(RRPC)。通过模拟MQTT通信流程,使用`LinkedBlockingQueue`作为消息队列,详细讲解了消息发送、接收及响应的同步处理机制,包括任务超时处理和内存泄漏的预防措施。文中还提供了具体的类设计和方法实现,帮助理解同步通信的内部工作原理。
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)