JUC系列(持续更新)

1、什么是J.U.C

JUC全称就是 Java.util.concurrent,我们去jdk1.8帮助文档看看
在这里插入图片描述

2、进程、线程、协程

2.1 简介

进程:是应用程序的启动实例,进程拥有打开代码和打开的文件资源、数据资源和独立的内存空间,它是资源分配的基本单位
线程:它是程序的实际执行者,一个进程可以有多个线程,但至少有一个,线程拥有自己的栈空间,它是调度分配的基本单位
协程:它是一种更轻量级的存在,正如进程有多个线程,线程也有多个协程
Java默认有几个线程? 2个:Main线程、GC线程
Java真的能开启线程吗? 不能,其实Java调用的start()方法底层又是去调用一个start0()的本地方法去开启的线程,Java是没有权限开启线程的
理解并行和并发的概念

  • 并行:(多个人一起行走)它是在多核下的:真正意义上的同时执行;线程池
  • 并发:(多线程操作同一资源)单核下:能营造出同时执行的假象,其实是线程交替执行的,这种交替是非常快的
    并发编程的本质:就是充分利用CPU的资源

2.2 线程有几个状态

这个概念是混的,从操作系统角度来讲:线程拥有5种状态,分别是:新建、可执行、运行、阻塞、死亡;从Java角度来讲:进入底层代码发现,它有6个枚举状态,分别是:NEW(新建)、RUNNABLE(运行)、WAITING(等待,傻傻的等)、TIMED_WAITING(超时等待,有时间)、BLOCKED(阻塞)、TERMINATED(终止)

2.3 wait和sleep的区别

wait sleep
1、来自不同的类 Object Thread
2、关于锁的释放 释放锁(进入等待锁池,等待notify的唤醒) 不释放锁(可以理解为抱着锁睡觉,一段时间和可以醒来继续执行)
3、使用范围不同 必须在同步代码块中 可以在任何地方睡
4、方法属性 实例方法 静态方法

3、Lock锁(重点)

3.1 简介

我们先看下jdk下的lock包下有什么实现类
在这里插入图片描述
锁从获取资源的公平性角度来讲可分为公平锁和非公平锁,从乐观和悲观方面分为乐观锁和悲观锁,从是否共享资源角度来讲可分为共享锁和独占锁,从锁的状态角度分为偏向锁、轻量级锁和重量级锁

3.2 lock锁和synchronized有什么区别

  1. synchronized是Java的关键字,lock是一个接口,它有很多实现类,如ReentrantLock,所以lock的扩展性更高
  2. synchronized是隐式的加锁解锁,lock是显示的加锁解锁,操作失误的话有可能会产生死锁
  3. synchronized在获取锁的过程中,如果获取不到,将会一直处于阻塞状态,lock不一定等待下去
  4. synchronized无法判断获取锁的状态,lock可以得到获取锁的状态
  5. synchronized是可重入锁,不可以中断的,非公平的,lock是可重入锁,可以判断获取锁的状态,默认是非公平的(可以设置)
  6. synchronize适合锁少量的代码问题,lock适合锁大量的代码问题

4、生产者和消费者问题

4.1 简介

线程间是如何通信的呢?最经典的例子就是生产者/消费者问题

4.2 使用synchronized实现的demo

有个小插曲 if和where 如果资源中使用if判断的话,可能会出现虚假唤醒的情况
在这里插入图片描述

package com.markus.pc;
/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/26 21:55
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i=0;i<10;i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i=0;i<10;i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i=0;i<10;i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i=0;i<10;i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
class Data{
    private int number = 0;
    public synchronized void increment() throws InterruptedException {
        while(number!=0){
            //等待
            this.wait();
        }
        //业务逻辑
        number++;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        //通知其他线程我+1完毕了
        this.notifyAll();
    }
    public synchronized void decrement() throws InterruptedException {
        while(number==0){
            //等待
            this.wait();
        }
        //业务逻辑
        number--;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        //唤醒其他线程
        this.notifyAll();
    }
}

4.3使用Lock实现的demo

package com.markus.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/27 9:12
 */
public class B {
    public static void main(String[] args) {
        Data1 data = new Data1();
        new Thread(()->{
            for (int i=0;i<10;i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i=0;i<10;i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i=0;i<10;i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i=0;i<10;i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
class Data1{
    private int number = 0;
    private Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();//精准的通知和唤醒线程
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            if(number!=0){
                //等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
    public void decrement() throws InterruptedException {
        lock.lock();
        try{
            if(number==0){
                //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            condition.signalAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

syn的替代方案
syn+wait+notify完全可以用Lock+await()+signalAll()来替换,但技术的更新肯定不是只是替换的作用,还会与改进,就比如 Condition 可以来实现精准唤醒

package com.markus.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/27 9:37
 */
public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(()->{
            for (int i = 0; i < 10 ; i++) {
                data.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10 ; i++) {
                data.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10 ; i++) {
                data.printC();
            }
        },"C").start();
    }
}
class Data3{
    private int number = 1; //1A执行 2B 3C
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    public void printA(){
        lock.lock();
        try{
            while(number!=1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"-> A");
            number = 2;
            condition2.signal();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try{
            while(number!=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"-> B");
            number = 3;
            condition3.signal();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try{
            while(number!=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"-> C");
            number = 1;
            condition1.signal();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

5、锁的是谁问题

希望通过这几个例子,能让大家明白,锁的是谁

1、两个线程访问同一个对象的同步方法 (会发生互斥)

package com.markus.lock8;
import java.util.concurrent.TimeUnit;
/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/27 22:25
 * todo: 1、两个线程访问同一个对象的同一方法 (会发生互斥)
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendSMS();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.sendSMS();
        },"B").start();
    }
}
class Phone{
    public synchronized void sendSMS(){
        try {
            System.out.println(Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信: "+Thread.currentThread().getName());
    }
}

2、两个线程访问两个对象的同一同步方法 (不会发生互斥)

package com.markus.lock8;

import java.util.concurrent.TimeUnit;

/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/27 22:33
 * todo: 2、两个线程访问两个对象的同一方法 (不会发生互斥)
 */
public class Test2 {
    public static void main(String[] args) {
        Phone1 phone1 = new Phone1();
        Phone1 phone2 = new Phone1();
        new Thread(()->{
            phone1.sendSMS();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.sendSMS();
        },"B").start();
    }
}
class Phone1{
    public synchronized void sendSMS(){
        try {
            System.out.println(Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信: "+Thread.currentThread().getName());
    }
}

3、两个线程访问synchronized修饰的静态方法 (会发生互斥)

package com.markus.lock8;
import java.util.concurrent.TimeUnit;
/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/27 22:42
 * todo: 3、两个线程访问synchronized修饰的静态方法 (会发生互斥)
 */
public class Test3 {
    public static void main(String[] args) {
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            phone1.sendSMS();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.sendSMS();
        },"B").start();
    }
}
class Phone2{
    public static synchronized void sendSMS(){
        try {
            System.out.println(Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信: "+Thread.currentThread().getName());
    }
}

4、两个线程访问同步方法和非同步方法 (不会互斥)

package com.markus.lock8;
import java.util.concurrent.TimeUnit;
/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/27 22:44
 * todo: 4、两个线程访问同步方法和非同步方法 (不会互斥)
 */
public class Test4 {
    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        //Phone3 phone2 = new Phone3();
        new Thread(()->{
            phone1.sendSMS();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone1.hello();
        },"B").start();
    }
}
class Phone3{
    public synchronized void sendSMS(){
        try {
            System.out.println(Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信: "+Thread.currentThread().getName());
    }
    public void hello(){
        System.out.println(Thread.currentThread().getName());
        System.out.println("hello: "+Thread.currentThread().getName());
    }
}

5、两个线程访问同一个对象的不同的普通同步方法 (会发生互斥)

package com.markus.lock8;

import java.util.concurrent.TimeUnit;

/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/27 22:46
 * todo: 5、两个线程访问同一个对象的不同的普通同步方法 (会发生互斥)
 */
public class Test5{
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        //Phone3 phone2 = new Phone3();
        new Thread(()->{
            phone1.sendSMS();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone1.hello();
        },"B").start();
    }
}
class Phone4{
    public synchronized void sendSMS(){
        try {
            System.out.println(Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信: "+Thread.currentThread().getName());
    }
    public synchronized void hello(){
        System.out.println(Thread.currentThread().getName());
        System.out.println("hello: "+Thread.currentThread().getName());
    }
}

6、两个线程同时访问同一类下不同的静态synchronized方法 (互斥)

package com.markus.lock8;

import java.util.concurrent.TimeUnit;

/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/27 22:48
 * todo: 6、两个线程同时访问同一类下不同的静态synchronized方法 (互斥)
 *       7、两个线程同时访问同一类下不同对对象的不同的静态synchronized方法 (互斥)
 */
public class Test6 {
    public static void main(String[] args) {
        Phone5 phone1 = new Phone5();
        Phone5 phone2 = new Phone5();
        new Thread(()->{
            phone1.sendSMS();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.hello();
        },"B").start();
    }
}
class Phone5{
    public static synchronized void sendSMS(){
        try {
            System.out.println(Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信: "+Thread.currentThread().getName());
    }
    public static synchronized void hello(){
        System.out.println(Thread.currentThread().getName());
        System.out.println("hello: "+Thread.currentThread().getName());
    }
}

6、集合不安全类

1、ArrayList(线程不安全)

在单线程情况下,ArrayList是没有问题的,但是在多线程环境下,会出现异常:ConcurrentModificationException

package com.markus.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/27 23:02
 */
public class ListTest {
    public static void main(String[] args) {
        //并发情况下,ArrayList是线程不安全的,会出现ConcurrentModificationException异常
        //我们如何去保证它线程安全呢?
        //1、使用vector
        //2、使用Collections.synchronizedList(list),封装,底层是维护的一个Object对象,对链表操作的时候对这个对象加锁
        //3、使用J.U.C下的CopyOnWriteArrayList;
//        List<String> list = new ArrayList<>();
//        List<String> list = new Vector<>();
//        List<String> list = Collections.synchronizedList(new ArrayList<>());
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10 ; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

我们来看看CopyOnWriteArrayList底层,顾名思义:写时复制,读的时候不影响,当在写的时候创建一个副本去写,写完之后将旧数组指向新数组。它是实现了一个ReentrantLock对象来进行加锁解锁,然后维护了一个volatile的array对象数组
在这里插入图片描述

2、Set(线程不安全)

HashSet的底层其实就是一个HashMap,利用HashMap的key进行存值,它是线程不安全的

package com.markus.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/27 23:17
 */
public class SetTest {
    public static void main(String[] args) {
        //线程不安全 出现 ConcurrentModificationException
        // 1、通过Collections的同步set来实现线程安全
        // 2、通过 J.U.C下的CopyOnWriteArraySet来实现线程安全
//        Set<String> set = new HashSet<>();
//        Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
        for(int i = 0 ; i < 30 ; i++){
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

3、Map(线程不安全)

package com.markus.unsafe;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/28 9:21
 */
public class MapTest {
    public static void main(String[] args) {
        //并发情况 出现 ConcurrentModificationException
        // 1、通过Collections.synchronizedMap();解决
        // 2、通过ConcurrentHashMap解决
//        Map<String,String> map = new HashMap<>();
//        Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
        Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30 ; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

7、Callable

1、有缓存
2、结果可能会等待

package com.markus.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/28 9:47
 */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread() 只认识 Runnable
        //new Thread(Runnable) Runnable有一个实现类:FutureTask
        //new Thread(FutureTask) FutureTask认识Callable
        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread);
        new Thread(futureTask).start();
        new Thread(futureTask).start();//按常理说它应该执行两次call,但是实际执行只有一次,结果会被缓存,提高效率
        Integer i = (Integer) futureTask.get();//它会阻塞,如果call方法是一个耗时操作,它会一直阻塞到返回返回值
        //可采用异步通信的方式解决
        System.out.println(i);
    }
}
class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 1024;
    }
}

8、常用辅助类

1、CountDownLatch

在这里插入图片描述
原理:

  • countDown() 计数器-1
  • await() 等到计数器为0的话,会被唤醒,继续向下执行
package com.markus.add;
import java.util.concurrent.CountDownLatch;
/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/28 10:36
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Go out");
                countDownLatch.countDown();// -1 操作
            },String.valueOf(i)).start();
        }
        countDownLatch.await();//当计数器为0时,被唤醒,继续向下执行
        System.out.println("Close Door");
    }
}

2、CyclicBarrier

在这里插入图片描述
可以理解为一个加法器,所有线程等待屏障,直到所有行都被处理

package com.markus.add;

import com.markus.pc.C;

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

/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/28 12:04
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3,()->{
            System.out.println("西天取经成功");
        });
        for (int i = 1; i <= 3; i++) {
            final int temp = i;
            // lambda能操作到 i 吗?
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"收徒"+temp+"个徒弟");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3、Semaphore

原理:
semaphore.acquire():获得,如果已经满了,就需要等待,直到被释放为止
semaphore.release():释放,会将当前的信号量释放 +1,然后唤醒等待的线程
作用:多个共享资源的互斥的使用!并发限流,控制最大线程数

package com.markus.add;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
 * Author:markusZhang
 * degree of proficiency:
 * Date:Create in 2020/5/28 13:52
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        //模拟 停车位
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; 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();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/MarkusZhang/article/details/106376251
今日推荐