JUC

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() 方法。

9. 线程按序交替

10. ReadWriteLock 读写锁

11. 线程八锁

12. 线程池

13. 线程调度

14. ForkJoinPool 分支/合并框架 工作窃取

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

但行益事莫问前程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值