文章目录
1. 简介
a.线程使用不当的影响
多线程涉及线程之间的调度、CPU上下文的切换、线程的创建销毁,若不能有效利用,则降低性能
b.concurrent
在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的Collection 实现等。
2. volatile 关键字-内存可见性
a.内存可见性
public class TestVolatile {
public static void main(String[] args) {
/*线程修改flag的值*/
ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo).start();
/*main线程读取flag的值*/
while (true){
if (threadDemo.isFlag()){
System.out.println("-------------");
break;
}
}
}
}
class ThreadDemo implements Runnable{
private boolean flag=false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag=true;
System.out.println("flag="+isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
运行:

原因:

while(true)调用系统底层的代码,执行速度非常快,以至于没有机会再次读取一次数据。
结果:
当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。
可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
通过同步来保证对象被安全地发布。除此之外使用一种更加轻量级的 volatile 变量。
b.volatile 关键字


添加synchronized同步锁,刷新内存,然后加锁,因为在程序运行时,首先需要判断锁,若发现有其他线程占有锁,形成阻塞,挂起,需要等待下次CPU分配再来执行,所以效率低。
Java 提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程,调用内存栅栏,将缓存中的数据及时刷新。可以将 volatile 看做一个轻量级的锁,但是又与锁有些不同:
对于多线程,不是一种互斥关系;不能保证变量状态的“原子性操作”;



添加volatile关键字后,可视为直接对主存中的数据进行操作
3. 原子变量-CAS算法
/* i++ 的原子性问题
* int i=10; i=i++; //10
* i++计算机底层执行 int temp=1; i=i+1; i=temp;
* i++实际上分为三个步骤 “读 改 写"
* */
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo atomicDemo = new AtomicDemo();
for(int i=0;i<10;i++){
new Thread(atomicDemo).start();
}
}
}
class AtomicDemo implements Runnable{
private int serialNumber=0;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSerialNumber());
}
public int getSerialNumber() {
return serialNumber++;
}
}

原子变量:1.volatile保证内存可见性 2.CAS算法保证数据原子性

import java.util.concurrent.atomic.AtomicInteger;
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo atomicDemo = new AtomicDemo();
for(int i=0;i<10;i++){
new Thread(atomicDemo).start();
}
}
}
class AtomicDemo implements Runnable{
private AtomicInteger serialNumber=new AtomicInteger();
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSerialNumber());
}
public int getSerialNumber() {
return serialNumber.getAndIncrement();
/* getAndIncrement 后加加
* incrementAndGet 前加加*/
}
}
a.CAS 算法
CAS (Compare-And-Swap) 硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。CAS 是一种无锁的非阻塞算法的实现。
CAS 包含了 3 个操作数: 内存值 V ;预估值 A ;更新值 B。 当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。
若不成功,CAS不会放弃CPU的执行权,会立即尝试更新,缺点在于需要自定义失败后的方法。
public class TestCompareAndSwap {
public static void main(String[] args) {
CompareAndSwap compareAndSwap = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int value = compareAndSwap.get();
boolean b = compareAndSwap.compareAndSet(value, (int) (Math.random() * 101));
System.out.println(b);
}
}).start();
}
}
}
/*jvm模拟*/
class CompareAndSwap {
private int value;
/*获取内存值*/
public synchronized int get() {
return value;
}
/*比较*/
public synchronized int compareAndSwap(int expectValue, int newValue) {
int oldValue = value;
if (oldValue == expectValue) {
this.value = newValue;
}
return oldValue;
}
/*设置*/
public synchronized boolean compareAndSet(int expectValue, int newValue) {
return expectValue == compareAndSwap(expectValue, newValue);
}
}
b.原子变量
类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可
将 volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类。
类 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对
相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。
AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 类进一步扩展了原子操
作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方
面也引人注目,这对于普通数组来说是不受支持的。 核心方法:boolean compareAndSet(expectedValue, updateValue) java.util.concurrent.atomic 包下提供了一些原子操作的常用类: AtomicBoolean 、AtomicInteger 、AtomicLong 、 AtomicReference
AtomicIntegerArray 、AtomicLongArray
AtomicMarkableReference
AtomicReferenceArray
AtomicStampedReference
4. ConcurrentHashMap 锁分段机制
a.HashTable与HashMap的区别
hashTable方法均为同步,实现同步的方法是synchronized,hashMap是非同步的,但也可以使用Collections类中的静态方法使其变得同步。因此在单线程程序中hashMap的效率要高于hashTable,如果在多线程程序中,可以考虑使用hashTable;
hashTable不允许有null的键和值,hashMap允许有空的键和值;
hashMap的遍历是使用方法keySet()返回一个保存有所有键的Set视图,然后用迭代器进行遍历,采用的是fail-fast机制。hashTable的遍历可以使用elements()返回值的枚举,也可以使用entrySet()keySet()values()返回键的Collection视图。
b.Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。
ConcurrentHashMap 同步容器类是Java 5 增加的一个线程安全的哈希表。对与多线程的操作,介于 HashMap 与 Hashtable 之间。内部采用“锁分段”机制替代 Hashtable 的独占锁,进而提高性能。
此包还提供了设计用于多线程上下文中的 Collection 实现:
ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、
CopyOnWriteArrayList 和 CopyOnWriteArraySet。当期望许多线程访问一个给
定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap,
ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远
大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList。
5. CountDownLatch 闭锁
a.概述
CountDownLatch 作为一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:
CountDownLatch 中有一个计数器字段,在对象创建时初始化,表示需要等待的事件数量。countDown() 方法递减计数器,表示有一个事件已经发生了,await() 方法等待计数器为 0 时,表示所有的需要等待的时间都已经发生。如果计数器的值非 0 会一直阻塞直到计数器为 0。
b.运用场景
确保某个计算在其需要的所有资源都被初始化之后才继续执行;
确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
等待直到某个操作所有参与者都准备就绪再继续执行
c.代码示例
import java.util.concurrent.CountDownLatch;
public class TestCountDownLatch {
public static void main(String[] args) {
CountDownLatch latch= new CountDownLatch(5);
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for(int i=0;i<5;i++){
new Thread(ld).start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("执行时间"+(end-start));
}
}
class LatchDemo implements Runnable{
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
synchronized (this){
try {
for(int i=0;i<50000;i++){
if(i%2==0){
System.out.println(i);
}
}
}finally {
latch.countDown();
}
}
}
}
CountDownLatch只提供一个构造器,count的值不能小于0,否则抛出异常

countDown() 将 count值减1

调用await() 方法的线程将会被挂起,直到count值变为0才会继续执行

若等待一定时间后,count值仍然没有变为0则不再等待继续执行

6. 实现 Callable 接口
a.概述
Callable 接口类似于 Runnable但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
Callable 需要依赖FutureTask ,FutureTask 可用作闭锁。
b.步骤
第一步:创建Callable子类的实例化对象
第二步:创建FutureTask对象,并将Callable对象传入FutureTask的构造方法中
(FutureTask实现了Runnable接口和Future接口)

第三步:实例化Thread对象,并在构造方法中传入FurureTask对象
第四步:启动线程
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestCallable {
public static void main(String[] args) {
ThreadDemo1 threadDemo1=new ThreadDemo1();
/*执行Callable方式,需要FutureTask实现类的支持,用于接收结果*/
FutureTask<Integer> result=new FutureTask<>(threadDemo1);
new Thread(result).start();
try {
Integer sum=result.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo1 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=0;i<=100;i++){
sum+=i;
}
return sum;
}
}
7. Lock 同步锁
a.概述
在 Java 5.0 之前,协调共享对象的访问使用的机制只有 synchronized 和 volatile,synchronized真正起作用的是锁对象,作为隐式监视器锁,并不明确在哪里加锁,在哪里释放锁 。Java 5.0 后增加了新的机制,但并不是一种替代内置锁的方法,而是当内
置锁不适用时,作为一种可选择的高级功能。
ReentrantLock 实现了 Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。
b.代码示例
通过lock()上锁,通过unlock()释放锁
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicket implements Runnable {
private int ticket=18;
ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"卖出第"+ticket--+"张票");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
return;
}
}finally {
lock.unlock();
}
}
}
}
class T {
public static void main(String[] args) {
Runnable r = new SaleTicket();
new Thread(r, "窗口1").start();
new Thread(r, "窗口2").start();
new Thread(r, "窗口3").start();
}
}
相对于synchronized,使用显式锁Lock所需的代码多一些,但如果在使用synchronized关键字时,失败了,那么就会抛出一个异常。但是你没有机会去做清理工作为维护系统使其处于良好的状态。有了显式的Lock对象,可使用finally子句。
8. Condition 控制线程通信
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll。
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,使用其 newCondition() 方法。
502

被折叠的 条评论
为什么被折叠?



