JUC

0.JUC的简介

  • 在Java5.0中提供了java.util.concurrent(简称JUC)包,在此包中增加了很多在并发编程中很实用的类,用于定义类似线程的自定义子系统,包括线程池,异步IO,轻量级任务框架。提供可调的,灵活的线程池。还提供了设计用于多线程上下文中的Collection实现等。

1.volatile关键字-内存可见性

1.1 volatile关键字

  • 保证内存可见性
  • 禁止内存指令重排
  • 不保证原子性(线程操作时没有互斥性,可以多个线程同时操作)

1.2 volatile关键字的demo

/**
 *
 * 本测试用来测试votalile关键字的内存可见性
 * @author
 * @version 1.00
 * @time 2020/3/12 0012  下午 3:56
 */
public class VolatileTest {

    public static void main(String[] args) {
        //创建myThread对象,启动线程
        MyThread myThread = new MyThread();
        new Thread(myThread).start();
        while (true){
            //如果为flase则中断
            if(myThread.getFlag()){
                System.out.println("主线程循环中断=======");
                break;
            }
        }

    }
}

//定义MyThread线程类
class MyThread implements Runnable{

    //定义全局变量flag,设置初始值为false.并实现get,set方法
    private Boolean flag = false;

    //实现run方法
    @Override
    public void run() {

        //线程睡0.3秒后将flag改为true
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;

        System.out.println("flag="+flag);
    }


    public Boolean getFlag() {
        return flag;
    }

    public void setFlag(Boolean flag) {
        this.flag = flag;
    }
}
控制台输出:
flag=true  main方法的while循环没中断,说明main方法的线程么有获取到flag变量已变为true

解释:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-htCMRdMu-1584702325287)(.\jucimages\volatile缓存.jpg)]

上面图片可以看着一个简单的jvm内存模型,在主存存储flag的值为false,其中的flag变量的内存变化如下:

  • 自定义线程先从主存中读取flag的值到自己的缓存中,读取完成后将值改为true后,在将值写回主存中,写回后打印,flag == true。
  • 在自定义线程未修改flag值前,main线程读取flag的值为flase,然后开始执行while循环,因为while循环执行效率特别高,main线程没有空闲去重新读取主存中的flag值,一直使用自己缓存中的值,这样造成内存不可见。

1.3 volatile的内存可见性

内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。注意,一个读一个写操作时出现。

。我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的 volatile 变量。

demo:

/**
 *
 * 本测试用来测试votalile关键字的内存可见性  共享变量加volatile解决内存可见性问题
 * @author
 * @version 1.00
 * @time 2020/3/12 0012  下午 3:56
 */
public class VolatileTest2 {

    public static void main(String[] args) {
        //创建myThread对象,启动线程
        MyThread1 myThread1 = new MyThread1();
        new Thread(myThread1).start();
        while (true){
            //如果为flase则中断
            if(myThread1.getFlag()){
                System.out.println("主线程循环中断=======");
                break;
            }
        }

    }
}
控制台输出:
主线程循环中断=======
flag=true

测试完成,,添加volatile关键字解决了共享数据的可见性问题。内存可见性问题出现在两个线程一个读一个写的情况下,如果两个都是写操作就要考虑原子性。

1.4 volatile关键字的特性

  • 保证内存可见性
  • 禁止指令重排
  • 不保证原子性

2.原子变量与CAS算法

2.1 原子性和原子变量

2.1.1 原子性

原子性是指操作上不可分割,就是一个操作无论有几步都要同时执行完成,不可分割。

  • 常见的i++操作就不具备原子性。

    • i = 10 ;i = i++;此时打印i,i的值为10,此时i++操作分为三步,
    • 1,将i的值赋值给临时变量 int tem = 10;
    • 2,i = i + 1;
    • 3, 将tmp的值赋值给i, i = tmp,此时i的值为10,这就是i++的底层操作,分为3步。
  • 下面用例子测试不用原子类和用原子变量类进行多线程的i++操作。

public class AtomicClassTest {

    public static void main(String[] args) {
        MyThreadDemo myThreadDemo = new MyThreadDemo();
        for (int i = 0; i <= 10; i++) {
            //创建10个线程,共享MyThreadDemo
            new Thread(myThreadDemo).start();
        }

    }

}


class MyThreadDemo implements Runnable {

    private int i = 0;

    //实现run方法
    @Override
    public void run() {

        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //调用i++并打印值
        System.out.println("此时i的值为" + iPlusPlust());

    }

    //i++fangf
    public int iPlusPlust() {
        return i++;
    }
}
输出为:
此时i的值为0
此时i的值为7
此时i的值为6
此时i的值为4
此时i的值为5
此时i的值为1
此时i的值为3
此时i的值为2
此时i的值为0
此时i的值为1
此时i的值为8
此时i为1出现两次,出现线程安全问题。

2.1.2 原子变量

  • 在java.util.current.atomic包下面有常用的原子类,原子类的操作具有原子性

  • demo

    /**
     * atomic原子变量类的测试
     * @author
     * @version 1.00
     * @time 2020/3/13 0013  下午 12:09
     */
    public class AtomicClassTest2 {
    
        public static void main(String[] args) {
            MyThreadDemo1 myThreadDemo = new MyThreadDemo1();
            for (int i = 0; i <= 10; i++) {
                //创建10个线程,共享MyThreadDemo
                new Thread(myThreadDemo).start();
            }
    
        }
    
    }
    
    
    class MyThreadDemo1 implements Runnable {
    
        private AtomicInteger ato = new AtomicInteger();
    
        //实现run方法
        @Override
        public void run() {
    
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //调用i++并打印值
            System.out.println("返回的结果值为:" + iPlusPlus());
    
        }
    
        //i++fangf
        public int iPlusPlus() {
            return ato.getAndIncrement();
        }
    }
    运行结果为:
    返回的结果值为:0
    返回的结果值为:2
    返回的结果值为:1
    返回的结果值为:3
    返回的结果值为:4
    返回的结果值为:5
    返回的结果值为:6
    返回的结果值为:10
    返回的结果值为:8
    返回的结果值为:9
    返回的结果值为:7
    解决了i++操作的原子性问题,,即线程安全问题。2.1.3 原子变量的简单原理2.1.3 原子变量简单原理
    

2.1.3 原子变量类简单原理

  • 原子变量类内部变量用volatily关键字进行修饰,保证内存可见性。
  • 原子变量类内部采用了CAS算法,(compare-and-swap)比较和替换,CAS 是一种无锁的非阻塞算法的实现.
    • 第一步,读取要更改的变量的值;
    • 第二步,将读取的值和预估值(也称为旧值)进行比较;(二,三步同时进行)
    • 第三步,如果读取值和预估值一致则特换为新值,如果不一致则不替换(不一致则说明数值已经被更改了)。
    • cas算法效率比锁高的原因是:cas算法的线程和线程等待不同,cas算法不会失去cpu的占用权,效率高。

3.ConcurrentHashMap锁分段机制

3.1 HashTable的线程安全问题

  • HashTable是一个Java.util包为我们提供的一个线程安全的map。但是它在进行复合操作时任然有线程安全问题,例如:

    HashTable ht = new HashTable();
    //如果不存在则添加
    if(ht.contants("a")){
    	ht.add("a");//因为HashTable的各个方法是用Synchronized修饰的,每个方法各自加锁,线程1判断完包含后,向map里面添加数据前,线程二往map里面添加了同样的数据,这样判断已经不成立了,出现多线程操作问题。
    

3.2 ConcurrentHashMap

  • ConcurrentHashMap 同步容器类是Java 5 增加的一个线程安全的哈希表。对于多线程的操作,介于HashMap 与 Hashtable 之间。内部采用“锁分段”机制替代 Hashtable 的独占锁。进而提高性能。
  • java.util.concurrent包还提供了设计用于多线程上下文中的 Collection 实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet。当期望许多线程访问一个给定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap,ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList。

3.3 CopyOnWriteArrayList

  • 在Java中可以使用Collections.synchronizedList(new ArrayList )方法将一个ArrayList包装成线程安全的List。

    但是这个List在进行迭代时任然是不安全的。因为如果看SynchronizedList源码可以看出执行add()等方法的时候是加了synchronized关键字的,但是listIterator(),iterator()却没有加.所以在使用的时候需要加synchronized.

  • 此时可以同过CopyOnWriteArrayList进行替换,解决线程安全问题

    public class ConcurrentHashMapTest {
    
        public static void main(String[] args) {
            MyCurrentDemo myCurrentDemo = new MyCurrentDemo();
    
            for(int i=0; i<=10; i++){
                new Thread(myCurrentDemo).start();
            }
        }
    
    }
    
    
    class MyCurrentDemo implements Runnable {
    
        //定义全局变量
        private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    
        //初始化list
        static {
            list.add("A");
            list.add("B");
            list.add("C");
            list.add("D");
            list.add("E");
        }
    
    
        @Override
        public void run() {
            //边遍历边添加
            Iterator<String> iterator = list.iterator();
    
            while (iterator.hasNext()) {
                System.out.println(iterator.next());
                //往list里面添加元素,不是iterator里面
                list.add("AAA");
            }
        }
    }
    打印正常,如果将CopyOnWriteArrayList改为Collections.synchronizedList(new ArrayList)则报java.util.ConcurrentModificationException异常。
    

4.CountDownLatch

  • CountDownLatch 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

  • 闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行;

    • 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
    • 确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
    • 等待直到某个操作所有参与者都准备就绪再继续执行。
    public class CountDownLatchTest2 {
    
        public static void main(String[] args) {
    
            CountDownLatch countDownLatch = new CountDownLatch(10);
            MyDemo1 myDemo = new MyDemo1(countDownLatch);
    
            long start = System.currentTimeMillis();
    
            //开启5个线程
            for(int i=0; i<10; i++){
                new Thread(myDemo).start();
            }
    
            //进行等待,线程运行结束后才释放
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            long end = System.currentTimeMillis();
    
            System.out.println("运行的时间:"+(end - start));
        }
    }
    
    
    /**
     *
     * 线程类  打印0-5000以内的整数
     */
    class MyDemo1 implements Runnable{
    
    
        private CountDownLatch latch;
    
        public MyDemo1(CountDownLatch latch) {
            this.latch = latch;
        }
    
        @Override
        public void run() {
    
            //加锁 防止线程安全问题
            synchronized (this){
                try {
                    for (int i=0; i<=5000; i++){
                        if(i%2 == 0){
                            System.out.println(Thread.currentThread().getName()+"打印数字"+i);
                        }
                    }
                } finally {
                    //latch必须减一,所以放在finally中
                    latch.countDown();
                }
    
            }
        }
    }
    

5.0 生产者消费者模式

5.1使用synchronized关键字和wait(),notifyAll()方法实现

/**
 *
 * 生产者 消费者模式
 * @author
 * @version 1.00
 * @time 2020/3/14 0014  下午 6:58
 */
public class CustomerTest {

    public static void main(String[] args) {

        Store store = new Store();

        new Thread(new customer(store),"消费者A").start();
        new Thread(new productor(store), "生产者B").start();

    }

}


/**
 * 生产者 和 消费者 之间的桥梁。存储商品最多为10个
 */
class Store{

    private Integer store = 0;

    //生产商品 同步方法
    public synchronized void producter(){

        while (store >= 10){
            System.out.println(Thread.currentThread().getName()+"商品库存已满");
            //超过10个停止生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //继续生产
        System.out.println(Thread.currentThread().getName()+"生产商品,此时商品为:"+ ++store);

        //唤醒其他线程
        this.notifyAll();
    }


    //消费商品 同步方法
    public synchronized void customer(){

        while (store <= 0){
            System.out.println(Thread.currentThread().getName()+"此时商品库存为0");
            //库存少于0个停止消费
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //进行消费
        System.out.println(Thread.currentThread().getName() +"消费商品,库存为:"+ --store);
        this.notifyAll();;
    }

}


/**
 * 消费者
 */
class customer implements Runnable{

    private Store store;
    //构造器
    public customer(Store store) {
        this.store = store;
    }


    @Override
    public void run() {
        //调用生产方法,生成二十个
        for(int i=0; i<=20; i++){
            store.customer();
        }
    }
}

/**
 * 生产者
 */
class productor implements Runnable{

    private Store store;

    //构造器
    public productor(Store store) {
        this.store = store;
    }


    @Override
    public void run() {
        for(int i=0; i<=20; i++){
            store.producter();
        }
    }
}
  • 注意的是,,在Store类中的消费和生产方法的条件判断都是While判断,此处不可以使用if判断,如果if判断会造成虚假唤醒!!

5.2 使用Condition和ReentrantLock实现,生产者消费者模式

5.2.1 ReentrantLock

  • 在 Java 5.0 之前,协调共享对象的访问时可以使用的机制只有 synchronized 和 volatile 。Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。
  • ReentrantLock 实现了 Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。

5.2.2 Condition

  • Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
  • 在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll。
  • Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,请使用newCondition() 方法。
/**
 * 生产者 消费者模式 使用ReentrantLock和Condition实现
 *
 * @author
 * @version 1.00
 * @time 2020/3/14 0014  下午 6:58
 */
public class CustomerTest2 {

    public static void main(String[] args) {

        Store1 store = new Store1();

        new Thread(new Customer1(store), "消费者A").start();
        new Thread(new Productor1(store), "生产者B").start();

    }

}


/**
 * 生产者 和 消费者 之间的桥梁。存储商品最多为10个
 */
class Store1 {

    private Integer store = 0;

    private Lock lock = new ReentrantLock();

    Condition condition = lock.newCondition();

    //生产商品 同步方法
    public void producter() {
        lock.lock();

        try {
            while (store >= 10) {
                System.out.println(Thread.currentThread().getName() + "商品库存已满");
                //超过10个停止生产
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //继续生产
            System.out.println(Thread.currentThread().getName() + "生产商品,此时商品为:" + ++store);
            //唤醒其他线程
            condition.signalAll();
        } finally {
            //解锁
            lock.unlock();
        }
    }


    //消费商品 同步方法
    public void customer() {
        lock.lock();
        try {
            while (store <= 0) {
                System.out.println(Thread.currentThread().getName() + "商品库存缺货");
                //库存少于0个停止消费
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //进行消费
            System.out.println(Thread.currentThread().getName() + "消费商品,此时商品为:" + --store);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

}


/**
 * 消费者
 */
class Customer1 implements Runnable {

    private Store1 store;

    //构造器
    public Customer1(Store1 store) {
        this.store = store;
    }


    @Override
    public void run() {
        //调用生产方法,生成二十个
        for (int i = 0; i <= 20; i++) {
            store.customer();
        }
    }
}

/**
 * 生产者
 */
class Productor1 implements Runnable {

    private Store1 store;

    //构造器
    public Productor1(Store1 store) {
        this.store = store;
    }


    @Override
    public void run() {
        for (int i = 0; i <= 20; i++) {
            store.producter();
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值