java并发编程(1/2)

JUC并发编程

视频地址:https://www.bilibili.com/video/BV1B7411L7tE

笔记地址:https://blog.csdn.net/qq_36188127/article/details/110501841

1、什么是 JUC

JUC 就是 java.util.concurrent 下面的类包,专门用于多线程的开发。

2、线程和进程

2.1、什么是线程

进程是操作系统分配资源的最小单元xxx.exe,而线程是cpu调度的最小单元。

线程是进程中的一个实体,线程本身是不会独立存在的。

进程是代码在数据集合上的一次运行活动, 是系统进行资源分配和调度的基本单位。

线程则是进程的一个执行路径, 一个进程中至少有一个线程,进程中的多个线程共享进程的资源。

操作系统在分配资源时是把资源分配给进程的, 但是CPU 资源比较特殊, 它是被分配到线程的, 因为真正要占用C PU 运行的是线程, 所以也说线程是CPU 分配的基本单位。

Java 默认有 2 线程 !(mian 线程GC 线程

Java 中,使用 Thread、Runnable、Callable 开启线程。

Java 没有权限开启线程Thread.start() 方法调用了一个 native 方法 start0(),它调用了底层 C++ 代码。

thread底层源码

   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 */
           }
       }
   }
// Java 没有权限操作底层硬件的
   private native void start0();

2.2、并发与并行的区别

并发(多线程操作同一个资源)

  • 是指同一个时间段内多个任务同时都在执行,并都没有执行结束。

并行多线程同时操作不同资源

  • 是说在单位时间内多个任务同时在执行。

CPU单核,多个任务都是并发执行的,这是因为单个 CPU 同时只能执行一个任务。

CPU多核 意味着每个线程可以使用自己的 CPU 运行,这减少了上下文切换的开销,但随着对应用系统性能核吞吐量要求的提高,出现了处理海量数据核请求的要求,这些都对高并发编程有着迫切的需求。

public class test01 {
    public static void main(String[] args) {
        // 获取CPU的核数
       System.out.println(Runtime.getRuntime().availableProcessors());//8
    }
}

Java三高指的是什么

  • 高并发

  • 高可用

  • 高性能

并发编程的本质:

  • 充分利用CPU的资源

线程的状态

//线程状态
Thread.State.

public enum State {
    //新生
    NEW,
    //运行
    RUNNABLE,
    //阻塞
    BLOCKED,
     //等待
    WAITING,
	//超时等待
    TIMED_WATING
    //死亡
    TERMINATED

wait/seelp区别

  1. 来自不同的类

  • wati ==> Object

  • sleep ==> Thread

企业中使用休眠的一般方式:

 TimeUnit.DAYS.sleep(1);// 休眠一天
TimeUnit.SECONDS.sleep(1);// 休眠一秒
  1. 关于锁的释放

wait会释放锁,sleep睡觉了会抱着锁睡觉不会释放

  1. 使用范围不同

wait 必须在同步代码块中;

sleep 可以在任何地方;

  1. 是否需要捕获异常

wait 不需要捕获异常;

sleep 必须要捕获异常;

3、Lock

  • 锁是一种工具,用于控制对共享资源的访问

  • Lock和synchronized,这两个是最经常创建的锁,他们都可以达到线程安全的目的,但是使用和功能上有较大不同

  • Lock不是完全替代synchronized的,而是当使用synchronized不合适或不足以满足要求的时候,提供高级功能

  • Lock 最常见的是ReentrantLock实现

3.1、使用 synchroniezd 关键字加锁

代码演示

public class test01 {
    public static void main(String[] args) {
        saleTicket ticket = new saleTicket();

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

class saleTicket{
    private int ticket = 40;

    //未使用synchronized拿票顺序不统一计数有误,使用synchronized 后拿票顺序整齐
    public synchronized void sale(){
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"达到了第"+ticket--+"张票,剩余"+ticket+"张票");
        }
    }
}

未使用synchronize:

使用synchronize:

3.2、使用Lock类加锁

ReentrantLock(true/false) 可重入锁分类

  • false=>NonfairSync 非公平锁,可以插队(默认)

  • true=>FairSync 公平锁,不可以插队,先来后到

代码使用

public class test01 {
    public static void main(String[] args) {
        saleTicket ticket = new saleTicket();
        
        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();
    }
}

class saleTicket{
    private int ticket = 40;

    /**lock三部曲
     * 1.创建锁对象new ReentrantLock();
     * 2.加锁 lock.lock();使用try-catch包裹业务代码
     * 3.解锁 finally=>lock.unlock();
     * */
    public synchronized void sale(){
        Lock lock = new ReentrantLock();
        lock.lock();
        try {
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"达到了第"+ticket--+"张票,剩余"+ticket+"张票");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

控制台输出

3.3 Synchronized 与Lock 的区别

① Synchronized 是 Java 内置关键字,Lock 是一个 Java 类

② Synchronied无法判断取锁的状态,Lock 可以判断

③ Synchronied 会自动释放锁,Lock 必须要手动加锁和手动释放锁(unlock)!可能会遇到死锁

④ Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

⑤ Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

⑥ Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码

注: “广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。“

锁的分类

1.图解

特点:

  • 从线程是否需要对资源加锁可以分为 悲观锁乐观锁

  • 从资源已被锁定,线程是否阻塞可以分为 自旋锁

  • 从多个线程并发访问资源,也就是 Synchronized 可以分为 无锁偏向锁轻量级锁重量级锁

  • 从锁的公平性进行区分,可以分为公平锁非公平锁

  • 从根据锁是否重复获取可以分为 可重入锁不可重入锁

  • 从那个多个线程能否获取同一把锁分为 共享锁排他锁

资料: https://mp.weixin.qq.com/s/Y8_Fy5gOrgH7WH9CzpGtzA

4、 生产者和消费者的关系

4.1 Synchronized 版本

代码演示

public class test02 {
    public static void main(String[] args) {
        //生产者消费者特点:判断等待,业务操作,通知线程
        data data = new data();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}

//这是一个缓冲类,生产和消费之间的仓库
class data{
    //    这是仓库的资源,生产者生产资源,消费者消费资源
    private int num = 0;

    //    +1,生产者生产资源
    public synchronized void increment() throws InterruptedException {
        // 首先查看仓库中的资源(num),如果资源不为0,就利用 wait 方法等待消费,释放锁
        if(num!=0){
            //利用关键字加锁
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"-->"+num);
        // 唤醒通知其他线程 +1 执行完毕
        this.notifyAll();
    }

    //-1 消费者消费资源
    public synchronized void decrement() throws InterruptedException {
        if(num==0){
            this.wait();
        }

        num--;
        System.out.println(Thread.currentThread().getName()+"-->"+num);
        // 唤醒通知其他线程 -1 执行完毕
        this.notifyAll();
    }
}

测试结果

若有四个线程,使用if判断的话则会出现虚假唤醒问题 会出现数字2,3的情况

解决办法: if 改为 while,防止虚假唤醒

//这是一个缓冲类,生产和消费之间的仓库
class data{
    //    这是仓库的资源,生产者生产资源,消费者消费资源
    private int num = 0;

    //    +1,生产者生产资源
    public synchronized void increment() throws InterruptedException {
        // 首先查看仓库中的资源(num),如果资源不为0,就利用 wait 方法等待消费,释放锁
        while (num!=0){
            //利用关键字加锁
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"-->"+num);
        // 唤醒通知其他线程 +1 执行完毕
        this.notifyAll();
    }

    //-1 消费者消费资源
    public synchronized void decrement() throws InterruptedException {
        while(num==0){
            this.wait();
        }

        num--;
        System.out.println(Thread.currentThread().getName()+"-->"+num);
        // 唤醒通知其他线程 -1 执行完毕
        this.notifyAll();
    }
}
  • 结论:使用if判断只会执行一次判断,而多个线程同时操作的的情况下可能会同时+1,而使用while重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

4.2 Lock 版本

代码展示

public class test03 {
    public static void main(String[] args) {
        //生产者消费者特点:判断等待,业务操作,通知线程
        data2 data = new data2();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"D").start();

    }
}


class data2{

    private int num = 0;
    Lock lock =new ReentrantLock();
    Condition condition = lock.newCondition();
//    condition.await();//等待
//    condition.signalAll();//唤醒

    public  void increment() throws InterruptedException {
        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();
        }
    }

    public synchronized void decrement() throws InterruptedException {
        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();
        }
    }
}

结果输出

代码对比

如何做到ABCD线程按照指定顺序执行呢?

Condition 的优势

精准的等待和唤醒线程!

  • condition.await();//等待

  • condition.signalAll();//唤醒

举例:指定通知下一个进行顺序。

代码演示

public class test03 {
    public static void main(String[] args) {
        //生产者消费者特点:判断等待,业务操作,通知线程
        data2 data = new data2();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.printA();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.printB();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.printC();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C").start();

    }
}


class data2{

    private int num = 1;
    Lock lock =new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
//    condition.await();//等待
//    condition.signalAll();//唤醒全部
//    condition.signal();//唤醒单个

    public  void printA() throws InterruptedException {
        lock.lock();
        try {
            while (num!=1){
                condition1.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"-->AAAAA");
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public  void printB() throws InterruptedException {
        lock.lock();
        try {
            while (num!=2){
                condition2.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"-->BBBBB");
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public  void printC() throws InterruptedException {
        lock.lock();
        try {
            while (num!=3){
                condition3.await();
            }
            num=1;
            System.out.println(Thread.currentThread().getName()+"-->CCCCC");
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

结果演示:

5、8 锁现象

1.两个同步方法,先执行发短信还是打电话?

public class test04 {
    public static void main(String[] args) {
        phone phone = new phone();
        new Thread(()->{
            phone.sendMsg();
        }).start();

        //延迟1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        }).start();

    }
}

class phone{
    public synchronized void sendMsg(){
        System.out.println("发短信");
    }

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

结果:

结论:一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能等待,换句话说,某一时刻内,只能有唯一一个线程去访问这些synchronized方法。

2.将发短信方法增加延迟4秒

class phone{
    public synchronized void sendMsg(){
        //延迟4秒
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

结果:

结论锁的是当前对象this,被锁定后,其他线程都不能进入到当前对象的其他的synchronized方法。

3.将打电话改变为普通方法

class phone{
    public synchronized void sendMsg(){
        //延迟4秒
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

}

输出结果:

先输出打电话,4秒后输出发短信

将普通方法call()增加4秒延迟

class phone{
    public synchronized void sendMsg(){
        //延迟4秒
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public  void call(){
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打电话");
    }

}

输出结果:

5秒后输出发短信,后输出打电话

结论:加个普通方法后发现和同步锁无关。普通方法不受synchronized锁的影响

4.两个同步方法,使用new 出两个对象执行分别执行其中方法

public class test04 {
    public static void main(String[] args) {
        phone phone = new phone();
        phone phone1 = new phone();
        new Thread(()->{
            phone.sendMsg();
        }).start();

        //延迟1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        }).start();

    }
}

class phone{
    public synchronized void sendMsg(){
        System.out.println("发短信");
    }

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

}

结果输出:

将发短信方法增加3秒延迟

class phone{
    public synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

}

输出结果:

输出打电话后,两秒后输出发短信

结论:两个对象执行,就产生了两把锁,之间互不干扰。

5.一个对象调用用两个静态同步方法

public class test04 {
    public static void main(String[] args) {
        phone phone = new phone();
        new Thread(()->{
            phone.sendMsg();
        }).start();

        //延迟1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        }).start();

    }
}

class phone{
    public static synchronized void sendMsg(){
        System.out.println("发短信");
    }

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

输出结果:

  • 将sendMsg()方法延迟3秒执行

class phone{
    public static synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

输出结果:3秒钟后显示

  • 将打call()方法设置为普通同步方法

class phone{
    public static synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

输出结果:3秒钟后显示结果

**结论:**静态同步方法锁的是这个class,谁先拿到这个锁就先执行。静态与非静态方法调用互不影响

6.两个对象调用两个静态同步方法

public class test04 {
    public static void main(String[] args) {
        phone phone = new phone();
        phone phone1 = new phone();
        new Thread(()->{
            phone.sendMsg();
        }).start();

        //延迟1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        }).start();

    }
}

class phone{
    public static synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

输出结果:

测试:无论是否在sendMsg()方法加延迟,始终是发短信在前打电话在后

结论:

  • 两个对象调用静态方法其实是调用同一class锁,所以谁先拿到锁先执行。

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

7.一个对象,一个静态同步方法,一个普通同步方法情况

public class test04 {
    public static void main(String[] args) {
        phone phone = new phone();
        new Thread(()->{
            phone.sendMsg();
        }).start();

        //延迟1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        }).start();

    }
}

class phone{
    public static synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

输出结果:先输出打电话,3秒钟后输出发短信

结论: 因为一个锁的是Class类的模板,一个锁的是对象的调用者。所以不存在等待,直接运行。

8.两个对象,一个静态同步方法,一个普通同步方法情况

public class test04 {
    public static void main(String[] args) {
        phone phone = new phone();
        phone phone1 = new phone();
        new Thread(()->{
            phone.sendMsg();
        }).start();

        //延迟1秒
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        }).start();

    }
}

class phone{
    public static synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

输出结果:先输出打电话,3秒钟后输出发短信

**结论:**两个对象使用两把锁,锁的不是同一东西。同时进行有延迟的后输出。这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间不会有竞争条件。

小结:

  • 所有的非静态同步方法用的都是同一把锁 -- 实例对象本身(this)

  • 所有的静态同步方法用的也是同一把锁 -- 类对象本身(class)

6、多线程集合安全问题

6.1、List多线程

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

  • List list = new CopyOnWriteArrayList<>();

public class ListTest {
    public static void main(String[] args) {
        //java.util.ConcurrentModificationException 并发修改异常
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }).start();
        }
    }
}

结果:

总结:原生ArryList在多线程下会出现并发修改异常

解决方法

public class ListTest {
    public static void main(String[] args) {
        //java.util.ConcurrentModificationException 并发修改异常
//        List<String> list = new ArrayList<>();

        //解决方法:
//      1、List<String> list = new Vector<>();
//       2、List<Object> list = Collections.synchronizedList(new ArrayList<>());

        //3、 CopyOnWrite写入时复制 简称:COW 这是一种计算机程序设计领域的一种优化策略
        //多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)
        // 在写入的时候避免覆盖,造成数据问题!
        // 读写分离
        List<Object> list = new CopyOnWriteArrayList<>();

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

结果:

Vector、synchronizedList、CopyOnWriteArrayList有什么区别呢?

源码分析:

Vector

synchronizedList:

CopyOnWriteArrayList:

总结:

相比于同步锁synchronized,lock锁更加高效,所以优先使用CopyOnWriteArrayList

6.2、Set多线程

  • Set set = Collections.synchronizedSet(new HashSet<>());

  • Set set = new CopyOnWriteArraySet<>();

public class SetTest {
    public static void main(String[] args) {
//        Set<Object> set = new HashSet<>();//报并发修改异常
//        Set<Object> set = Collections.synchronizedSet(new HashSet<>());//解决方法一
        Set<Object> set = new CopyOnWriteArraySet<>();//解决方法二
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }).start();
        }

    }
}

结果输出:

原理与List相似

HashSet 底层到底是什么?

//new HashSet()
public HashSet() {
    map = new HashMap<>();
}

//add方法
 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

6.3、Map多线程

  • Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());

  • Map<Object, Object> map = new ConcurrentHashMap<>();

public class MapTest {
    public static void main(String[] args) {
        //默认加载因子是0.75,默认的初始容量是16
//        Map<Object, Object> map = new HashMap<>();//报并发修改异常
        
//        Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());//解决方法一
        Map<Object, Object> map = new ConcurrentHashMap<>();//解决方法二
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 5));
                System.out.println(map);
            },i+"").start();
        }
    }
}

结果输出:

7、Callable创建线程

特点:

  1. 可获取线程返回值

  1. 多次调用callable线程只调用一次方法。

public class CallableTest {
    public static void main(String[] args) {

        //Thread()-构造方法->Thread(Runnable target)
        //Runnable-实现类->FutureTask
        //FutureTask-构造方法->FutureTask(Callable<V> callable)
        //通过FutureTask类这样就可以使用Thread启动Callable线程
        ThreadTest threadTest = new ThreadTest();
        FutureTask futureTask = new FutureTask(threadTest);
        new Thread(futureTask).start();

        try {
            String obj = (String) futureTask.get();
            System.out.println(obj);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class ThreadTest implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("执行线程");
        return "hello callable";
    }
}

执行结果:

若调用两次Callable线程,会执行几次方法?

public static void main(String[] args) {
    ThreadTest threadTest = new ThreadTest();
    FutureTask futureTask = new FutureTask(threadTest);
    new Thread(futureTask).start();//第一次调用
    new Thread(futureTask).start();//第二次调用

    try {
        String obj = (String) futureTask.get();
        System.out.println(obj);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

执行结果:只调用一次线程。

原理:FutureTask 构造方法,

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // 第一调用设置该线程状态为new,第二次进来则不会调用
}

8、常用辅助类

8.1、CountDownLatch

减法计数器:只有计数器归零,然后程序向下执行 countDownLatch.await();

public class CountDownLatchTest {
    public static void main(String[] args) {
        //情景:教室里有6个学生都走了之后才能关门
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            final int temp=i;
            new Thread(()->{
                System.out.println("教室里第"+temp+"学生走了");
                countDownLatch.countDown(); //每个线程都数量-1
            }).start();
        }
        try {
            countDownLatch.await();// 等待计数器归零,然后向下执行
            System.out.println("教室里学生都走了,关闭教室门");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果输出:

测试代码:

8.2、CyclickBarrier

加法计数器 new CyclicBarrier(int),

  • 没有达到计时器数量,程序一直等待

  • 超出计时器属性,程序卡住

public class CyclicBarrierTest {
    public static void main(String[] args) {
        //情景:集齐7颗龙珠,方可召唤神龙
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("已经集齐7颗龙珠,可召唤神龙");
        });
        for (int i = 1; i <=7; i++) {
            final int temp=i;
            new Thread(()->{
                System.out.println("收集了第"+temp+"颗龙珠");
                try {
                    cyclicBarrier.await();//加法计数等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

输出结果:

测试代码:

8.3、Semaphore

  • 设置信号点-规定一次有几个线程可以同时进行

  • 线程少于设定信号点,程序直接运行结束

  • 线程多于设定信号点,按照一出一进操作,直到线程走完

Semaphore semaphore = new Semaphore(3);

public class SemaphoreTest {
    public static void main(String[] args) {
        //情景:3个车位,6辆车。走一批,进入一批
        //设置信号点-规定一次有几个线程可以同时进行
        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();//释放资源,让下一个线程进来
                }
            }).start();
        }
    }
}

测试结果:

调试代码:

9、ReadWriteLock(读写锁)

public class ReadWriteLockTest {
    public static void main(String[] args) {
//        线程操作资源类
        MyCache myCache = new MyCache();
        int num = 6;
        for (int i = 1; i < num; i++) {
            int finalI = i;
            new Thread(()->{
                myCache.writeReadWrite(String.valueOf(finalI),String.valueOf(finalI));

            },String.valueOf(i)).start();
        }
        for (int i = 1; i < num; i++) {
            int finalI = i;
            new Thread(()->{
                myCache.readReadWriteLock(String.valueOf(finalI));
            },String.valueOf(i)).start();
        }
    }
}

/*
 * 自定义缓存
 *
 * */
class MyCache{
    //volatile能保证顺序性和可见性,但是不能保证原子性,不过能增加概率
    private volatile Map<String,String> map = new HashMap<>();

    public  void write(String key,String value){
        System.out.println(Thread.currentThread().getName()+"线程开始写入");
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"线程开始写入ok");
    }
    //输出结果
    /**
     * 1线程开始写入
     * 5线程开始写入
     * 4线程开始写入
     * 3线程开始写入
     * 2线程开始写入
     * 2线程开始写入ok
     * 3线程开始写入ok
     * 4线程开始写入ok
     * 5线程开始写入ok
     *///若不加锁的情况下,同时被多个线程写入

/使用锁,达到同时只有一个线程来写
    //一:使用同步锁
    public synchronized void writeSyn(String key,String value){
        System.out.println(Thread.currentThread().getName()+"线程开始写入");
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"线程开始写入ok");
    }
    //输出结果
    /**
     * 1线程开始写入
     * 1线程开始写入ok
     * 5线程开始写入
     * 5线程开始写入ok
     * 4线程开始写入
     * 4线程开始写入ok
     */

    //二:使用lock锁
    private Lock lock = new ReentrantLock();
    public void writeLock(String key,String value){
        lock.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 {
            lock.unlock();
        }
    }
    //输出结果
    /**
     * 1线程开始写入
     * 1线程开始写入ok
     * 2线程开始写入
     * 2线程开始写入ok
     * 3线程开始写入
     * 3线程开始写入ok
     * 4线程开始写入
     * 4线程开始写入ok
     */

    //三:使用读写锁
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public  void writeReadWrite(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();//写锁 解锁
        }
    }
    //readWriteLock 输出结果
    /**
     * 1线程开始写入
     * 1线程开始写入ok
     * 1线程开始读取
     * 1线程读取ok
     * 2线程开始写入
     * 3线程开始读取
     * 2线程开始读取
     */

    //      取,读 同时可以让多个线程读
    public void read(String key){
        System.out.println(Thread.currentThread().getName()+"线程开始读取");
        map.get(key);
        System.out.println(Thread.currentThread().getName()+"线程读取ok");
    }

    //使用读写锁加锁
    public void readReadWriteLock(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"线程开始读取");
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"线程读取ok");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }

    }
    //输出结果
/**
 * 1线程开始写入
 * 1线程开始写入ok
 * 2线程开始写入
 * 2线程开始写入ok
 * 3线程开始写入
 * 3线程开始写入ok
 * 4线程开始写入
 * 4线程开始写入ok
 * 5线程开始写入
 * 5线程开始写入ok
 * 1线程开始读取
 */
}

Valotile

valotile跟synchronized一样,是Java内置的关键字。不过valotile只能修饰变量。valotile主要的作用是保证变量在内存中的可见性、有序性

可见性:valotile修饰的变量被修改后,对内存中是立即可见的。举个例子:有两个线程A、B。有一个valotile修饰的变量Y。当线程A对变量Y进行修改后,线程B能够立刻感知。感知到后会重新读取变量Y的最新值放到自己工作内存中进行操作。

有序性:我们都知道,Java代码是一行一行根据编写顺序去运行的。看着是顺序执行没错。但是实际底层JVM在执行字节码文件指令时,有些指令并不会依照代码顺序一样执行。因为Java编译器会在编译成字节码的时候为了提高性能会对你的代码进行指令优化,这个优化叫做指令重排。这个指令重排在单线程环境下不会有问题,因为不管你怎么重排指令,最后的结果都是期望的。但是如果在多线程环境下,就会有线程安全问题。所以valotile帮我们禁止了指令重排的优化,保证了编译器是严格按照代码编写顺序去编译指令。

10、阻塞队列

10.1、阻塞队列BlockQueue

阻塞队列 BlockQueue 是Collection 的一个子类

应用场景:多线程并发处理、线程池

BlockingQueue 有四组 API

方式

抛出异常

不会抛出异常

阻塞等待

超时等待

添加

add

offer

put

offer(timenum,timeUnit)

移出

remove

poll

take

poll(timenum,timeUnit)

判断队首元素

element

peek

-

-

代码说明:

public class BlockingQueue {
    /*
     * 抛出异常
     * add、remove、element
     * */
    @Test
    public void testTryCatch(){
        //设置阻塞队列大小为3
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        // add添加元素
        System.out.println(blockingQueue.add(1));//true
        System.out.println(blockingQueue.add(2));//true
//        System.out.println(blockingQueue.add(2));//true 说明元素可重复
        System.out.println(blockingQueue.add(3));//true
        System.out.println(blockingQueue.add(4));//抛出异常   java.lang.IllegalStateException: Queue full

        //移除方法 按照先入先出顺序移除元素
        System.out.println(blockingQueue.remove());//1
        System.out.println(blockingQueue.remove());//2
        System.out.println(blockingQueue.remove());//3
       System.out.println(blockingQueue.remove());//抛出异常 java.util.NoSuchElementException

    }

    /*
     * 不抛出异常,offer、poll、peek
     * */
    @Test
    public void testTrueOrFalse() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // offer添加元素
        System.out.println(blockingQueue.offer(1));//true
        System.out.println(blockingQueue.offer(2));//true
        System.out.println(blockingQueue.offer(3));//true
        System.out.println(blockingQueue.offer(4));//false
        System.out.println(blockingQueue.offer(5, 5, TimeUnit.SECONDS));//设置5秒等待,超过5秒插不进去则返回false

        //poll按照先入先出移除元素
        System.out.println(blockingQueue.poll());//1
        System.out.println(blockingQueue.poll());//2
        System.out.println(blockingQueue.poll());//3
        System.out.println(blockingQueue.poll());//null
        System.out.println(blockingQueue.poll( 5, TimeUnit.SECONDS));//设置5秒等待,超过5秒无法移除则返回 null


    }

    /*
     * 等待,一直阻塞
     * */
    @Test
    public void test03() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
//      一直阻塞,不会返回值
        blockingQueue.put(1);
        blockingQueue.put(2);
        blockingQueue.put(3);
        System.out.println(blockingQueue.size());//3
        //blockingQueue.put(4);//程序一直运行阻塞

//        若队列已满,再添加,则阻塞等待添加,返回被取出的值
        System.out.println(blockingQueue.take());//1
        System.out.println(blockingQueue.take());//2
        System.out.println(blockingQueue.take());//3
        System.out.println(blockingQueue.size());//0
//        System.out.println(blockingQueue.take());//程序一直运行阻塞
    }

    /*
     * 超时等待
     * 这种情况也会发生阻塞等待,但超过约定的时间会结束等待程序继续走
     * */
    @Test
    public void test04() throws InterruptedException {
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        System.out.println("开始等待");
        blockingQueue.offer("d",5, TimeUnit.SECONDS);
        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(5, TimeUnit.SECONDS);//超过两秒,我们就不等待了
        System.out.println("取值结束等待");
    }
}

10.2同步队列Synchronized

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

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

put 方法和 take 方法;

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

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

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

public class SynchronousQueue {
    public static void main(String[] args) {
        java.util.concurrent.SynchronousQueue<String> synchronousQueue = new java.util.concurrent.SynchronousQueue<>();
//存储
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"put 01");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName()+"put 02");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName()+"put 03");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
//取出
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
                System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
                System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值