狂神JUC笔记

Posted 在奋斗的大道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了狂神JUC笔记相关的知识,希望对你有一定的参考价值。

目录

1、什么是JUC

2、线程和进程 

3、Lock锁(重点)

4、生产者和消费者问题! 

 5、八锁现象

6、集合不安全 

7、Callable

8、 常用的辅助类

8.1 CountDownLatch

8.2 CyclickBarrier

8.3 Semaphore

9、读写锁

10、阻塞队列

BlockingQueue

如何使用阻塞队列呢?

SynchronousQueue同步队列

11、线程池(重点)

如何去设置线程池的最大大小如何去设置?

12、四大函数式接口

 13、ForkJoin

14、异步回调 

15、JMM

16、Volatile 

 17、深入理解CAS

18、原子引用

19、各种锁的理解 

1、公平锁、非公平锁

2、可重入锁

3、自旋锁

 4、死锁


本文来源于:B站狂神JUC,视频地址:狂神B站

1、什么是JUC

JUC是 java util concurrent

java.util 是Java的一个工具包~

2、线程和进程 

进程:一个程序,QQ.EXE Music.EXE;

一个进程可以包含多个线程,至少包含一个线程!

Java默认有几个线程?2个线程! main线程、GC线程

线程:开了一个进程Notepad++,写字,等待几分钟会进行自动保存(线程负责的)

对于Java而言:Thread、Runable、Callable进行开启线程的实现方式。

提问?JAVA真的可以开启线程吗? 开不了的!

    public synchronized void start() 
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try 
            start0();
            started = true;
         finally 
            try 
                if (!started) 
                    group.threadStartFailed(this);
                
             catch (Throwable ignore) 
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            
        
    
	//这是一个C++底层,Java是没有权限操作底层硬件的
    private native void start0();

 Java是没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。

并行、串行

并发: 多线程操作同一个资源。

  • CPU 只有一核,模拟出来多条线程,天下武功,唯快不破。那么我们就可以使用CPU快速交替,来模拟多线程。

并行: 多个人一起行走

  • CPU多核,多个线程可以同时执行。 我们可以使用线程池!
public class Test1 
    public static void main(String[] args) 
        //获取cpu的核数
        System.out.println(Runtime.getRuntime().availableProcessors());
    

并发编程的本质:充分利用CPU的资源!

线程有几个状态?

线程的状态:6个状态

public enum State 
        /**
         * Thread state for a thread which has not yet started.
         */
    	//运行
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
    	//运行
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * @link Object#wait() Object.wait.
         */
    	//阻塞
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>@link Object#wait() Object.wait with no timeout</li>
         *   <li>@link #join() Thread.join with no timeout</li>
         *   <li>@link LockSupport#park() LockSupport.park</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
    	//等待
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>@link #sleep Thread.sleep</li>
         *   <li>@link Object#wait(long) Object.wait with timeout</li>
         *   <li>@link #join(long) Thread.join with timeout</li>
         *   <li>@link LockSupport#parkNanos LockSupport.parkNanos</li>
         *   <li>@link LockSupport#parkUntil LockSupport.parkUntil</li>
         * </ul>
         */
    	//超时等待
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
    	//终止
        TERMINATED;
    

wait/sleep的区别 

1、来自不同的类

wait => Object

sleep => Thread

一般情况企业开发中使用休眠是:

TimeUnit.DAYS.sleep(1); //休眠1天
TimeUnit.SECONDS.sleep(1); //休眠1s

2、关于锁的释放

wait 会释放锁;

sleep睡觉了,不会释放锁;

3、使用的范围是不同的

wait 必须在同步代码块中;

sleep 可以在任何地方睡;

4、是否需要捕获异常

wait是不需要捕获异常;

sleep必须要捕获异常;

3、Lock锁(重点)

传统的Synchronized

/**
 * 真正的多线程开发
 * 线程就是一个单独的资源类,没有任何的附属操作!
 */
public class SaleTicketDemo01 
    public static void main(String[] args) 
        //多线程操作
        //并发:多线程操作同一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();

        //@FunctionalInterface 函数式接口 jdk1.8之后 lambda表达式
        new Thread(()->
            for(int i=0;i<40;i++)
                ticket.sale();
            
        ,"A").start();
        new Thread(()->
            for(int i=0;i<40;i++)
                ticket.sale();
            
        ,"B").start();
        new Thread(()->
            for(int i=0;i<40;i++)
                ticket.sale();
            
        ,"C").start();
    

//资源类
//属性+方法
//oop
class Ticket
    private int number=50;


    //卖票的方式
    // synchronized 本质:队列,锁
    public synchronized void sale()
        if(number>0)
            System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
            number--;
        
    

 Lock接口

 

公平锁: 十分公平,必须先来后到~;

非公平锁: 十分不公平,可以插队;(默认为非公平锁)

public class SaleTicketDemo02 
    public static void main(String[] args) 
        //多线程操作
        //并发:多线程操作同一个资源类,把资源类丢入线程
        Ticket2 ticket = new Ticket2();
        new Thread(()->for(int i=0;i<40;i++) ticket.sale(); ,"A").start();
        new Thread(()->for(int i=0;i<40;i++) ticket.sale(); ,"B").start();
        new Thread(()->for(int i=0;i<40;i++) ticket.sale(); ,"C").start();
    


//lock三部曲
//1、    Lock lock=new ReentrantLock();
//2、    lock.lock() 加锁
//3、    finally=> 解锁:lock.unlock();
class Ticket2
    private int number=50;

    Lock lock=new ReentrantLock();

    //卖票的方式
    // 使用Lock 锁
    public void sale()
        //加锁
        lock.lock();
        try 
            //业务代码
            if(number>=0)
                System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
                number--;
            
        catch (Exception e) 
            e.printStackTrace();
        
        finally 
            //解锁
            lock.unlock();
        
    

 Synchronized 和 Lock区别

  • Synchronized 内置的Java关键字,Lock是一个Java类
  • Synchronized 无法判断获取锁的状态,Lock可以判断
  • Synchronized 会自动释放锁,Lock必须要手动加锁和手动释放锁!可能会遇到死锁
  • Synchronized 线程1(获得锁->阻塞)、线程2(等待);Lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。
  • Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;
  • Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;
     

4、生产者和消费者问题! 

Synchronized版本

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();
    

class Data
    //数字  资源类
    private int number = 0;

    //+1
    public synchronized void increment() throws InterruptedException 
        if(number!=0)
            //等待操作
            this.wait();
        
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程 我+1完毕了
        this.notifyAll();
    

    //-1
    public synchronized void decrement() throws InterruptedException 
        if(number==0)
            //等待操作
            this.wait();
        
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程  我-1完毕了
        this.notifyAll();
    


 问题存在,A线程B线程,现在如果我有四个线程A B C D!

 解决方案: if 改为while即可,防止虚假唤醒

Lock版本 

通过Lock找到Condition

public class B 
    public static void main(String[] args) 
        Data2 data = new Data2();

        new Thread(()->for(int i=0;i<10;i++) 
            data.increment();
        
        ,"A").start();
        new Thread(()->for(int i=0;i<10;i++) 
            data.decrement();
        ,"B").start();
        new Thread(()->for(int i=0;i<10;i++) 
            data.increment();
        
        ,"C").start();
        new Thread(()->for(int i=0;i<10;i++) 
            data.decrement();
        
        ,"D").start();
    

class Data2
    //数字  资源类
    private int number = 0;

    //lock锁
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    //+1
    public void increment()  
        lock.lock();
        try

            //业务
            while (number!=0)
                //等待操作
                condition.await();
            
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程 我+1完毕了
            condition.signalAll();
         catch (Exception e) 
            e.printStackTrace();
         finally 
            lock.unlock();
        
    

    //-1
    public void decrement()  
        lock.lock();
        try
            //业务
            while (number==0)
                //等待操作
                condition.await();
            
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程 我+1完毕了
            condition.signalAll();
         catch (Exception e) 
            e.printStackTrace();
         finally 
            lock.unlock();
        
    

Condition的优势:精准的通知和唤醒的线程

如果我们要指定的通知按照一定的顺序执行,如何实现?可以使用Condition来指定通知进程。

/**
 * A 执行完 调用B
 * B 执行完 调用C
 * C 执行完 调用A
 */

public class C 

    public static void main(String[] args) 
        Data3 data3 = new Data3();
        new Thread(()->
            for(int i=0;i<10;i++)
                data3.printA();
            
        ,"A").start();
        new Thread(()->
            for(int i=0;i<10;i++)
                data3.printB();
            
        ,"B").start();
        new Thread(()->
            for(int i=0;i<10;i++)
                data3.printC();
            
        ,"C").start();
    


class Data3
    //资源类
    private Lock lock=new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1; //1A 2B 3C

    public void printA()
        lock.lock();
        try 
            //业务 判断 -> 执行 -> 通知
            while(number!=1)
                //等待
                condition1.await();
            
            //操作
            System.out.println(Thread.currentThread().getName()+",AAAAA");
            //唤醒指定的线程
            number=2;
            condition2.signal(); // 唤醒2

         catch (Exception e) 
            e.printStackTrace();
         finally 
            lock.unlock();
        
    
    public void printB()
        lock.lock();
        try 
            //业务 判断 -> 执行 -> 通知
            while (number!=2)
                condition2.await();
            
            System.out.println(Thread.currentThread().getName()+",BBBBB");
            //唤醒3
            number=3;
            condition3.signal();
         catch (Exception e) 
            e.printStackTrace();
         finally 
            lock.unlock();
        
    
    public void printC()
        lock.lock();
        try 
            //业务 判断 -> 执行 -> 通知
            while(number!=3)
                condition3.await();
            
            System.out.println(Thread.currentThread().getName()+",CCCCC");
            //唤醒1
            number=1;
            condition1.signal();
         catch (Exception e) 
            e.printStackTrace();
         finally 
            lock.unlock();
        
    

 5、八锁现象

如何判断锁的是谁!锁到底锁的是谁?

锁会锁住:对象、Class

深刻理解我们的锁

  • 问题1:

结果 :先发短信,再打电话。

  •  问题2:让发短信延迟4秒,再次验证输出结果。

结果:还是先发短信,再打电话。

原因:因为synchronized 锁的对象是方法的调用!对于两个方法用的是同一个锁,谁先拿到谁先执行!另外一个则等待! 

  •  问题3:如果我们添加一个普通方法,那么先执行哪一个呢?

结果:先执行hello,然后再执行发短信!原因是hello是一个普通方法不受synchronized锁的影响,但是我发现,如果我把发短信里面的延迟4秒去掉,那么就会顺序执行,先执行发短信然后再执行hello,原因应该是顺序执行的原因吧,不是太理解。

  • 问题4:如果我们使用的是两个对象,一个调用发短信,一个调用打电话,那么整个顺序是怎么样的呢?

 结果:先打电话,再发短信。原因:在发短信方法中延迟了4s,又因为synchronized锁的是对象,但是我们这使用的是两个对象,所以每个对象都有一把锁,所以不会造成锁的等待。正常执行

  • 问题5,6:如果我们把synchronized的方法加上static变成静态方法!那么顺序又是怎么样的呢?

(1)我们先来使用一个对象调用两个方法!

结果:先发短信,后打电话

(2)如果我们使用两个对象调用两个方法!

结果:还是先发短信,后打电话

问题: 为什么加了static就始终前面一个对象先执行呢!为什么后面会等待呢?

原因:对于static静态方法来说,对于整个类Class来说只有一份,对于不同的对象使用的是同一份方法,相当于这个方法是属于这个类的,如果静态static方法使用synchronized锁定,那么这个synchronized锁会锁住整个类!不管多少个实例对象,对于静态的锁都只有一把锁,谁先拿到这个锁就先执行,其他的线程都需要等待!

  • 问题7:如果我们使用一个静态同步方法、一个同步方法、一个对象调用顺序是什么?

结果:先打电话,后发短信。

原因:因为sendSms方法锁的是Phone4 Class类,call锁的是对象调用者Phone4 Class类实例对象。后面那个打电话不需要等待发短信,直接运行就可以了。

  • 问题8:如果我们使用一个静态同步方法、一个同步方法、两个对象调用顺序是什么呢?

结果:先打电话,再发短信

原因:两把锁锁的不是同一个东西,所以后面的第二个对象不需要等待第一个对象的执行。

 总结:

 synchronized修饰普通方法锁的是类实例化对象。

 synchronized修饰静态方法锁的是类模板。

6、集合不安全 

List不安全

示例代码:

//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest 
    public static void main(String[] args) 

        List<Object> arrayList = new ArrayList<>();

        for(int i=1;i<=10;i++)
            new Thread(()->
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            ,String.valueOf(i)).start();
        

    

错误信息:

 结论:ArrayList 在并发情况下是不安全的!

解决方案1:ArrayList切换成Vector就是线程安全

 解决方案2:使用Collections.synchronizedList(new ArrayList<>());

public class ListTest 
    public static void main(String[] args) 

        List<Object> arrayList = Collections.synchronizedList(new ArrayList<>());

        for(int i=1;i<=10;i++)
            new Thread(()->
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            ,String.valueOf(i)).start();
        

    

解决方案3:使用JUC中的包:List arrayList = new CopyOnWriteArrayList<>(); 

public class ListTest 
    public static void main(String[] args) 

        List<Object> arrayList = new CopyOnWriteArrayList<>();

        for(int i=1;i<=10;i++)
            new Thread(()->
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            ,String.valueOf(i)).start();
        

    

CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略

 CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

CopyOnWriteArrayListVector厉害在哪里?

Vector底层是使用synchronized关键字来实现的:效率特别低下。

CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!

 Set 不安全

和List、Set同级的还有一个BlockingQueue 阻塞队列;

Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;

解决方案还是两种:

  • 使用Collections工具类的synchronized包装的Set类
  • 使用CopyOnWriteArraySet 写入复制的JUC解决方案
//同理:java.util.ConcurrentModificationException
// 解决方案:
public class SetTest 
    public static void main(String[] args) 
//        Set<String> hashSet = Collections.synchronizedSet(new HashSet<>()); //解决方案1
        Set<String> hashSet = new CopyOnWriteArraySet<>();//解决方案2
        for (int i = 1; i < 100; i++) 
            new Thread(()->
                hashSet.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(hashSet);
            ,String.valueOf(i)).start();
        
    

 HashSet底层是什么?

HashSet底层就是一个HashMap

public HashSet() 
        map = new HashMap<>();


//add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
//hashSet就是使用了hashmap key不能重复的原理
public boolean add(E e) 
        return map.put(e, PRESENT)==null;

//PRESENT是什么? 是一个常量  不会改变的常量  无用的占位
private static final Object PRESENT = new Object();

Map不安全

map基本操作:

//map 是这样用的吗?  不是,工作中不使用这个
//默认等价什么? new HashMap<>(16,0.75);
Map<String, String> map = new HashMap<>();
//加载因子、初始化容量

 默认加载因子是0.75,默认的初始容量是16

同样的HashMap类也存在并发修改异常

public static void main(String[] args) 
        //map 是这样用的吗?  不是,工作中不使用这个
        //默认等价什么? new HashMap<>(16,0.75);
        Map<String, String> map = new HashMap<>();
        //加载因子、初始化容量
        for (int i = 1; i < 100; i++) 
            new Thread(()->
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            ,String.valueOf(i)).start();
        
    

 结果:异常java.util.ConcurrentModificationException 并发修改异常。

解决方案:

  • 使用Collections.synchronizedMap(new HashMap<>());处理
  • 使用ConcurrentHashMap进行并发处理

7、Callable

Callable 与Runnable 区别

  1. Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
     
  2. Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
  3. 3、方法不同,Runnable.run()/Callablle.call()

Runnable 代码测试

传统线程编码方式:

public class CallableTest 
    public static void main(String[] args) 
        for (int i = 1; i < 10; i++) 
            new Thread(new MyThread()).start();
        
    


class MyThread implements Runnable

    @Override
    public void run() 
        System.out.println(Thread.currentThread().getName());
    

 使用Callable进行多线程操作:、

Calleable 泛型T就是call运行方法的返回值类型

问题:如何将Callable怎么放入到Thread里面呢? 

源码分析:Thread.java 源码

通过上述Thread的构造函数,发现仅支持传入Runnable类型的参数。

解决思路:在Runnable里面有一个叫做FutureTask的实现类,可以将Runnable接口和Callable接口进行整合。

FutureTask构造函数可以接受Callable参数;

 

FutureTask 和Calleable 代码测试 

public class CallableTest 
    public static void main(String[] args) throws ExecutionException, InterruptedException 
        for (int i = 1; i < 10; i++) 
//            new Thread(new Runnable()).start();
//            new Thread(new FutureTask<>( Callable)).start();
            MyThread thread= new MyThread();
            //适配类:FutureTask
            FutureTask<String> futureTask = new FutureTask<>(thread);
            //放入Thread使用
            new Thread(futureTask,String.valueOf(i)).start();
            //获取返回值
            String s = futureTask.get();
            System.out.println("返回值:"+ s);
        
    


class MyThread implements Callable<String> 

    @Override
    public String call() throws Exception 
        System.out.println("Call:"+Thread.currentThread().getName());
        return "String"+Thread.currentThread().getName();
    

 Calleable + FutureTask 主要重点:

8、 常用的辅助类

8.1 CountDownLatch

其实就是一个减法计数器,对于计数器归零之后再进行后面的操作,这是一个计数器!

**
 * 计数器减法
 */
public class CountDownLatchDemo 
    public static void main(String[] args) throws InterruptedException 
        //总数是6
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6 ; i++) 
            new Thread(()->
                System.out.println(Thread.currentThread().getName()+" Go out");
                countDownLatch.countDown(); //每个线程都数量-1
            ,String.valueOf(i)).start();
        
        countDownLatch.await();  //等待计数器归零  然后向下执行

        System.out.println("close door");

    

 

 主要方法:

  • countDown 减一操作;
  • await 等待计数器归零。

await等待计数器为0,就唤醒,再继续向下运行。

8.2 CyclickBarrier

其实就是一个加法计数器;

package com.zzg.juc;

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

public class CyclicBarrierDemo 
    public static void main(String[] args) 

        //主线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->
            System.out.println("召唤神龙~");
        );

        for (int i = 1; i <= 7; i++) 
            //子线程
            int finalI = i;
            new Thread(()->
                System.out.println(Thread.currentThread().getName()+" 收集了第 "+ finalI+" 颗龙珠");
                try 
                    cyclicBarrier.await(); //加法计数 等待
                 catch (InterruptedException e) 
                    e.printStackTrace();
                 catch (BrokenBarrierException e) 
                    e.printStackTrace();
                
            ).start();
        

    

 

8.3 Semaphore

Semaphore:信号量

功能示例:抢车位-3个车位 6辆车;

public class SemaphoreDemo 
    public static void main(String[] args) 
        //停车位为3个
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) 
            int finalI = i;
            new Thread(()->
                try 
                    semaphore.acquire(); //得到
                    //抢到车位
                    System.out.println(Thread.currentThread().getName()+" 抢到了车位"+ finalI +"");
                    TimeUnit.SECONDS.sleep(2); //停车2s
                    System.out.println(Thread.currentThread().getName()+" 离开车位");
                 catch (InterruptedException e) 
                    e.printStackTrace();
                finally 
                    semaphore.release();//释放
                
            ,String.valueOf(i)).start();
        
    

 

原理:

semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!

semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!

作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!

9、读写锁

功能要求:在多线程环境下,实现全局缓存,别有写入操作、读取操作。

不加锁情况

package com.ogj.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo 
    public static void main(String[] args) 
        MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
        //开启5个线程 写入数据
        for (int i = 1; i <=5 ; i++) 
            int finalI = i;
            new Thread(()->
                mycache.put(String.valueOf(finalI),String.valueOf(finalI));
            ).start();
        
        //开启10个线程去读取数据
        for (int i = 1; i <=10 ; i++) 
            int finalI = i;
            new Thread(()->
                String o = mycache.get(String.valueOf(finalI));
            ).start();
        
    


class MyCache_ReadWriteLock
    private volatile Map<String,String> map=new HashMap<>();

    //使用读写锁
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //普通锁
    private Lock lock=new ReentrantLock();

    public void put(String key,String value)
        //写入
        System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
        map.put(key, value);
        System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
    

    public String get(String key)
        //得到
        System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
        String o = map.get(key);
        System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
        return o;
    

 运行效果:

Thread-0 线程 开始写入
Thread-4 线程 开始写入  # 插入了其他的线程进行写入
Thread-4 线程 写入OK
Thread-3 线程 开始写入
Thread-1 线程 开始写入
Thread-2 线程 开始写入
Thread-1 线程 写入OK
Thread-3 线程 写入OK
Thread-0 线程 写入OK   # 对于这种情况会出现 数据不一致等情况
Thread-2 线程 写入OK
Thread-5 线程 开始读取
Thread-6 线程 开始读取
Thread-6 线程 读取OK
Thread-7 线程 开始读取
Thread-7 线程 读取OK
Thread-5 线程 读取OK
Thread-8 线程 开始读取
Thread-8 线程 读取OK
Thread-9 线程 开始读取
Thread-9 线程 读取OK
Thread-10 线程 开始读取
Thread-11 线程 开始读取
Thread-12 线程 开始读取
Thread-12 线程 读取OK
Thread-10 线程 读取OK
Thread-14 线程 开始读取
Thread-13 线程 开始读取
Thread-13 线程 读取OK
Thread-11 线程 读取OK
Thread-14 线程 读取OK

Process finished with exit code 0

解决方案一:采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。

本次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证。

package com.ogj.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo 
    public static void main(String[] args) 
        MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
        //开启5个线程 写入数据
        for (int i = 1; i <=5 ; i++) 
            int finalI = i;
            new Thread(()->
                mycache.put(String.valueOf(finalI),String.valueOf(finalI));
            ).start();
        
        //开启10个线程去读取数据
        for (int i = 1; i <=10 ; i++) 
            int finalI = i;
            new Thread(()->
                String o = mycache.get(String.valueOf(finalI));
            ).start();
        
    


class MyCache_ReadWriteLock
    private volatile Map<String,String> map=new HashMap<>();

    //使用读写锁
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //普通锁
    private Lock lock=new ReentrantLock();

    public void put(String key,String value)
        //加锁
        readWriteLock.writeLock().lock();
        try 
            //写入
            //业务流程
            System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
         catch (Exception e) 
            e.printStackTrace();
         finally 
            readWriteLock.writeLock().unlock(); //解锁
        
    

    public String get(String key)
        //加锁
        String o="";
        readWriteLock.readLock().lock();
        try 
            //得到
            System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
            o = map.get(key);
            System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
         catch (Exception e) 
            e.printStackTrace();
         finally 
            readWriteLock.readLock().unlock();
        
        return o;
    

 运行效果:

Thread-0 线程 开始写入
Thread-0 线程 写入OK
Thread-1 线程 开始写入
Thread-1 线程 写入OK
Thread-2 线程 开始写入
Thread-2 线程 写入OK
Thread-3 线程 开始写入
Thread-3 线程 写入OK
Thread-4 线程 开始写入
Thread-4 线程 写入OK

# 以上 整个过程没有再出现错乱的情况,对于读取,我们运行多个线程同时读取,
# 因为这样不会造成数据不一致问题,也能在一定程度上提高效率
Thread-9 线程 开始读取
Thread-9 线程 读取OK
Thread-10 线程 开始读取
Thread-5 线程 开始读取
Thread-11 线程 开始读取
Thread-11 线程 读取OK
Thread-10 线程 读取OK
Thread-7 线程 开始读取
Thread-7 线程 读取OK
Thread-6 线程 开始读取
Thread-5 线程 读取OK
Thread-14 线程 开始读取
Thread-8 线程 开始读取
Thread-14 线程 读取OK
Thread-6 线程 读取OK
Thread-13 线程 开始读取
Thread-12 线程 开始读取
Thread-13 线程 读取OK
Thread-8 线程 读取OK
Thread-12 线程 读取OK

10、阻塞队列

阻塞队列jdk1.8文档解释: 

 

 

BlockingQueue

blockingQueue 是Collection的一个子类;

什么情况我们会使用 阻塞队列呢?

多线程并发处理、线程池!

整个阻塞队列的家族如下:Queue以下实现的有Deque、AbstaractQueue、BlockingQueue;

BlockingQueue以下有Link链表实现的阻塞队列、也有Array数组实现的阻塞队列。

如何使用阻塞队列呢?

操作:添加、移除

但是实际我们要学的有:四组API

方式抛出异常不会抛出异常,有返回值阻塞 等待超时 等待
添加addofferputoffer(timenum,timeUnit)
移除removepolltakepoll(timenum,timeUnit)
判断队列首elementpeek--
/**
     * 抛出异常
     */
    public static void test1()
        //需要初始化队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        //抛出异常:java.lang.IllegalStateException: Queue full
//        System.out.println(blockingQueue.add("d"));
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        //如果多移除一个
        //这也会造成 java.util.NoSuchElementException 抛出异常
        System.out.println(blockingQueue.remove());
    
=======================================================================================
/**
     * 不抛出异常,有返回值
     */
    public static void test2()
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        //添加 一个不能添加的元素 使用offer只会返回false 不会抛出异常
        System.out.println(blockingQueue.offer("d"));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        //弹出 如果没有元素 只会返回null 不会抛出异常
        System.out.println(blockingQueue.poll());
    
=======================================================================================
/**
     * 等待 一直阻塞
     */
    public static void test3() throws InterruptedException 
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        //一直阻塞 不会返回
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");

        //如果队列已经满了, 再进去一个元素  这种情况会一直等待这个队列 什么时候有了位置再进去,程序不会停止
//        blockingQueue.put("d");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        //如果我们再来一个  这种情况也会等待,程序会一直运行 阻塞
        System.out.println(blockingQueue.take());
    
=======================================================================================
/**
     * 等待 超时阻塞
     *  这种情况也会等待队列有位置 或者有产品 但是会超时结束
     */
    public static void test4() throws InterruptedException 
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        System.out.println("开始等待");
        blockingQueue.offer("d",2, TimeUnit.SECONDS);  //超时时间2s 等待如果超过2s就结束等待
        System.out.println("结束等待");
        System.out.println("===========取值==================");
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println("开始等待");
        blockingQueue.poll(2,TimeUnit.SECONDS); //超过两秒 我们就不要等待了
        System.out.println("结束等待");
    

SynchronousQueue同步队列

同步队列 没有容量,也可以视为容量为1的队列

进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;

put方法 和 take方法;

Synchronized 和 其他的BlockingQueue 不一样 它不存储元素;

put了一个元素,就必须从里面先take出来,否则不能再put进去值!

并且SynchronousQueue 的take是使用了lock锁保证线程安全的

/**
 * 同步队列
 */
public class SynchronousQueueDemo 
    public static void main(String[] args) 
        BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
        //研究一下 如果判断这是一个同步队列

        //使用两个进程
        // 一个进程 放进去
        // 一个进程 拿出来
        new Thread(()->
            try 
                System.out.println(Thread.currentThread().getName()+" Put 1");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName()+" Put 2");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName()+" Put 3");
                synchronousQueue.put("3");
             catch (InterruptedException e) 
                e.printStackTrace();
            
        ,"T1").start();

        new Thread(()->
            try 
                System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
//                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
//                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());

             catch (InterruptedException e) 
                e.printStackTrace();
            
        ,"T2").start();
    

11、线程池(重点)

线程池:三大方法、7大参数、4种拒绝策略

池化技术

程序的运行,本质:占用系统的资源!我们需要去优化资源的使用 ===> 池化技术

线程池、JDBC的连接池、内存池、对象池 等等。。。。

资源的创建、销毁十分消耗资源

池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。

线程池的好处

1、降低资源的消耗;

2、提高响应的速度;

3、方便管理;

线程复用、可以控制最大并发数、管理线程

线程池:三大方法

  • ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
  • ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
  • ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//工具类 Executors 三大方法;
public class Demo01 
    public static void main(String[] args) 

        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
        ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
        ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的

        //线程池用完必须要关闭线程池
        try 

            for (int i = 1; i <=100 ; i++) 
                //通过线程池创建线程
                threadPool.execute(()->
                    System.out.println(Thread.currentThread().getName()+ " ok");
                );
            
         catch (Exception e) 
            e.printStackTrace();
         finally 
            threadPool.shutdown();
        
    

线程池:7大参数

源码分析

public static ExecutorService newSingleThreadExecutor() 
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));

public static ExecutorService newFixedThreadPool(int nThreads) 
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());

public static ExecutorService newCachedThreadPool() 
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());

本质:三种方法都是开启的ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,  //核心线程池大小
                          int maximumPoolSize, //最大的线程池大小
                          long keepAliveTime,  //超时了没有人调用就会释放
                          TimeUnit unit, //超时单位
                          BlockingQueue<Runnable> workQueue, //阻塞队列
                          ThreadFactory threadFactory, //线程工厂 创建线程的 一般不用动
                          RejectedExecutionHandler handler //拒绝策略
                         ) 
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;

阿里巴巴的Java操作手册中明确说明:对于Integer.MAX_VALU

以上是关于狂神JUC笔记的主要内容,如果未能解决你的问题,请参考以下文章

《狂神说-JUC》

《狂神说-JUC》

Java后端开发工程师学习笔记狂神说Java笔记

JUC并发编程 共享模式之工具 JUC Semaphore(信号量) -- Semaphore原理

JUC

内存Cache直接映射、全相联映射和组相联映射