JUC-学习笔记

传统的多线程开发方式

implements Runnable

 public class oldThreadDev implements Runnable {
    @Override
    public void run() {
        System.out.println("传统多线程基于implements Runnable");
    }
}

调用方式

    /**
     * Description:  传统的多线程开发方式
     */
    public void oldThreadDev() {
        new Thread(new oldThreadDev()).start();
    }

或者extend Thread

多线程并发会引起一些问题

传统的方案:synchronized

class TicketByNowMoreThread {
    //50张票
    private int number = 50;

    //买票的方式  传统的实现限制多线程并发问题->synchronized
    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "票;剩余" + number + "张");
        }
    }
    //锁:对象锁,class锁
}

模拟多线程抢票

 /**
     * Description: 传统的多线程  synchronized
     * 线程就是单独的资源,没有任何附属的操作{属性和方法}
     */
    public void synchronizedThread() {
        //并发:多线程操作同一个资源类,把资源类丢入线程 ||不需要资源类去实现或者继承达到多线程
        TicketByNowMoreThread ticket = new TicketByNowMoreThread();
        //  Runnable -> @FunctionalInterface  函数式接口 lambda表达式(parsm)->{逻辑代码}

        //模拟并发||多个线程同时跑
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                ticket.sale();
            }
        }, "AThread").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                ticket.sale();
            }
        }, "BThread").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                ticket.sale();
            }
        }, "CThread").start();
    }

或许你还听说Lock:


//多线程买票JUC->Lock 就三步:第一步new一个JUC下的lock锁,第二步加锁,第三步解锁
class TicketByJUCLockThread {
    //50张票
    private int number = 50;

    /**
     * Description: ReentrantLock 默认有两个构造函数:参数非公平(NonfairSync)和公平锁(FairSync)
     * public ReentrantLock() { sync = new ReentrantLock.NonfairSync(); }
     * public ReentrantLock(boolean fair) { sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync(); }
     */

    //JUC下的->可重入锁
    Lock lock = new ReentrantLock();

    //买票的方式  传统的实现限制多线程并发问题->synchronized
    public void sale() {
        //多线程抢占资源之前加锁 lock.lock();
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "票;剩余" + number + "张");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //多线程抢占资源完成之后解锁lock.unlock();
            lock.unlock();
        }
    }
    //锁:对象锁,class锁
}

Lock锁就是JUC下的接口,它的主要是用的实现类就是ReentrantLock

Lock和同步关键字的区别

package com.gton.juc;

import java.util.concurrent.TimeUnit;

/**
 * @description: 锁是什么,八个锁是什么
 * @author: GuoTong
 * @createTime: 2021-09-19 14:48
 * @since JDK 1.8 OR 11
 **/
@SuppressWarnings("all")
public class LockISWhat {

    public static void main(String[] args) {

        //静态同步方法锁的是class对象 ||普通同步方法锁的是调用对象本身


    }

    public void synchronizedStaticAndNo() {
        //一个同步普通方法,一个同步静态方法,静态锁的是class,普通是锁的是调用对象
        //不是同一个东西,等待的继续等待,不等待的直接执行,不是通一把锁
        Phone phone = new Phone();
        new Thread(() -> {
            phone.callStatic();
        }, "A").start();
        try {
            //睡眠:20秒:一般睡眠都是JUC下的TimeUnit
            TimeUnit.SECONDS.sleep(1);

        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            phone.sendMsg();
        }, "B").start();
    }

    public void synchronizedLockObjStaticToNoOne() {
        //如果是static修饰的同步方法;它本身锁的也是同一个东西,
        // 不过不是调用对象,而是惟一的class对象。
        // 即使不同的实例对象调用也是得前一个锁抢到释放后才能执行下一个
        Phone phone01 = new Phone();
        Phone phone02 = new Phone();
        new Thread(() -> {
            phone01.callStatic();
        }, "A").start();
        try {
            //睡眠:20秒:一般睡眠都是JUC下的TimeUnit
            TimeUnit.SECONDS.sleep(1);

        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            phone02.sendMsgStatic();
        }, "B").start();
    }

    public void synchronizedLockObjStatic() {
        //如果是static修饰的同步方法;它本身锁的也是同一个东西,
        // 不过不是调用对象,而是惟一的class对象。
        Phone phone = new Phone();
        new Thread(() -> {
            phone.callStatic();
        }, "A").start();
        try {
            //睡眠:20秒:一般睡眠都是JUC下的TimeUnit
            TimeUnit.SECONDS.sleep(1);

        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            phone.sendMsgStatic();
        }, "B").start();
    }

    public void synchronizedLockObjNotOne() {
        //为什么A里面睡还是外面睡都是A先执行呢,是因为有锁的概念,
        //synchronized锁 的是当前调用对象phone,谁先拿到谁执行。这两个都是当前一个对象。

        // A没释放就轮不到B,如果换成不同的对象调用就不存在锁的竞争和等待。
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone1.call();
        }, "A").start();

        try {
            //睡眠:20秒:一般睡眠都是JUC下的TimeUnit
            TimeUnit.SECONDS.sleep(1);

        } catch (Exception e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone2.sendMsg();
        }, "B").start();


        //这个不同步的没有锁,就会立即执行
        new Thread(() -> {
            phone1.noSynFunction();
        }, "B").start();
    }

    public void synchronizedLockObj() {
        //为什么A里面睡还是外面睡都是A先执行呢,是因为有锁的概念,
        //synchronized锁 的是当前调用对象phone,谁先拿到谁执行。这两个都是当前一个对象。

        // A没释放就轮不到B,如果换成不同的对象调用就不存在锁的竞争和等待。
        Phone phone = new Phone();
        new Thread(() -> {
            phone.call();
        }, "A").start();
        try {
            //睡眠:20秒:一般睡眠都是JUC下的TimeUnit
            TimeUnit.SECONDS.sleep(1);

        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            phone.sendMsg();
        }, "B").start();
    }

}

class Phone {

    public synchronized void sendMsg() {
        try {
            //睡眠:20秒:一般睡眠都是JUC下的TimeUnit
            TimeUnit.SECONDS.sleep(2);

        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static synchronized void callStatic() {
        System.out.println("打电话");
    }

    public static synchronized void sendMsgStatic() {
        try {
            //睡眠:20秒:一般睡眠都是JUC下的TimeUnit
            TimeUnit.SECONDS.sleep(2);

        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call() {
        System.out.println("打电话");
    }

    public void noSynFunction() {
        System.out.println("非同步方法");
    }
}

生产者and消费者

package com.gton.juc;

/**
 * @description:生产者and消费者 老版本的wait和notify+synchronized:实现
 * 六字真言{等待-业务-通知}
 * @author: GuoTong
 * @createTime: 2021-09-16 20:17
 * @since JDK 1.8 OR 11
 **/
public class SCZAndXFZ {
    public static void main(String[] args) {
        AData aData = new AData();
        /**
         * Description: 两个线程貌似没问题:但是4个线程8个线程就不安全了
         * @author: GuoTong
         */
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    aData.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    aData.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        //多个线程的时候,超过两个:B=>-1
        // wait和notify如果是if的话就是会虚假唤醒,所以多个线程就不能用if只配一个同步字段,得内外都加上同步
        // 或者使用while
        //B=>-2 B=>-3 C=>-4 C=>-5 C=>-6 C=>-7 C=>-8 C=>-9 C=>-10 C=>-11 C=>-12 A=>-11
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    aData.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    aData.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();


    }
}

class AData {

    private int num = 0;

    //num+1
    public synchronized void increment() throws InterruptedException {
        //if在同步方法中使用wait和notify会产生虚假唤醒,推荐使用while。
        // if (num != 0) {
        while (num != 0) {
            //等待
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "=>" + num);
        //通知我加一完成
        this.notifyAll();
    }

    //num-1
    public synchronized void decrement() throws InterruptedException {
        //if在同步方法中使用wait和notify会产生虚假唤醒,推荐使用while。
        //if (num == 0) {
        while (num == 0) {
            //等待
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "=>" + num);
        //通知 我减一完成
        this.notifyAll();
    }
}

JUC下的生产者和消费者

package com.gton.juc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @description: JUC实现生产者和消费者JUC->
 * Lock-synchronized和Condition(await,signal)-(Objiect)Wait Or Notify
 * Condition的出现肯定不是和原来一样的操作,那就出现新的东西没意思。所以他肯定有优势和补充
 * condition可以精准的唤醒和通知对应的线程。可以让指定的进程等待和唤醒
 * @author: GuoTong
 * @createTime: 2021-09-17 21:15
 * @since JDK 1.8 OR 11
 **/
public class JUCToSCZAndXFZ {

    public static void main(String[] args) {

        BData jucaData = new BData();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    jucaData.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AThread").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    jucaData.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BThread").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    jucaData.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CThread").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    jucaData.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "DThread").start();
    }
}

class BData {

    private int num = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();


    //num+1
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            //if在同步方法中使用wait和notify会产生虚假唤醒,推荐使用while。
            // if (num != 0) {
            while (num != 0) {
                //等待
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            //通知我加一完成
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //num-1
    public void decrement() throws InterruptedException {
        //if在同步方法中使用wait和notify会产生虚假唤醒,推荐使用while。
        //if (num == 0) {
        lock.lock();
        try {
            while (num == 0) {
                //等待
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            //通知 我减一完成
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

指定唤醒线程使用的方法

package com.gton.juc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @description: JUC实现生产者和消费者JUC->指定唤醒版
 * Lock-synchronized和Condition(await,signal)-(Objiect)Wait Or Notify
 * Condition的出现肯定不是和原来一样的操作,那就出现新的东西没意思。所以他肯定有优势和补充
 * condition可以精准的唤醒和通知对应的线程。可以让指定的进程等待和唤醒
 * @author: GuoTong
 * @createTime: 2021-09-17 21:15
 * @since JDK 1.8 OR 11
 **/
public class JUCToSCZAndXFZ02 {

    public static void main(String[] args) {

        CData jucaData = new CData();
        //condition指定唤醒的线程执行:A调完就调B,B调完就调C
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    jucaData.printA();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AThread").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    jucaData.printB();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BThread").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    jucaData.printC();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CThread").start();

    }
}

class CData {

    private Lock lock = new ReentrantLock();
    //精准唤醒某个线程
    private Condition condition01 = lock.newCondition();
    private Condition condition02 = lock.newCondition();
    private Condition condition03 = lock.newCondition();
    private volatile int number = 1; // 1唤醒A,2唤醒B,3唤醒C


    //num+1
    public void printA() throws InterruptedException {
        lock.lock();
        try {
            //if在同步方法中使用wait和notify会产生虚假唤醒,推荐使用while。
            // if (num != 0) {
            while (number != 1) {
                //等待
                condition01.await();
            }
            number = 2;
            System.out.println(Thread.currentThread().getName() + "=>被唤醒" + number);
            //通知我加一完成:指定通知condition02唤醒
            condition02.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //num-1
    public void printB() throws InterruptedException {
        //if在同步方法中使用wait和notify会产生虚假唤醒,推荐使用while。
        //if (num == 0) {
        lock.lock();
        try {
            while (number != 2) {
                //等待
                condition02.await();
            }
            number = 3;
            System.out.println(Thread.currentThread().getName() + "=>被唤醒" + number);
            //通知 我减一完成
            condition03.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //num-1
    public void printC() throws InterruptedException {
        //if在同步方法中使用wait和notify会产生虚假唤醒,推荐使用while。
        //if (num == 0) {
        lock.lock();
        try {
            while (number != 3) {
                //等待
                condition03.await();
            }
            number = 1;
            System.out.println(Thread.currentThread().getName() + "=>被唤醒" + number);
            //通知 我减一完成
            condition01.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}


集合的安全

LIST-ArrayLis

    public static void main(String[] args) {
        //多线程下的ArrayList 安全安全问题
   /*
        ArrayList不安全 -
        解决方案一:Vector  线程安全的集合 不推荐
        解决方案二:工具类Collections支持同步list
        解决方案三:JUC下的CopyOnWriteArrayList
         */
        ListByJUC listByJUC = new ListByJUC();
        listByJUC.JUCSynList();

    }

    public void NoSynArrayList() {
        List<String> list = new ArrayList<>();//
        //Arrays.asList("1", "2", "3");  UnsupportedOperationException 不支持操作异常

        //ConcurrentModificationException 并发修改异常
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                list.forEach(System.out::print);
            }, String.valueOf(i)).start();
        }
    }

    public void CollectsSynList() {
         /*
        ArrayList不安全 -解决方案一:Vector  线程安全的集合 不推荐
        工具类Collections支持同步list
         */
        List<String> list1 = Collections.synchronizedList(new ArrayList<>());
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                list1.add(UUID.randomUUID().toString().substring(0, 5));
                list1.forEach(System.out::print);
            }, String.valueOf(i)).start();
        }
    }

    public void JUCSynList() {
        //CopyOnWriteArrayList JUC安全list  底层是数组 加了两个关键字:transient volatile
        List<String> list = new CopyOnWriteArrayList<>();
        //CopyOnWriteArrayList 写入时复制,读写分离,读取时固定的,写入覆盖(),写入时覆盖造成数据有问题
        //比vector牛逼在哪里:vector  synchronized  效率低下
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }

Set 安全类

package com.gton.juc;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

/**
 * @description: JUC或者同步的set集合
 * @author: GuoTong
 * @createTime: 2021-09-19 16:15
 * @since JDK 1.8 OR 11
 **/
public class SetByJUC {
    public static void main(String[] args) {
        //HashSet 线程不安全 :它的底层是HashMap,
        // 本质不重复就是使用了HashMap的key。
        //解决方法
        /*
         * 1.Collections下:Set<String> set = Collections.synchronizedSet(new HashSet<>());
         * 2.JUC下:Set<String> objects = new CopyOnWriteArraySet<>();
         * */
        Set<String> set = new HashSet<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }

    }
}

Map 安全类

package com.gton.juc;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @description:Map下的安全问题
 * @author: GuoTong
 * @createTime: 2021-09-19 16:21
 * @since JDK 1.8 OR 11
 **/
@SuppressWarnings("uncheck")
public class MapByJUC {
    public static void main(String[] args) {
        //map是这样的么?默认等价于什么?
        // 不是,默认等价于加载因子0.75和初始容量  1<<4 = 16 {new HashMap<>(16,0.75);
        Map<String, Object> map = new HashMap<>();
        //底层特别重要的两个值就是:加载因子和初始容量
        /*
        解决方式:
            1. Collections.synchronizedMap()
            2. new ConcurrentHashMap<>()
         */
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }

    }
}

前面说了Lock,再说一个更细粒度的读写锁

package com.gton.juc;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @description: 读写锁
 * @author: GuoTong
 * @createTime: 2021-09-19 17:58
 * @since JDK 1.8 OR 11
 **/
public class ReadWriteLockDemo {
    public static void main(String[] args) throws InterruptedException {
        MyDiyCache myDiyCache = new MyDiyCache();
        Semaphore semaphore = new Semaphore(5);
        semaphore.acquire();
        //写入
        for (int i = 0; i < 5; i++) {
            final int key = i;
            new Thread(() -> {
                myDiyCache.add(String.valueOf(key), key << 2);
            }, String.valueOf(i)).start();
        }
        semaphore.release();//保证写完了在读,否则读到未写的就是null
        //读取
        for (int i = 0; i < 5; i++) {
            final int key = i;
            new Thread(() -> {
                myDiyCache.get(String.valueOf(key));
            }, String.valueOf(i)).start();
        }
    }

}

class MyDiyCache {
    //自定义缓存
    private volatile Map<String, Object> map = new HashMap<>();
    //读写锁:更加细粒度的控制||写入的时候希望同时一个人写,读的时候可以同时没人都读
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    //存- 写
    public void add(String key, Object value) {
        //开启写锁
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入key" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入key" + key + "OK");
        } finally {
            lock.writeLock().unlock();
        }

    }

    //读 - 取
    public void get(String key) {
        //开启写锁
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取成功" + o);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }

    }

}

JUC下的信号量

package com.gton.juc;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @description: 信号量
 * @author: GuoTong
 * @createTime: 2021-09-19 17:48
 * @since JDK 1.8 OR 11
 **/
public class SemaphoreDemo {

    public static void main(String[] args) {

        try {
            //默认传入线程数量
            Semaphore semaphore = new Semaphore(3);
            for (int i = 0; i < 6; i++) {
                new Thread(() -> {
                    //并发限流的时候用得到
                    try {
                        //获得许可,如果满了就等到释放为止再得到
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "抢到车位");
                        TimeUnit.SECONDS.sleep(2);
                        System.out.println(Thread.currentThread().getName() + "离开车位");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        //释放许可
                        semaphore.release();
                    }

                }, String.valueOf(i)).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

JUC的计数器,加法计数器和减法计数器

package com.gton.juc;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;

/**
 * @description: JUC计数器CountDownLatch减法计数器
 * @author: GuoTong
 * @createTime: 2021-09-19 17:36
 * @since JDK 1.8 OR 11
 **/
public class CountDownLatchDemo {
    public static void main(String[] args) {
        CountDownLatch downLatch = new CountDownLatch(6);
        try {
            for (int i = 0; i < 6; i++) {
                new Thread(() -> {
                    System.out.println(Thread.currentThread().getName() + "走了");
                    downLatch.countDown();//开启计数 ,减法计数-1
                }, String.valueOf(i)).start();
            }
            downLatch.await();//等待计数器归零然后向下执行
            System.out.println("没人了关门");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

/**
 * Description:加法计数器
 *
 * @author: GuoTong
 * @date: 2021-09-19 17:42:10
 * @return:
 */
class CyclicBarrierDemo {
    public static void main(String[] args) {

        //集齐七颗龙珠召唤神龙
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙成功");
        });
        //召唤龙族的线程
        for (int i = 0; i < 7; i++) {
            final int temp = i;//在lambda表达式使用
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集了" + temp + "龙珠");
                try {
                    cyclicBarrier.await();//直到手机七个就召唤神龙
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

CallAble

package com.gton.juc;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * @description: CallAble多线程
 * @author: GuoTong
 * @createTime: 2021-09-19 16:38
 * @since JDK 1.8 OR 11
 **/
public class CallAbleJUC {

    public static void main(String[] args) {
        //CallAble 类似于Runnable ,它可以抛出异常,可以返回值,可以方法不同不是run方法,这边是call

        //传统的方式Runnable
        new Thread(new RunnableAbleTest()).start();
        try {
            //CallAble方式 不能直接被Thread执行,只能和Runnable挂钩,
            // 但是不直接和Runnable挂钩,和它的实现类FutureTask有挂钩

            //CallAble实现类
            CallAbleTest callAbleTest = new CallAbleTest();
            //需要适配类
            FutureTask futureTask = new FutureTask<>(callAbleTest);

            new Thread(futureTask, "A").start();
            new Thread(futureTask, "B").start();//结果是被缓存的
            //获取线程体的返回结果
            Object o = futureTask.get();//这个方法可能产生阻塞,把它放到最后一行,或者使用异步通信来处理
            System.out.println(o);

        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}

class RunnableAbleTest implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable-run");
    }
}

class CallAbleTest implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("CallAble - Call()");
        return "CallAble Back-String";
    }
}

JMM内存模型多线程问题

JAVA内存模型,不存在的东西,是一个概念,也是一个约定!

关于JMM的一些同步的约定:

1、线程解锁前,必须把共享变量立刻刷回主存;

2、线程加锁前,必须读取主存中的最新值到工作内存中;

3、加锁和解锁是同一把锁;

线程中分为 工作内存、主内存

工作内存-主内存:8组操作

8种操作:

  • Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
  • Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
  • assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
  • store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
  • write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
  • lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

在这里插入图片描述

JMM对这8种操作给了相应的规定:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

Volatile 是 Java 虚拟机提供 轻量级的同步机制

1、保证可见性
2、不保证原子性
3、禁止指令重排

img

package com.gton.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description: JMM-Java内存模型
 * @author: GuoTong
 * @createTime: 2021-10-21 11:04
 * @since JDK 1.8 OR 11
 **/
public class JMMDemo {

    private static int num = 0;

    private volatile static int numVolatile = 0;

    public static void main(String[] args) throws InterruptedException {
        //问题
        NoSeeNum();
        //解决线程之间的变量互相不知道,加上关键字 volatile修饰主内存变量 ,保证线程可见性
        volatileSeeNum();

    }

    public static void NoSeeNum() throws InterruptedException {
        new Thread(() -> {
            while (num == 0) {

            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(1);
        num = 1;

        //明明num在主内存已经变为1,但是线程A还是没有停,说明A线程不知道主线程的num已经变化
        System.out.println(num);
    }

    public static void volatileSeeNum() throws InterruptedException {
        new Thread(() -> {
            while (numVolatile == 0) {

            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(1);
        numVolatile = 1;

        //明明num在主内存已经变为1,但是线程A还是没有停,说明A线程不知道主线程的num已经变化
        System.out.println(numVolatile);
    }
}

volatile不保证原子性》+原子操作包

//volatile 不保证原子性:每次执行不一样。可以用synchronized/Lock修饰需要原子操作的方法
class TestVolatileNoA {
    private volatile static int num = 0;

    //synchronized 虽然保证了原子性,但是太重了
    public synchronized static void add() {
        num++;
    }

    public static void main(String[] args) {
        //如果准确应该是2w
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2) {//main+gc ,程序的最小线程是两个,如果比两个多说明还有其他线程在执行存活
            Thread.yield();//线程礼让让线程直到余下两个为止
        }

        System.out.println(Thread.currentThread().getName() + ":" + num);
    }
}

//使用volatile,不使用synchronized/Lock保证原子操作的方法
class TestVolatileNoB {


    private volatile static int num = 0;
    //Atomic,全是原子性包
    private volatile static AtomicInteger Anum = new AtomicInteger();

    //synchronized 虽然保证了原子性,但是太重了
    public static void add() {
        num++; //反编译之后查看一行代码为什么不能保证原子性:其实它有三步:1,获取这个值,2,执行操作+1,3,写回结果值
    }

    public static void isQuestionCaoZuo() {
        //如果准确应该是2w
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
    }


    //Atomic原子包下的包装类:保证了原子性,
    public static void AtomicIntegerAdd() {
        Anum.getAndIncrement();
    }

    public static void NoQuestionCaoZuo() {
        //如果准确应该是2w
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    AtomicIntegerAdd();
                }
            }).start();
        }
    }

    public static void main(String[] args) {
        //isQuestionCaoZuo(); //volatile 不保证原子性
        NoQuestionCaoZuo();//原子类操作包下的保证原子性(CAS) +volatile 保证线程可见 
        while (Thread.activeCount() > 2) {//main+gc ,程序的最小线程是两个,如果比两个多说明还有其他线程在执行存活
            Thread.yield();//线程礼让让线程直到余下两个为止
        }

        System.out.println(Thread.currentThread().getName() + ":" + num);
    }
}

指令重排序

/**源代码:–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
 * 处理器在进行指令重排的时候,会考虑数据之间的依赖性!
*/

int x=1; //1

int y=2; //2

x=x+5; //3

y=x*x; //4

//我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成2134 1324

//可不可能是 4123? 不可能的

内存屏障:CPU指令。作用:

1、保证特定的操作的执行顺序;

2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)

内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
内存屏障有两个作用:

1.阻止屏障两侧的指令重排序;
2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

  • 对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
  • 对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。

  1. LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  2. StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  3. LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  4. StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能

彻底玩转volatile单例模式

饿汉式,DCL饿汉式,饱汉式。

package com.gton.signle;

/**
 * @description: 静态单例模式
 * @author: GuoTong
 * @createTime: 2021-10-21 13:52
 * @since JDK 1.8 OR 11
 **/
public class StaticSignle {


    //静态内部类实现单例模式:但是也不是安全,因为反射可以获取
    private static class InnerSignel {
        private static final StaticSignle SIGNLE = new StaticSignle();
    }

    private StaticSignle() {

    }

    public StaticSignle getStaticSignle() {
        return InnerSignel.SIGNLE;
    }
}
package com.gton.signle;

/**
 * @description: 懒汉单例模式多线程下突破:双重监测锁实现
 * @author: GuoTong
 * @createTime: 2021-10-21 13:46
 * @since JDK 1.8 OR 11
 **/
public class LazyManAddOne {
    private LazyManAddOne() {

    }

    //懒汉模式-多线程安全,双重校验锁 ;指令重排不安全
    private volatile static LazyManAddOne lazyManAddOne;

    public static LazyManAddOne getHungry() {
        if (lazyManAddOne == null) {
            synchronized (LazyManAddOne.class) {
                if (lazyManAddOne == null) {
                    lazyManAddOne = new LazyManAddOne(); //指令重排不安全,加上volatile
                    //但是依然不安全 ,不是原子操作:
                    //1.分配内存空间,
                    //2.执行构造方法,初始化对象
                    //3.把对象指向这个空间
                }
            }
        }

        return lazyManAddOne;
    }
}
package com.gton.signle;

/**
 * @description: 懒汉式单例模式
 * @author: GuoTong
 * @createTime: 2021-10-21 13:43
 * @since JDK 1.8 OR 11
 **/
public class LazyMan {
    private LazyMan() {

    }

    private static LazyMan lazyMan;

    //单线程下的懒汉模式这样没问题,但是多线程下可以
    public LazyMan getLazyMan() {
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}
package com.gton.signle;

/**
 * @description:饿汉单例模式
 * @author: GuoTong
 * @createTime: 2021-10-21 13:41
 * @since JDK 1.8 OR 11
 **/
public class Hungry {

    private Hungry() {

    }

    //饿汉式就是直接一上来就直接创建了单例模式
    private final static Hungry HUNGRY = new Hungry();


    public static Hungry getHungry(){
        return HUNGRY;
    }
}

package com.gton.signle;

/**
 * @description: 解决反射破坏单例模式
 * @author: GuoTong
 * @createTime: 2021-10-21 13:58
 * @since JDK 1.8 OR 11
 **/
public class GoodSignle {

    //标志位,无论是new的还是调反射创建都是改变标志位:但是反射就是道高一尺魔高一丈
    private static boolean gtFlag = false;

    private GoodSignle() {
        //解决反射强制破坏单例模式
        synchronized (GoodSignle.class) {
            //第一次创建正常
            if (!gtFlag) {
                gtFlag = true;
            } else {
                throw new RuntimeException("禁止使用反射破坏单例模式");

            }

        }
    }

    //懒汉模式-多线程安全,双重校验锁 ;指令重排不安全
    private volatile static GoodSignle lazyManAddOne;

    public static GoodSignle getHungry() {
        if (lazyManAddOne == null) {
            synchronized (LazyManAddOne.class) {
                if (lazyManAddOne == null) {
                    lazyManAddOne = new GoodSignle();
                }
            }
        }

        return lazyManAddOne;
    }
}
package com.gton.signle;

import com.sun.javafx.runtime.eula.Eula;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @description: JDK防止反射破坏单例模式的:枚举
 * @author: GuoTong
 * @createTime: 2021-10-21 14:06
 * @since JDK 1.8 OR 11
 **/
public enum EnumSingleSceraty {

    INSTANCE;

    public EnumSingleSceraty getInstance() {
        return INSTANCE;
    }


}

class TestEnumSingle {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingleSceraty instance = EnumSingleSceraty.INSTANCE;
        //获取对象
        Class<EnumSingleSceraty> sceratyClass = EnumSingleSceraty.class;
        Constructor<EnumSingleSceraty> constructor = sceratyClass.getConstructor(null);
        Constructor<EnumSingleSceraty> constructor1 = sceratyClass.getConstructor(String.class, int.class);
        EnumSingleSceraty newInstance = constructor.newInstance();
        System.out.println(instance);
        System.out.println(newInstance); //NoSuchMethodException
        System.out.println(constructor1); //不支持反射破解异常
    }
}

深入理解CAS

CAS :比较并交换

package com.gton.juc;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description: CAS
 * @author: GuoTong
 * @createTime: 2021-10-21 14:22
 * @since JDK 1.8 OR 11
 **/
public class CASDemo {


    public static void main(String[] args) {

        //compareAndSwap :比较并交换
        AtomicInteger atomicInteger = new AtomicInteger(2021);
        //CAS:自旋锁->比较并交换,如果是2021,就把它替换为2022
        boolean isSuccess = atomicInteger.compareAndSet(2021, 2022);
        System.out.println(isSuccess);

        //前面改了后面就失败
        boolean isSuccess2 = atomicInteger.compareAndSet(2021, 2022);
        System.out.println(isSuccess2);

        //CAS:比较当前工作内存中的值和主内存中的值,如果这个值是预期的,那就执行操作,否则就一致循环自旋等到预期
        //缺点:循环会耗时,一次性只能保证一个共享变量的原子性,ABA问题


        //ABA问题  狸猫换太子,S:开始状态女友没怀孕,B:让女友出轨怀孕了,让他然后自己打掉了,其实最后状态都是没怀孕。A:女友没怀孕


        /**Java----java本身无法操作内存,可以通过调C++ {native}操作内存
         * Unsafe 类,Java后门,直接操作内存
         */

    }
}

原子引用:类似于乐观锁

package com.gton.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @description: CAS
 * @author: GuoTong
 * @createTime: 2021-10-21 14:22
 * @since JDK 1.8 OR 11
 **/
public class CASDemo {


    public static void main(String[] args) {

        //compareAndSwap :比较并交换
        AtomicInteger atomicInteger = new AtomicInteger(2021);
        //CAS:自旋锁->比较并交换,如果是2021,就把它替换为2022
        boolean isSuccess = atomicInteger.compareAndSet(2021, 2022);
        System.out.println(isSuccess);

        //前面改了后面就失败
        boolean isSuccess2 = atomicInteger.compareAndSet(2021, 2022);
        System.out.println(isSuccess2);

        //CAS:比较当前工作内存中的值和主内存中的值,如果这个值是预期的,那就执行操作,否则就一致循环自旋等到预期
        //缺点:循环会耗时,一次性只能保证一个共享变量的原子性,ABA问题


        //ABA问题  狸猫换太子,S:开始状态女友没怀孕,B:让女友出轨怀孕了,让他然后自己打掉了,其实最后状态都是没怀孕。A:女友没怀孕


        /**Java----java本身无法操作内存,可以通过调C++ {native}操作内存
         * Unsafe 类,Java后门,直接操作内存
         */

    }

    public void versionTest() {
        //Integer 对象缓存机制:-128 ====127
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(66, 1);

        new Thread(() -> {
            int temp = atomicStampedReference.getStamp();
            System.out.println("a1->" + temp);


            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //期望值,更新期望值 ,期望乐观锁版本号,更新乐观锁版本号
            atomicStampedReference.compareAndSet(66, 88,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

            System.out.println("a2->" + atomicStampedReference.getStamp());

            //ABA还原回去
            atomicStampedReference.compareAndSet(88, 66,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("a3->" + atomicStampedReference.getStamp());
        }, "a").start();

        new Thread(() -> {
            int temp = atomicStampedReference.getStamp();
            System.out.println("b1->" + temp);


            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //期望值,更新期望值 ,期望乐观锁版本号,更新乐观锁版本号
            atomicStampedReference.compareAndSet(66, 81,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(atomicStampedReference.getStamp());
        }, "b").start();
    }
}

更多锁的理解

公平锁和非公平锁

Lock lock = new ReentrantLock();//非公平
Lock lock = new ReentrantLock(false);//公平

自旋锁:while,do-while:不达条件不放行

死锁:jps==>jstack

jps -l  查看进程号

jstack 进程号

posted on 2021-09-19 19:50  白嫖老郭  阅读(70)  评论(0编辑  收藏  举报

导航