读书笔记 | Java并发编程实战

摘要:
阅读笔记| Java并发编程实践I,基础知识1.线程安全2.什么是线程安全3.非原子64位操作4.易失性5.发布和转义6.并发容器6-1.ConcurrentHashMap6-2.CopyOnWriteArrayList6-3.阻塞队列和生产者消费者模式6-4.同步工具类6-4-1。阻塞6-4-2。未来任务6-4-4。信号量6-4-4。围栏循环
读书笔记 | Java并发编程实战

一、基础知识

1. 线程安全性

线程安全的代码,核心在于对状态访问操作的管理特别是共享和可变状态的管理

  • 对象的状态:存储在状态变量(如实例或静态域)中的数据
  • 共享意味着变量可以由多个线程同时访问
  • 可变意味着变量的值会发生改变

当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变量的访问.
Java常见的同步机制:
* synchronized
* volatile
* 显示锁(Explicit Lock)
* 原子变量

2. 什么是线程的安全性

线程的安全性就是当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么这个类就是线程安全的.

3. 非原子的64位操作

  • Java内存模型要求,变量的读取和写入操作都必须是原子操作,但对于非volatile类型的long和double变量,JVM允许将64位的读操作或写操作分为两个32位的操作.当读取一个非volatile类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么很可能会读到某个值的高32位和另一个值得低32位.因此,即使不考虑失效数据问题,在多线程中使用共享且可变的long和double等类型的变量也是不安全的,除非用关键字volatile来声明它们,或者用锁保护起来
  • 如何复现问题,64位系统上也会有这种问题吗
    • 64位系统中没有这个问题

4. volatile

  • volatile变量用来确保将变量的更新操作通知到其他线程.当把变量声明为volatile类型后,编译器和运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序.volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方.因此在读取volatile变量时,总是返回最新写入的值.
  • volatile变量对可见性的影响:
    • 当线程A首先写入一个volatile变量并且线程B随后读取该变量时,在写入volatile变量之前所有对A可见的所有变量的值,在B读取了volatile变量后,对B也是可见的.
  • volatile变量的正确使用方式包括
    • 确保它们自身状态的可见性
    • 确保它们所引用对象的状态的可见性
    • 作为一些事件的开关.

5. 发布与逸出

  • 发布:
    • 将一个指向该对象的引用保存到其他代码能访问的地方
    • 或者在一个非私有的方法中返回该引用
    • 或者将一个引用传递到其他类的方法中
  • 逸出:
    • 当某个不该发布的对象被发布时,这种情况就是逸出

6. 并发容器

6-1. ConcurrentHashMap

  • 内部结构
  • add
  • get
  • size

6-2. CopyOnWriteArrayList

  • 用途:在一些读操作远大于写操作的情况下,才可以使用写入时复制容器
    • 在事件通知系统中,在分发通知时,需要迭代已注册的监听器链表,在大多数情况下,注册和注销事件监听器的操作远小于接收事件的操作.

6-3. 阻塞队列和生产者消费者模式

  • 阻塞队列提供了可阻塞的put和take方法,以及支持定时的offer和poll方法.

6-4. 同步工具类

6-4-1. 闭锁

  • 闭锁是一种同步工具类,可以延迟线程的进度知道其达到终止状态.
  • 闭锁可以用来确保某些活动直到其他活动都完成后才继续执行
    • 确保某个计算所需要的资源都初始化后才开始执行(资源初始化)
    • 确保某个服务所依赖的其他服务都启动后才启动(服务依赖)
    • 确保某个操作的所有操作者都就绪后再继续执行
  • CountDownLatch
    • CountDownLatch(int)
    • await():void
    • await(long,TimeUnit):boolean
    • countDown():void
// 在计时测试中使用CountDownLatch来启动和停止线程
public long timeTasks(int nThreads, Runnable task) throws InterruptedException {
    final CountDownLatch startGate = new CountDownLatch(1);
    final CountDownLatch endGate = new CountDownLatch(nThreads);
    for (int i = 0; i < nThreads; i++) {
        Thread t = new Thread(() -> {
            try {
                //线程启动后都在这里等待startGate变为0
                startGate.wait(); 
                try {
                    task.run();
                } finally {
                    //任务运行完,endGate减一
                    endGate.countDown(); 
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t.start();
    }
    long start = System.nanoTime();
    startGate.countDown(); //所有线程开始任务
    endGate.wait();  //等待所有线程执行完成
    long end = System.nanoTime();
    return end - start;
}

6-4-2. FutureTask

  • FutureTask实现了Future语义,表示一种抽象的可生成结果的计算.FutureTask表示的计算是通过Callable来实现的,相当于一种可生成结果的Runnable,并且可以处于一下三种状态:等待运行,正在运行,运行完成.运行完成表示计算的所有可能结束方式,包括正常结束,由于取消而结束和由于异常而结束等.当FutureTask进入完成状态后,它会永远停止在这个状态上.
  • FutureTask的用途:
    • 在Executor框架中表示异步任务
    • 还可以用来表示一些时间较长的计算,这些计算可以在使用计算结果之前启动.通过提前启动计算,可以减少等待结果时需要的时间.
  • FutureTask的问题
    • Callable表示的任务可以抛出受检查的或不受检查的异常,这些异常被封装到ExecutionException中,并在Future.get中被重新抛出,这将使得调用get的代码变得复杂,因为它要对不同的异常进行不同的处理.
// 使用FutureTask来提前加载稍后需要的数据
public class N5_5_12Proloader {
    private final FutureTask<ProductInfo> future = new FutureTask<>(ProductInfo::new);
    private final Thread thread = new Thread(future);
    public void start() {
        thread.start();
    }
    public ProductInfo get() throws InterruptedException {
        try {
            return future.get();
        } catch (ExecutionException e) {
            System.out.println("初始化ProductionInfo发生错误");
            return null;
        }
    }
}
class ProductInfo {
}

6-4-3. 信号量Semaphore

  • 计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个操作的数量.计数信号量还可以用来实现某种资源池,或者对容器施加边界.
  • Semaphore
    • public Semaphore(int permits)
    • public Semaphore(int permits, boolean fair)
    • public void acquire() throws InterruptedException
    • public void release()

6-4-4. 栅栏CyclicBarrier

  • CyclicBarrier
    • public CyclicBarrier(int parties)
    • public CyclicBarrier(int parties, Runnable barrierAction)
    • public int await()
  • 等到设定的n个线程都到达了指定位置后在再同时继续往下执行,CyclicBarrier在初始化时还可以设置Runnable的action,最后一个到达指定位置的线程会去运行这个action

6-5. 构建高效且可伸缩的结果缓存

  • 场景:假设有个函数 value = fun(key),这个计算过程需要消耗一定的时间和资源,现在想要将计算的结果缓存下来,下次再计算同一个key时可以从缓存中直接获取value.
  • 思路:可以使用map类将key和value缓存起来,每次计算key的值时,先看map中有没有这个key对应的value,如果有,直接返回,如果没有,计算结果并存入map中.
  • 其中的坑:
    • 涉及到多线程,要使用ConcurrentHashMap,确保get和set时的线程安全
    • 因为计算需要消耗一定时间,如果一个线程在计算key的时候,另一个线程也来请求计算key,这个时候因为第一个线程的计算结果没出来,所以map中是空的,这时候第二个线程会再去计算.
  • 解决办法:map中不保存key和value的键值对,而是保存key和Future,其中Future中在计算value的值,通过future.get()方法,如果计算完成了直接返回value的值,如果计算还没结束,会阻塞一直等到它计算完成并返回.还需要注意的是,需要使用map.putIfAbsent(key,future)方法存入key和future,因为判断key是否存在和放入key不是原子操作.
二、结构化并发引应用程序

1. 任务执行

免责声明:文章转载自《读书笔记 | Java并发编程实战》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇学习WPF——了解WPF中的XAMLSeleniumServer3.0下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

关于利用RD client远程电脑,和输入法的一些问题

我在寝室,利用转接头,借助手机app“RDclient”成功完成在键盘上输入文字,并在电脑上输出内容。“RDclient”可以完成手机端远程连接电脑端,只需要知道电脑的ip,用户名和密码就可以。“RDclient”这是微软自家发布的软件,还是很不错的。不过,在刚开始连接的时候,输入法遇到一点问题。后来,在手机上,调出输入法打字的界面,更换成英文输入模式,然后...

数据不平衡的相关

大多数常见的机器学习算法不能很好地处理不平衡的数据集。例如,搜索引擎的点击预测(点击页面往往占很小的比例)、电子商务中的产品推荐(正在购买的推荐产品的比例很低)、信用卡欺诈检测、网络攻击识别、癌症检测等。处理数据不平衡的方法主要有以下几种。2.数据级别2.1重新采样2.1.1欠采样(下采样)欠采样通过减少丰富类的大小来平衡数据集。它试图通过增加稀有样本的数量...

如何下载Chrome离线版EXE安装文件和MSI版安装文件

对于Chrome的稳定版本(官方版本),您只需添加“?”在Chrome的“最终用户许可协议”页面上的链接之后?Standalone=1对于Beta版和开发版Chrome,只需记住以下地址:http://dl.google.com/chrome/install/{versionnumber}/crome_安装程序中的版本号。exe表示要下载的Chrome版本号...

iview表格动态数据实现合并功能

需求原型:代码实现:html part:从'../../libs/c导入{MsgType,PublicType}...

JavaScript算法学习:获取字符串最后一位方法及判断是否以指定字符串开始或结尾

Str.substr,其中start是必需的参数,表示坐标的起始位置。正值在正方向计数,负值在反方向计数,长度是可选参数,表示从起始位置开始计数的数字。...

winform窗体(六)——DataGridView控件及通过此控件中实现增删改查

“,”Delete Data“,btn)==DialogResult.Yes){}V.多条件查询。如果用户没有输入任何内容或文本框为空,则查询所有内容。//设置两个常量条件stringtj1=”1=1“;stringtj2=”1=1”;//根据用户的输入更改条件。//如果用户输入名称If(name!=”“){tj1=“Namelike@name“;}//如果...