《JAVA并发编程实战》——线程安全

书中的定义:

当多个线程访问一个类时,如果不考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步以及在调用方代码不必作其他协调,这个类的行为依然正确,那么就称这个类是线程安全的

视频给出的定义:

当多个线程访问一个类时,不管运行环境采用何种调度方式,或者这些进程如何交替执行,并且在主调度代码中不需要额外的同步或协同,这个类行为都表现出正确的行为,那么称为这个类为线程安全的。

其实仔细一看,两个定义差不多,所谓的线程安全,都是对类而言的,如果多个线程同时访问这个类,这个类响应的结果都和预期的一样,那么说明这个线程是安全。

线程的安全性体现在三个方面:原子性、可见性、有序性

  • 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作。

  • 可见性:一个线程对主内存的修改可以及时的被其他线程看到

  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。

原子性

JDK中atomic包

原子性是怎么实现的呢,在jdk中有一个Atomic包是保存可以确保线程的安全性

看下面的例子:

package com.mmall.concurrency.example.count;

import com.mmall.concurrency.annoations.ThreadSafe;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

import java.util.concurrent.atomic.AtomicInteger;

@ThreadSafe

public class CountExample2 {

// 请求总数

public static int clientTotal = 5000;

// 同时并发执行的线程数

public static int threadTotal = 200;

public static AtomicInteger count = new AtomicInteger(0);

public static void main(String[] args) throws Exception {

ExecutorService executorService = Executors.newCachedThreadPool();

final Semaphore semaphore = new Semaphore(threadTotal);

final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);

for (int i = 0; i < clientTotal ; i++) {

executorService.execute(() -> {

try {

semaphore.acquire();

add();

semaphore.release();

} catch (Exception e) {

log.error(“exception”, e);

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

System.out.println(count.get());

}

privat
作者徽是vip1024c
e static void add() {

count.incrementAndGet();

// count.getAndIncrement();

}

}

其实可以发现和文中最开始给出的例子想要做的事是一样的,只不过这里的count是AtomicInteger 类型的,也就是atomic包下的封装类。

public static AtomicInteger count = new AtomicInteger(0);

然后在count++ 操作时变成

count.incrementAndGet();

这个很有意思,这个才是确保这个类是线程安全的关键,可以看一下源码

public final int incrementAndGet() {

return unsafe.getAndAddInt(this, valueOffset, 1) + 1;

}

可以看到,调用了unsafe.getAndAddInt(this, valueOffset, 1) 方法,往下一层

public final int getAndAddInt(Object var1, long var2, int var4) {

int var5;

do {

var5 = this.getIntVolatile(var1, var2);

} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;

}

可以看到getAndAddInt方法的具体实现,var1为当前的对象,var2是当前count的值,var4是要加值,这里每次都是加1,所以var4的值为1.

var5的值是取主内存中当前count值,compareAndSwapInt()方法的关键是,如果从主内存中取出的当前count值var5与传入的当前count值var2不一致,那么就会重新取主内存count的值var5,只有当主内存的count值和当前count值相同时,才会将执行var5=varchat+var4操作。最后返回var5的值,这样就确保了每次在执行加一操作的时候得到的当前的count值都是最新的。

可以看到真正的核心就是compareAndSwapInt()方法啦,这就是是automic确保线程安全的CAS方法的具体实现。

另外再拓展一点:automic包下的AtomicLong类。看类名可以知道和AtomicInteger类一样,只是不同数据对应不同数据类型。但是在java8中新增一种类LongAdder.这个类和AtomicLong实现的功能是一样的,那为什么要在java8中新增一个LongAdder来替代AtomicLong呢。

刚刚上面讲了CAS的具体实现是通过循环一直判断,只有当值一样时候才会执行后续操作,低并发的时候还好,但是高并发的时候就会导致循环的次数增多,总而导致性能降低,使用LongAdder类,在低并发的时候和AtomicLong保持一直,但是在高并发的时候通过分散将压力分散到各个节点,总而提高高并发时的性能(具体的源码我也没有看,所以也是听被人说的这个,有兴趣的同学可以自己研究下)。但是LongAdder有个致命的缺点,就是虽然提高了性能,但是有的时候结果会出现偏差(通过离散模型统计的,在统计的时候,如果有并发更新,就可能会出现误差),导致如果需要结果是准确无误且唯一的时候最好使用AtomicLong。

在提一下atomic包下atomicStampReference类,这个类是解决CAS的ABA问题(是指一个线程操作某个值的时候,其他线程先修改了一次,然后又修改成原来的值。这样当前线程就会认为这个没有任何操作),但是实际上这个值是进行了2次操作的,值是把值改回去了,那怎么解决ABA问题呢,atomicStampReference类在线程操作某个值的时候,不仅会判断值,还会判断当前的版本号是否一致。一致才能进行下一步操作。线程在进行写或修改操作时会进行版本号加一。这样就能规避掉ABA问题了。

在看一下AtomicBoolean类。这个多线程调用类类让方法执行一次,也就是说比如5000个线程同时访问,但是只会有一个线程执行这个方法,其他的线程都不执行。

看一下代码:

package com.mmall.concurrency.example.atomic;

import com.mmall.concurrency.annoations.ThreadSafe;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

import java.util.concurrent.atomic.AtomicBoolean;

@ThreadSafe

public class AtomicExample6 {

private static AtomicBoolean isHappened = new AtomicBoolean(false);

// 请求总数

public static int clientTotal = 5000;

// 同时并发执行的线程数

public static int threadTotal = 200;

public static void main(String[] args) throws Exception {

ExecutorService executorService = Executors.newCachedThreadPool();

final Semaphore semaphore = new Semaphore(threadTotal);

final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);

for (int i = 0; i < clientTotal ; i++) {

executorService.execute(() -> {

try {

semaphore.acquire();

test();

semaphore.release();

} catch (Exception e) {

e.printStackTrace();

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

System.out.println(isHappened.get());

}

private static void test() {

if (isHappened.compareAndSet(false, true)) {

System.out.println(“execute”);

}

}

}

结果:表明test()方法只执行了一次的。

学习路线:

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成黑客大神,这个方向越往后,需要学习和掌握的东西就会越来越多以下是网络渗透需要学习的内容:
在这里插入图片描述

随着多核处理器的普及,使用并发成为构建高性能应用程序的关键。Java 5以及6在开发并发程序取得了显著的进步,提高了Java虚拟机的性能,提高了并发类的可伸缩性,并加入了丰富的新并发构建块。在本书中,这些便利工具的创造者不仅解释了它们究竟如何工作、如何使用,同时,还阐释了创造它们的原因,及其背后的设计模式。 本书既能够成为读者的理论支持,又可以作为构建可靠的,可伸缩的,可维护的并发程序的技术支持。本书并不仅仅提供并发API的清单及其机制,本书还提供了设计原则,模式和思想模型,使我们能够更好地构建正确的,性能良好的并发程序。 本书的读者是那些具有一定Java编程经验的程序员、希望了解Java SE 5,6在线程技术上的改进和新特性的程序员,以及Java并发编程的爱好者。 目录 代码清单 序 第1章 介绍 1.1 并发的(非常)简短历史 1.2 线程的优点 1.3 线程的风险 1.4 线程无处不在 第1部分 基础 第2章 线程安全 2.1 什么是线程安全性 2.2 原子性 2.3 锁 2.4 用锁来保护状态 2.5 活跃度与性能 第3章 共享对象 3.1 可见性 3.2 发布和逸出 3.3 线程封闭 3.4 不可变性 3.5 安全发布 . 第4章 组合对象 4.1 设计线程安全的类 4.2 实例限制 4.3 委托线程安全 4.4 向已有的线程安全类添加功能 4.5 同步策略的文档化 第5章 构建块 5.1 同步容器 5.2 发容器 5.3 阻塞队列和生产者一消费者模式 5.4 阻塞和可中断的方法 5.5 Synchronizer 5.6 为计算结果建立高效、可伸缩的高速缓存 第2部分 构建并发应用程序 第6章 任务执行 6.1 在线程中执行任务 6.2 Executor 框架 6.3 寻找可强化的并行性 第7章 取消和关闭 7.1 任务取消 7.2 停止基于线程的服务 7.3 处理反常的线程终止 7.4 JVM关闭 第8章 应用线程池 8.1 任务与执行策略问的隐性耦合 8.2 定制线程池的大小 8.3 配置ThreadPoolExecutor 8.4 扩展ThreadPoolExecutor 8.5 并行递归算法 第9章 GUI应用程序 9.1 为什么GUI是单线程化的 9.2 短期的GUI任务 9.3 耗时GUI任务 9.4 共享数据模型 9.5 其他形式的单线程子系统 第3部分 活跃度,性能和测试 第10章 避免活跃度危险 第11章 性能和可伸缩性 第12章 测试并发程序 第4部分 高级主题 第13章 显示锁 第14章 构建自定义的同步工具 第15章 原子变量与非阻塞同步机制 第16章 Java存储模型 附录A 同步Annotation 参考文献 索引
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值