Java多线程及并发知识点总结

目录

 

一、volatile的理解

二、CAS的理解

三、ArrayList多线程异常:java.util.ConcurrentModificationException (同HashSet、HashMap问题)

四、公平锁、非公平锁、可重入锁(递归锁)、自旋锁、读/写锁(共享锁/独占锁)的理解

五、CountDownLatch、 CyclicBarrier、Semaphore的使用

六、阻塞队列的理解

七、Synchronized和Lock的区别

八、Callable接口的理解

九、线程池Executor的理解

十、死锁的理解


一、volatile的理解

1.volatile是Java虚拟机提供的轻量级的同步机制

1.1保证可见性

1.2不保证原子性-反JMM:数据加载过快,返回主内存数据覆盖,导致数据丢失

1.3禁止指令重排

2.JMM(Java内存模型)理解

2.1可见性:主内存线程--->分配--->工作内存1、2、3线程...--->返回--->主内存线程

2.2原子性

2.3有序性

3.在哪些地方用到volatile

3.1单例模式DCL(Double Check Lock)代码:DCL双重检查加锁-可能在instance引用对象时,由于指令重排,导致初始化完成前被其他线程引用,造成线程不安全问题。

4.示例:

4.1非volatile 、synchronized解决多线程原子性问题:

JUC:java.util.concurrent.atomic包下提供了原子性方法,如:

AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.getAndIncrement();

4.2volatile单例模式DCL,禁止指令重排

private volatile static Instance ins = null;
public static Instance getInstance(){
    if (ins == null){
        synchronized (Instance.class){
            if (ins == null){
                ins = new Instance();
            }
        }
    }
    return ins;
}

二、CAS的理解

1.什么是CAS

1.1compareAndSet--->比较并交换,期望相同即交换

2.CAS底层原理

2.1CAS方法

atomicInteger.compareAndSet(expect,update);

2.2调用Unsafe:Unsafe类的方法大部分为native修饰(Java调用非Java代码的接口),其直接调用内存偏移地址获取数据。

unsafe.compareAndSwapInt(this, valueOffset, expect, update);

3.CAS的缺点

3.1由于没有加锁,循环时间长,开销大

3.2只能保证一个共享变量的原子性

3.3会导致ABA问题:

①多线程时间差引起线程1A---(线程2A-B-C-D...-A)---B期间,线程2多次修改数据,导致线程1认为数据未变化。

②适合只管结果不管过程的业务。

4.示例

4.1时间戳原子引用解决ABA问题:AtomicStamped...

public static void main(String[] args) {
        String val = "A";
        AtomicStampedReference<String> satf = new AtomicStampedReference<String>(val, 1);
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "初始版本号:" + satf.getStamp() + " val:" + satf.getReference());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            satf.compareAndSet("A", "B", satf.getStamp(), satf.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "修改后版本号:" + satf.getStamp() + " val:" + satf.getReference());
            satf.compareAndSet("B", "A", satf.getStamp(), satf.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "修改后版本号:" + satf.getStamp() + " val:" + satf.getReference());
        }, "t1").start();
        new Thread(() -> {
            int init = satf.getStamp();
            System.out.println(Thread.currentThread().getName() + "初始版本号:" + satf.getStamp());
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean flg = satf.compareAndSet("A", "B",init, satf.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "等待t1后版本号:" + satf.getStamp() + " cas状态:" + flg + " val值:" + satf.getReference());
        }, "t2").start();
    }

三、ArrayList多线程异常:java.util.ConcurrentModificationException (同HashSet、HashMap问题)

1.1异常复现(牺牲Vector安全性,提高性能的结果)

List<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
    new Thread(() -> {
        list.add(UUID.randomUUID().toString().substring(0, 8));
        System.out.println(list);
    }, String.valueOf(i)).start();
}

1.2解决方法

①简单解决:Collections.synchronized...

List<String> list = Collections.synchronizedList(new ArrayList<>());

②提升解决:JUC工具类--->CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap

如add方法,使用ReentrantLock加锁实现

List<String> list = new CopyOnWriteArrayList<>();
 public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

四、公平锁、非公平锁、可重入锁(递归锁)、自旋锁、读/写锁(共享锁/独占锁)的理解

1.公平锁和非公平锁

1.1公平锁:排队占锁。

1.2非公平锁:先抢占锁,抢不到排队占锁。

Synchronized非公平锁,ReentrantLock默认非公平锁,可选择锁方式:

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

2.可重入锁(递归锁)

2.1同一线程进入外层锁后,即不受限制进入内层锁,以此避免死锁的发生。

Synchronized、ReentrantLock为可重入锁。

    public synchronized void syc1() {
        syc2();        
    }
    public synchronized void syc2() {
    }

3.自旋锁

3.1采用循环的方式去尝试获取锁,以此防止出现阻塞,但是耗费CPU资源。

如:atomicInteger.getAndIncrement()--->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;
    }

4.读/写锁(共享锁/独占锁)

4.1独占锁:只被一个线程占有;

4.2共享锁:可被多个线程持有;

4.3解决Synchronized、ReentrantLock独占锁的问题:

ReentrantReadWriteLock提供了读写分离的锁机制,多线程操作时,写操作就独占锁,读操作就共享锁。

        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        try {
            rwLock.writeLock().lock();
            //写操作
        }finally {
            rwLock.writeLock().unlock();
        }
        try {
            rwLock.readLock().lock();
        } finally {
            //写操作
            rwLock.readLock().unlock();
        }

五、CountDownLatch、 CyclicBarrier、Semaphore的使用

1.CountDownLatch

1.1定义计数器,让调用await()的线程阻塞,直到计数减为0,再唤醒。

1.2示例

注:如果计数不够,会一直处于等待,耗费资源。

        System.out.println("定义countDownLatch计数器");
        int cdNum = 6;
        CountDownLatch countDownLatch = new CountDownLatch(cdNum);
        for (int i = 0; i < cdNum; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "分线程结束,并计数");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        System.out.println(Thread.currentThread().getName() + "等待分线程全部结束后继续主线程");
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "主线程结束");

2.CyclicBarrier

2.1定义计数器,直到计数加为num,才会执行await()后的方法。

2.2示例

        System.out.println("定义cyclicBarrier计数器");
        int num = 6;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
            System.out.println("分线程执行完毕,交回主线程");
        });
        for (int i = 0; i < num; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "分线程执行中");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }

3.Semaphore

3.1Semaphore用于多线程抢占多个资源,及并发资源数的控制,Synchronized、ReentrantLock是多线程抢占一个资源,也就是并发数为1。

3.2示例

        System.out.println("定义semaphore信号量,进行多线程抢占资源");
        int num = 3;
        Semaphore semaphore = new Semaphore(num);
        for (int i = 0; i < 9; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "  线程占用资源中");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"  3秒后释放资源");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }

六、阻塞队列的理解

队列:先到先得。

栈:先进后出。

1.1阻塞队列,即队列满时能出不能进,队列空时能进不能出。

1.2常用方法:

方法类型 抛出异常 特殊值 阻塞 超时
出入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() tkae() poll(time,unit)
检查 element() peek() 不可用 不可用

1.3常用实现类:

SynchronousQueue:不存储元素的阻塞队列,只能put一个,take一个。

ArrayBlockingQueue

LinkedBlockingQueue

PriorityBlockingQueue
SynchronousQueue

1.4扩展示例:

public static void main(String[] args) throws Exception {
        MyResource myResource = new MyResource(new ArrayBlockingQueue<>(5));
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t生产线程启动");
            try {
                myResource.myProd();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Prod").start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t消费线程启动");
            try {
                myResource.myConsumer();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Cons").start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("时间到,停止活动");
        myResource.stop();
    }

    static class MyResource {
        /**
         * 默认开启 进行生产消费的交互
         */
        private volatile boolean flag = true;
        /**
         * 默认值是0
         */
        private AtomicInteger atomicInteger = new AtomicInteger();
        private BlockingQueue<String> blockingQueue = null;

        public MyResource(BlockingQueue<String> blockingQueue) {
            this.blockingQueue = blockingQueue;
            System.out.println("接口实现方法\t" + blockingQueue.getClass().getName());
        }

        public void myProd() throws Exception {
            String data = null;
            boolean returnValue;
            while (flag) {
                data = atomicInteger.incrementAndGet() + "";
                returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
                if (returnValue) {
                    System.out.println(Thread.currentThread().getName() + "\t 插入队列数据 " + data + " 成功");
                } else {
                    System.out.println(Thread.currentThread().getName() + "\t 插入队列数据 " + data + " 失败");
                }
                TimeUnit.SECONDS.sleep(1);
            }
            System.out.println(Thread.currentThread().getName() + "\t 停止 表示 flag" + flag);
        }

        public void myConsumer() throws Exception {
            String result = null;
            while (flag) {
                result = blockingQueue.poll(2L, TimeUnit.SECONDS);
                if (StringUtils.isBlank(result)) {
                    flag = false;
                    System.out.println(Thread.currentThread().getName() + "\t 超过2m没有取到 消费退出");
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "\t 消费队列 " + result + " 成功");
            }
        }

        public void stop() throws Exception {
            flag = false;
        }
    }

七、Synchronized和Lock的区别

1.Synchronized是关键字,属于JVM层面;Lock是JUC的具体类,是API层面的锁。

2.Synchronized不需要手动释放资源;Lock需要手动释放,用lock和unlock配合使用,否则导致死锁。

3.Synchronized是不能被中断的;Lock是可以中断的。

4.Synchronized是非公平锁;Lock默认非公平锁,设为true即为公平锁。

5.Synchronized随机唤醒锁;Lock可以精准唤醒锁。

Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
c1.await();
c1.signal();

八、Callable接口的理解

1.创建线程的四种方式:

①直接继承Thread。

②实现Runnable接口:无返回结果,不抛异常。

③实现Callalbe接口:有返回结果,抛异常。

④线程池Executor。

2.Callable接口可以获取执行结果,并利用阻塞一直等待结果返回。

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> oft = new FutureTask<>(new MyThread());
        new Thread(oft, "1").start();
        int num1 = 100;
        System.out.println(Thread.currentThread().getName() + "\t 获取Callable线程结果,未完成则堵塞等待");
        int num2 = oft.get();
        System.out.println(Thread.currentThread().getName() + "\t 处理结果: \t" + (num1 + num2));
    }
    static class MyThread implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            System.out.println(Thread.currentThread().getName() + "\t Callable进行处理中!");
            TimeUnit.SECONDS.sleep(3);
            return 100;
        }
    }

九、线程池Executor的理解

1.线程池优势:通过线程的复用,管理线程并发数,最大化利用系统资源,提供响应速度。

2.常见实现方法:

Executors.newFixedThreadPool 创建定长线程池,使用LinkedBlockingQueue阻塞队列
Executors.newSingleThreadPool 创建一个线程的线程池,使用LinkedBlockingQueue阻塞队列
Executors.newCachedThreadPool 创建可调节的线程池,使用SynchronousQueue阻塞队列
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();
//        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName() + "\t 进入业务处理");
            });
        }
        threadPool.shutdown();
    }

3.线程池参数说明

--->public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
--->public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue
                          ) {
    this(corePoolSize,------------------------>核心线程数
         maximumPoolSize,--------------------->最大线程数 
         keepAliveTime, ---------------------->空闲线程存活时间,最大线程数减为核心线程数
         unit,-------------------------------->空闲线程存活时间d单位
         workQueue,--------------------------->任务队列,被提交但未被执行的任务
         Executors.defaultThreadFactory(), --->线程工厂
         defaultHandler);}-------------------->拒绝策略,最大线程数和任务队列都满的时候执行拒绝策略

4.拒绝策略说明

AbortPolicy 默认策略,直接抛出RejectedExecution异常
CallerRunsPolicy 将任务回退到调用者处理,从而降低新任务的流量
DiscardOldestPolicy 将队列中等待最久的任务抛弃,并提交当前任务
DiscardPolicy 直接丢弃当前任务

5.生产实现策略

5.1线程池使用ThreadPoolExecutor的方式创建。

5.2最大线程5+阻塞队列5<小于任务15,执行决绝策略。

5.3简单配置:

①CPU密集型任务线程数=CPU核数+1;

②  IO密集型任务线程数=CPU核数*2   或  CPU核数/(1-阻塞系统)

阻塞系数≈0.8 - 0.9

    public static void main(String[] args) {
        int cpuNum = Runtime.getRuntime().availableProcessors();
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                1L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(5),
                Executors.defaultThreadFactory()
//                new ThreadPoolExecutor.AbortPolicy()
//                new ThreadPoolExecutor.CallerRunsPolicy()
//                new ThreadPoolExecutor.DiscardOldestPolicy()
//                new ThreadPoolExecutor.DiscardPolicy()
        );
        for (int i = 0; i < 15; i++) {
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName()+"\t 进入业务处理");
            });
        }
        threadPool.shutdown();

十、死锁的理解

1.死锁即多个线程相互等待的现象,由系统资源不足,进程运行推进的顺序不合适,资源分配不当导致。

2.示例:

    public static void main(String[] args) {
        String lock1 = "lock1";
        String lock2 = "lock2";
        new Thread(new DeadLock(lock1, lock2), "A").start();
        new Thread(new DeadLock(lock2, lock1), "B").start();
    }

    static class DeadLock implements Runnable {
        private String val1;
        private String val2;

        public DeadLock(String val1, String val2) {
            this.val1 = val1;
            this.val2 = val2;
        }

        @Override
        public void run() {
            synchronized (val1) {
                System.out.println(Thread.currentThread().getName() + "\t 当前持有" + val1 + "\t 准备获取" + val2);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (val2) {
                    System.out.println(Thread.currentThread().getName() + "\t 已获取" + val2);
                }
            }
        }
    }

3.如何排查是否发生死锁

① 查询进程号

jps -l 

② 得到Java stack information for the threads listed above,即死锁发生的具体信息

stack 进程号

发布了59 篇原创文章 · 获赞 13 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/u012725623/article/details/105409435