JUC并发编程一(什么是JUC、线程和进程、Lock锁、生产者消费者问题、8锁现象、集合类不安全)

从b站狂神说Java处学习。
内容如下:
在这里插入图片描述

一、什么是juc(java.util.concurrent)并发编写

学习从:源码+官方文档
在这里插入图片描述

在这里插入图片描述

二、线程和进程

在这里插入图片描述
Java真的可以开启线程吗?
答:开不了
原因:看源码
thread的start方法调用了一个start0方法
native表示本地方法,底层C++,Java无法直接c操作硬件

private native void start0();
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 */
            }
        }
    }

    private native void start0();

在这里插入图片描述
在这里插入图片描述
看cpu核数的三种方法,1、代码2、任务管理器3、电脑管理器

在这里插入图片描述

回顾多线程

线程有六个状态 (不是五个)看Thread类源码

在这里插入图片描述

wait/sleep区别

在这里插入图片描述

比较点waitsleep
来自不同的类ObjectThread
关于锁的释放会释放锁不会释放
使用范围wait必须在同步代码块中任何地方都可以睡

三、Lock锁(重点)

传统示范(不建议这么使用):要使用面向对象的思想把代码简化降低耦合度,写一个资源类(面向资源类对象)
在这里插入图片描述

传统的Synchroized方式

线程就是一个单独的资源类,没有任何附属操作:
真正的多线程开发,公司的开发,降低耦合性:

因为Runnable底层是一个函数式接口@FunctionaltInterface,(意思是接口只有一个方法)可以用lambda表达式简化匿名内部类
在这里插入图片描述
在这里插入图片描述
OOP编程就是对象 对象就是属性、方法
在这里插入图片描述
接下来进入正题Lock锁
在这里插入图片描述
进入到locks包下的lock接口,lock接口的使用方法
在这里插入图片描述
怎么使用接口里面的方法:实现类
在这里插入图片描述
接了来用lock锁实现刚刚的卖票
ReentrantLock(瑞全踢lock)
在这里插入图片描述

公平锁、非公平锁

公平锁:十分公平,可以先来后到
非公平锁:十分公平,可以插队(默认)
为什么默认是非公平锁,因为符合现实相对公平,比如一个3s可以执行完的线程排在3h的线程之后,那么难道还让那个3s的等3h然后再执行吗?
在这里插入图片描述
在这里插入图片描述

lock和synchronize的对比

比较点locksynchronize
所处层面是Java中的一个接口,它有许多的实现类来为它提供各种功能Java中的一个关键字,当我们调用它时会从在虚拟机指令层面加锁
获得锁的方式Lock的使用离不开它的实现类AQS,而它的加锁并不是针对对象的,而是针对当前线程的,并且AQS中有一个原子类state来进行加锁次数的计数可对实例方法、静态方法和代码块加锁,相对应的,加锁前需要获得实例对象的锁或类对象的锁或指定对象的锁。说到底就是要先获得对象的监视器(即对象的锁)然后才能够进行相关操作。
获锁失败使用Lock加锁的程序中,获锁失败的线程会被自动加入到AQS的等待队列中进行自旋,自旋的同时再尝试去获取锁,等到自旋到一定次数并且获锁操作未成功,线程就会被阻塞加锁的程序中,获锁失败的对象会被加入到一个虚拟的等待队列中被阻塞,直到锁被释放;1.6以后加入了自旋操作
释放锁1、可调用ulock方法去释放锁比synchronized更灵活;2、还可以使用trylock(尝试获取锁)方法,意思是没等到释放就结束了;不能指定解锁操作,1、执行完代码块的对象会自动释放锁2、占有锁的线程发生了异常,此时JVM会让线程自动释放锁3、调用wait方法,释放锁进入wating状态
造成死锁而Lock必须在finally中主动unlock锁,否则就会出现死锁synchronized在发生异常时,会自动释放掉锁,故不会发生死锁现(此时的死锁一般是代码逻辑引起的),比如:1、线程1(获得锁1,【一秒后获得锁2】),线程2(获得锁2,【一秒后获得锁1】),然后就会一直等待;2、线程1(获得锁,阻塞)、线程2(等待,傻傻的等)造成死锁
效率方面比较高比较低
是否判断获得锁可以判断获得锁无法判断获得锁的状态
重入锁都是可重入锁,不可中断,非公平锁可重入锁,可以判断锁什么时候中断,非公平(可自己设置)
适用范围适合锁少量的代码同步适合锁大量的代码同步

锁是什么,怎么判断锁的是谁?

四、生产者消费者问题

面试常问:单例模式、排序算法、生产者消费者问题、死锁

juc版本:lock

传统写法:Synchronized 版 wait notify

在这里插入图片描述

单独的资源类,降低耦合性
在这里插入图片描述
上面这样两个线程是没有问题的,但是如果有多个线程呢
问题存在:比如线程ABCD会怎样?

虚假唤醒

AC + BD -
输出:
在这里插入图片描述

注意上面if判断,if判断只判断一次,当A进去方法后,C也不会停,会继续进入方法,假设while判断,一旦被修改,一个拿到锁了,另一个会等待

原因:看官方api,object类中的wait方法
在这里插入图片描述
所以要循环判断,while
在这里插入图片描述
输出:在这里插入图片描述

虚假唤醒发生的条件为:
1、当一个数据存在三个及以上的线程竞争访问时(必要条件)
2、至少有一个线程没有对数据进行加锁访问(充分条件,使得虚假唤醒发生可能)

当满足两个条件才可能发生虚假唤醒。仅仅是可能
解决虚假唤醒(以下任意一种方式即可):
1、所有的线程访问数据时都加锁
2、在循环中等待

但是用while会造成一个问题,因为在轮询切换时,可能会漏掉notify的中断,从而无休止的休眠下去,CPU消耗巨大

解决上面了但是仍然有问题,比如线程ABABCBABC…的随机顺序,如果我想有序执行ABCD该怎么办?

JUC版本的生产者消费者问题

之前我们已经了解到,lock锁代替synchroized
那么谁来代替wait和notify呢?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
点击进去codition接口的await方法
进入Condition类,然后可以发现
在这里插入图片描述

使用方法:

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock(); try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally { lock.unlock(); }
   }

   public Object take() throws InterruptedException {
     lock.lock(); try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally { lock.unlock(); }
   }
 } 

重要的是三步:

//1、用锁去创建它
final Lock lock = new ReentrantLock();
final Condition notFull  = lock.newCondition(); 
//等待await
notFull.await();
//唤醒signal
notEmpty.signal();
//唤醒全部
notFull.singalAll();

然后用上面那个示例,修改一下,-方法也改成跟这个一样
在这里插入图片描述

继续拿四条线程来试一下ABCD AC+ BD-
输出:
在这里插入图片描述
由此看出用lock不会产生虚假线程,但是仍然会有跟上面while循环的一样的线程执行顺序问题

是不是没有什么变化
但是任何一个新技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充:
意思是使用notify和wait实现不了

Condition 精准的通知和唤醒线程

因为一个Condition(监视器)只能监视一个方法,然后通过监视器来监视我要唤醒的是哪个线程,所以刚刚监视了三个

/**
 * A 执行完调用B,B执行完调用C,C执行完调用A
 */
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{ // 资源类 Lock

    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()+"=>AAAAAAA");
            // 唤醒,唤醒指定的人,B
            number = 2;
            condition2.signal();
        } 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()+"=>BBBBBBBBB");
            // 唤醒,唤醒指定的人,c
            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()+"=>CCCCCCCCC");
            // 唤醒,唤醒指定的人,c
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

输出:

A=>AAAAAAA
B=>BBBBBBBBB
C=>CCCCCCCCC
A=>AAAAAAA
B=>BBBBBBBBB
C=>CCCCCCCCC
A=>AAAAAAA
B=>BBBBBBBBB
C=>CCCCCCCCC

java.lang.IllegalMonitorStateException是在调用object的wait和notify,notifyAll方法的时候可能会出现的异常

官方说法:如果当前线程不是对象监视器(锁)的所有者。
理解:在synchronized中调用上述三种方法的对象,不是你用synchronized锁住的对象就会报错

Object lock = new Object();
synchronized(lock){
    lock.wait();
}
 
如果是
Object lock = new Object();
synchronized(lock){
    new Object().wait();
}
报错

五、八锁现象

如何判断锁的是谁!永远的知道什么锁,锁到底锁的是谁!
并不是八锁只是为了深刻理解我们的锁

只是八道题:

  • synchronized 锁的对象是方法的调用者(p1)!
  • 两个方法用的是同一个锁,谁先拿到谁执行!(不能回答先调用,是先拿到锁)
package com.kuang.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁,就是关于锁的8个问题
 * 1、标准情况下,两个线程先打印 发短信还是 打电话? 1/发短信  2/打电话
 * 2、sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信  2/打电话
 */
public class Test1 {
    public static void main(String[] args) {
        Phone p1 = new Phone();

        //锁的存在
        new Thread(()->{
            p1.sendSms();
        },"A").start();

        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            p1.call();
        },"B").start();
    }
}

class Phone{

    // synchronized 锁的对象是方法的调用者!、
    // 两个方法用的是同一个锁,谁先拿到谁执行!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

}

  • 两个对象,两个调用者,两把锁!
  • hello这里没有锁!不是同步方法,不受锁的影响
package com.kuang.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 3、 增加了一个普通方法后!先执行发短信还是Hello? 普通方法
 * 4、 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
 */
public class Test2  {
    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.call();
        },"B").start();
    }
}

class Phone2{

    // synchronized 锁的对象是方法的调用者!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

    // 这里没有锁!不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }

}

  • // static 静态方法 // 类一加载就有了!锁的是Class类 //一个类只有一个Class对象
package com.kuang.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 5、增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话? 1、发短信 2、打电话
 * 6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?  1、发短信  2、打电话
 */
public class Test3  {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        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(()->{
            phone2.call();
        },"B").start();
    }
}

// Phone3唯一的一个 Class 对象
class Phone3{

    // synchronized 锁的对象是方法的调用者!
    // static 静态方法
    // 类一加载就有了!锁的是Class
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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


}

  • 静态的同步方法 锁的是 Class 类模板
  • 普通的同步方法 锁的调用者

/**
 * 1、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?    先打电话
 * 2、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?    先打电话
 */
public class Test4  {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

// Phone3唯一的一个 Class 对象
class Phone4{

    // 静态的同步方法 锁的是 Class 类模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    // 普通的同步方法  锁的调用者
    public synchronized void call(){
        System.out.println("打电话");
    }

}

小结

new this 具体的一个手机(引用对象)
static Class 唯一的一个模板

六、集合类不安全

List不安全

  • java.util.ConcurrentModificationException 并发修改异常!

面试的时候多说些这种异常,内存溢出、栈溢出、或者是上述提到的异常,一定是在实际代码过程中出现的异常
在这里插入图片描述
注意:这里用的是for循环包的线程,跟之前案例中线程方法里面接的for循环不一样,一个是创建多个线程,一个是线程内循环多次
ArrayList默认是线程不安全的,所以会报错。
解决方案:

  1. 用Vector就好了 (如果这么说很可能被问到,arraylist的add是怎么写的)

就是一个扩容的过程

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

arraylist的初始容量

private static final int DEFAULT_CAPACITY = 10;

但是ArrayList是jdk1.2出来的,而Vector在jdk1.0就出来了比ArrayList早,别人肯定会想到这个解决方案不要自作聪明,这个方案只要提一嘴就行了然后说他不推荐使用。

  1. 解决方案2,我们可以把arraylist变成安全的,数组有工具类叫Arrays,集合有工具类叫Collections
    List list = Collections.synchronizedList(new ArrayList<>());

  2. 上面是普通人能想到的,接下来介绍JUC
    List list = new CopyOnWriteArrayList<>();
    在这里插入图片描述
    // CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略;
    // 多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)
    // 在写入的时候复制了一份避免覆盖,造成数据问题!
    // 读写分离

CopyOnWriteArrayList 比 Vector Nb 在哪里?
答:只要有synchronized的方法,相对效率比较低,Vector的底层add是synchronized修饰方法的,新版的是lock锁,并且是写入时复制一份再设置一份新的

package com.kuang.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

// java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {
    public static void main(String[] args) {
        // 并发下 ArrayList 不安全的吗,Synchronized;
        /**
         * 解决方案;
         * 1、List<String> list = new Vector<>();
         * 2、List<String> list = Collections.synchronizedList(new ArrayList<>());
         * 3、List<String> list = new CopyOnWriteArrayList<>();
         */
        // CopyOnWrite 写入时复制  COW  计算机程序设计领域的一种优化策略;
        // 多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)
        // 在写入的时候避免覆盖,造成数据问题!
        // 读写分离
        // CopyOnWriteArrayList  比 Vector Nb 在哪里?

        List<String> list = new CopyOnWriteArrayList<>();

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

    }
}

学习方法推荐:1、先会用、2、货比3家,寻找其他解决方案,3、分析源码!

Set不安全

  • //1、Set set = Collections.synchronizedSet(new HashSet<>());
  • //2、Set set = new CopyOnWriteArraySet<>();
package com.kuang.unsafe;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * 同理可证 : ConcurrentModificationException
 * //1、Set<String> set = Collections.synchronizedSet(new HashSet<>());
 * //2、Set<String> set = new CopyOnWriteArraySet<>();
 */
public class SetTest {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        // hashmap
        // Set<String> set = Collections.synchronizedSet(new HashSet<>());
        // Set<String> set = new CopyOnWriteArraySet<>();

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

    }
}

hashset的底层有什么
public HashSet() {
    map = new HashMap<>();
}
// add set 本质就是 map key是无法重复的!
 public boolean add(E e) {
     return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object(); // 不变得值!

HashMap不安全

回顾Map基本操作
初始容量16,加载因子0.75
在这里插入图片描述
解决方案:

  • Collections工具类
  • juc:concurrentHashMap(类似于上面的CopyOnWriteArraySet)
package com.kuang.unsafe;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

// ConcurrentModificationException
public class MapTest {

    public static void main(String[] args) {
        // map 是这样用的吗? 不是,工作中不用 HashMap
        // 默认等价于什么?  new HashMap<>(16,0.75);
        // Map<String, String> map = new HashMap<>();
        // 唯一的一个家庭作业:研究ConcurrentHashMap的原理
        Map<String, String> map = new ConcurrentHashMap<>();

        for (int i = 1; i <=30; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无极的移动代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值