Java多线程与并发编程

news2024/12/27 22:58:38

课程地址:
https://www.itlaoqi.com/chapter.html?sid=98&cid=1425
源码文档:
链接:https://pan.baidu.com/s/1WMvM3j6qhyjIeAT87kIcxg
提取码:5g56

Java多线程与并发编程

      • 1-并发背后的故事
          • 什么是并发
      • 2-你必须知道线程的概念
          • 程序、进程与线程
          • 并发和并行
          • 同步和异步
          • 临界区
          • 线程活跃度(死锁,饥饿,活锁)
          • 线程安全
      • 3-Java内存模型(JMM)
          • Java内存模型全称JMM(Java Memory Model)
      • 4-创建多线程-继承Thread
      • 5-创建多线程-实现Runnable接口
      • 6-创建多线程-实现Callable接口
      • 7-Synchronized线程同步机制
          • Synchronize的使用场景
      • 8-面试题-线程的五种状态
      • 9-死锁的产生
      • 10-重新认识线程安全ThreadSafe
      • 11-JAVA并发包与线程池
          • 什么是线程池
          • new Thread的弊端
          • ThreadPool - 线程池
          • 线程池的种类
      • 12-JUC之CountDownLatch倒计时锁
      • 13-JUC之Semaphore信号量
      • 14-JUC之CyclicBarrier循环屏障
          • CyclicBarrier的应用场景
      • 15-JUC之ReentrantLock重入锁
          • ReentrantLock与synchronized的区别
      • 15.1-JUC之Condition线程等待与唤醒
      • 16-JUC之Callable_Future
      • 17-JUC之同步容器
          • 请写出线程安全的类
          • 线程安全-并发容器
      • 18-JUC之Atomic与CAS算法(乐观锁)
          • 回顾原子性
          • Atomic包
          • Atomic的应用场景
      • 19-课程总结

1-并发背后的故事

什么是并发

并发就是指程序同时处理多个任务的能力。
并发编程的根源在于对多任务情况下对访问资源的有效控制

例如多人同时操作在线文档等
在这里插入图片描述

2-你必须知道线程的概念

程序、进程与线程

程序是静态的概念,windows下通常指exe文件。

进程是动态的概念,是程序在运行状态,进程说明程序在内存中的边界。
线程是进程内的一个”基本任务”,每个线程都有自己的功能,是CPU分配与调度的基本单位。

并发和并行

并行是基于多核cpu进行,多个线程同时调度任务
并发是单个cpu处理

在这里插入图片描述

同步和异步

同步,前面的事情不做完后面的事情干不了
异步,异步是指在程序执行过程中,不需要等待某个任务的完成,而是在发出该任务后继续执行后面的代码,等任务完成后再回来处理该任务的结果

在这里插入图片描述

临界区

临界区用来表示一种公共资源与共享数据,可以被多个线程使用。
同一时间只能有一个线程访问临界区(阻塞状态),其他资源必须等待。

举例:数据库在执行某条数据更新操作,数据库为了保证数据访问有效,所以会在更新前对这条数据开启一个锁,这个锁只能被其中某个用户访问,这个用户对这条数据更新的时候,其他用户对这条数据更新都得等着,直到更新的的用户更新完把锁释放掉,其他等待的用户才能够更新。

线程活跃度(死锁,饥饿,活锁)

死锁,大家对公共资源彼此争执,并且都不愿意释放
饥饿,线程一直获取不到资源
活锁,线程调度不够智能,资源没有被占用,线程还一直处于等待状态
三种情况都会形成系统阻塞
在这里插入图片描述

线程安全

在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
线程安全和不安全,取决于在多线程的情况下,在对比单线程所造成的结果是否相同,如果是相同则是线程安全的,结果不一致则可以定义为线程不安全

3-Java内存模型(JMM)

Java内存模型全称JMM(Java Memory Model)

内存主要有堆和栈组成
在这里插入图片描述
下面来一段demo代码详细讲解堆栈的作用,以及流程

public class Employee {
    private String name;
    private Integer age;
    private Department department;
    public Employee(){

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public void sayJoke(String content){
        System.out.println(this.getName() + "说" + content);
    }

    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.setName("老齐");
        emp.setAge(13);
        Department department = new Department();
        department.setDname("小卖部");
        emp.setDepartment(department);
        emp.sayJoke("一言不合就开车");
    }
}

class Department{
    private String dname;

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }
}

在这里插入图片描述
1.运行方法前进行类的加载,加载到employee和department这两个类,把这两个类的结构,成员变量和成员方法加载到方法区(方法区静态还会存储静态的方法和变量
2.启动main方法,创建一个线程,会开辟一个栈空间,压入mian方法的栈(栈帧:每执行一个方法就会有一个对应的栈帧
3.emp = new Empolyee(); 创建对象,存储在堆空间,将栈地址指向开辟的堆空间(也可以叫做引用
4.emp.setName(“老齐”);先在方法区创建字符串"老齐",然后将字符串引用到name属性上,setName()方法的栈帧执行完,就会出栈
5.emp.setAge(13);数字按值引用,直接赋值,不需要在静态区开辟新空间
6.dept = new Department();同上3
7.dept.setDname(“小卖铺”);同上4
8.emp.setDepartment(dept);Employee对象的department属性直接指向Department对象的地址(也可以叫做地址被属性所引用)
9.emp.sayJoke(“一言不合就开车”)
10.方法执行完成,站内栈帧全部弹出,线程销毁

4-创建多线程-继承Thread

/**
 * 使用集成Thread的方式实现多线程
 */
public class Match1 {
    public static void main(String[] args) {
        Runner liuxiang = new Runner();//创建一个新的线程
        liuxiang.setName("刘翔");//设置线程名称
        Runner laoqi = new Runner();
        laoqi.setName("老齐");
        Runner op = new Runner();
        op.setName("路飞");

        liuxiang.start();//启动线程
        laoqi.start();
        op.start();

    }
}
class Runner extends Thread{
    @Override
    public void run() {
        Integer speed = new Random().nextInt(100);
        for(int i = 1 ; i <= 100 ; i++){
            try {
                Thread.sleep(1000); //当前线程休眠1秒
            }catch (Exception e){
                e.printStackTrace();
            }
            //this.getName()打印当前线程的名字
            System.out.println(this.getName() + "已前进" + (i * speed) + "米(" + speed + "米/秒)");
        }
    }
}

5-创建多线程-实现Runnable接口

public class Match2 {
    public static void main(String[] args) {
        Runner2 liuxiang = new Runner2();
        Thread thread1 = new Thread(liuxiang);
        thread1.setName("刘翔");

        Thread laoqi = new Thread(new Runner2());
        laoqi.setName("老齐");

        Thread op = new Thread(new Runner2());
        op.setName("路飞");

        thread1.start();
        laoqi.start();
        op.start();
    }
}

class Runner2 implements Runnable {
    @Override
    public void run() {
        Integer speed = new Random().nextInt(100);
        for(int i = 1 ; i <= 100 ; i++){
            try {
                Thread.sleep(1000); //当前线程休眠1秒
            }catch (Exception e){
                e.printStackTrace();
            }
            //Thread.currentThread()用于获取当前执行的线程对象
            //在Runnable中是无法使用this获取到当前线程对象的
            System.out.println(Thread.currentThread().getName() + "已前进" + (i * speed) + "米(" + speed + "米/秒)");
        }
    }
}

6-创建多线程-实现Callable接口

并发工具包-Concurrent

JDK1.5以后为我们专门提供了一个并发工具包java.util.concurrent。
java.util.concurrent 包含许多线程安全、测试良好、高性能的并发构建块。创建 concurrent 的目的就是要实现 Collection 框架对数据结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性

public class Match3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建一个线程池。里面天生有3个“空”线程。Executors是调度器,对线程池进行管理
        ExecutorService executorService =  Executors.newFixedThreadPool(3);
        Runner3 liuxiang = new Runner3();//实例化Callable对象
        liuxiang.setName("刘翔");
        Runner3 laoqi = new Runner3();
        laoqi.setName("老齐");
        Runner3 op = new Runner3();
        op.setName("路飞");

        //将这个对象扔到线程池中,线程池自动分配一个线程来运行liuxiang这个对象的call方法
        //Future用于接受线程内部call方法的返回值
        Future<Integer> result1 =  executorService.submit(liuxiang);
        Future<Integer> result2 =  executorService.submit(laoqi);
        Future<Integer> result3 =  executorService.submit(op);

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();//关闭线程池释放所有资源
        System.out.println("刘翔累计跑了" + result1.get() + "米" );
        System.out.println("老齐累计跑了" + result2.get() + "米" );
        System.out.println("路飞累计跑了" + result3.get() + "米" );
    }
}
class Runner3 implements Callable<Integer>{
    private String name ;
    public void setName(String name){
        this.name = name;
    }
    //实现Callable接口可以允许我们的线程返回值或抛出异常
    @Override
    public Integer call() throws Exception {
        Integer speed = new Random().nextInt(100);
        Integer distince = 0; //总共奔跑的距离
        for(int i = 1 ; i <= 100 ; i++){
            Thread.sleep(10);
            distince = i * speed;
            System.out.println(this.name + "已前进" + distince + "米(" + speed + "米/秒)");
        }
        return distince;
    }
}

三种线程创建方式的对比
在这里插入图片描述

7-Synchronized线程同步机制

同步代码样例

public class SyncSample {
    public static void main(String[] args) {
        Couplet c = new Couplet();
        for(int i = 0 ; i < 10000 ; i++){
            new Thread(){
                public void run(){
                    int r = new Random().nextInt(2);
                    if(r % 2 == 0){
                        Couplet.first();
                    }else{
                        Couplet.second();
                    }
                }
            }.start();
        }
    }
}
class Couplet{
    Object lock = new Object(); //锁对象
    public synchronized static void first(){
//        synchronized (lock) { //同步代码块,在同一时间只允许有一个线程执行访问这个方法
            System.out.printf("琴");
            System.out.printf("瑟");
            System.out.printf("琵");
            System.out.printf("琶");
            System.out.println();
//        }
    }
    public static void second(){
        synchronized (Couplet.class) { //因为两个同步代码指向了同一把锁lock,所以在同一个时间内只允许有一个代码块执行,其他等待
            System.out.printf("魑");
            System.out.printf("魅");
            System.out.printf("魍");
            System.out.printf("魉");
            System.out.println();
        }

    }
}

现实中同步的列子
在这里插入图片描述

代码中同步的列子
synchronized(同步锁)关键字的作用就是利用一个特定的对象设置一个锁lock(绣球),在多线程(游客)并发访问的时候,同时只允许一个线程(游客)可以获得这个锁,执行特定的代码(迎娶新娘)。执行后释放锁,继续由其他线程争抢。

Synchronize的使用场景

Synchronize可以使用在以下三种场景,对应不同锁对象:
synchronized代码块 - 任意对象即可
synchronized方法 - this当前对象
synchronized静态方法 - 该类的字节码对象

8-面试题-线程的五种状态

新建,当线程被new出来时,处于新建状态
就绪,等待状态,等待cpu分配资源(时间片),当分配到资源,就会自动执行run()
运行,程序运行状态
阻塞,当锁被解除或者休眠时间到了,线程就处于就绪状态,等待cpu分配资源
》I/O(下载一个很大数据的文件需要很长时间),
》sleep()(线程休眠),
》lock锁(比如synchronized关键字,其中就有等待的机制),
》yield()(主动把当前cpu的时间让出去,让给优先级更高的线程来去执行),
死亡,当程序所有都运行完,当前线程就会被jvm自动销毁,进入死亡状态,被垃圾回收
在这里插入图片描述

9-死锁的产生

举例两个线程运用不同的顺序操作AB文件:

public class DeadLock {
    private static String fileA = "A文件";
    private static String fileB = "B文件";

    public static void main(String[] args) {
        new Thread(){ //线程1
            public void run(){
                while(true) {
                    synchronized (fileA) {//打开文件A,线程独占
                        System.out.println(this.getName() + ":文件A写入");
                        synchronized (fileB) {
                            System.out.println(this.getName() + ":文件B写入");
                        }
                        System.out.println(this.getName() + ":所有文件保存");
                    }
                }
            }
        }.start();

        new Thread(){ //线程2
            public void run(){
                while(true) {
                    synchronized (fileB) {//打开文件A,线程独占
                        System.out.println(this.getName() + ":文件B写入");
                        synchronized (fileA) {
                            System.out.println(this.getName() + ":文件A写入");
                        }
                        System.out.println(this.getName() + ":所有文件保存");
                    }
                }
            }
        }.start();
    }
}

10-重新认识线程安全ThreadSafe

线程安全,在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

举例多线程情况下的计数器

public class DownloadsSample {
    public static int users = 100;//同时模拟的并发访问用户数量
    public static int downTotal = 50000; //用户下载的真实总数
    public static int count = 0 ;//计数器

    public static void main(String[] args) {
        //调度器,JDK1.5后提供的concurrent包对于并发的支持
        ExecutorService executorService  = Executors.newCachedThreadPool();
        //信号量,用于模拟并发的人数
        final Semaphore semaphore = new Semaphore(users);
        for(int i = 0 ; i < downTotal ; i++){
            executorService.execute(()->{
                //通过多线程模拟N个用户并发访问并下载
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();//关闭调度服务
        System.out.println("下载总数:" + count);
    }
    //线程不安全
    public static void add(){
        count++;
    }
    /*线程安全
    public synchronized static void add(){
        count++;
    }*/

}

在这里插入图片描述
在这里插入图片描述

11-JAVA并发包与线程池

什么是线程池

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

new Thread的弊端

new Thread()新建对象,性能差
线程缺乏统一管理,可能无限制的新建线程,相互竞争,严重时会占用过多系统资源导致死机或OOM

ThreadPool - 线程池

》 重用存在的线程,减少对象对象、消亡的开销
》 线程总数可控,提高资源的利用率
》 避免过多资源竞争,避免阻塞
》提供额外功能,定时执行、定期执行、监控等。

线程池的种类

在java.util.concurrent中,提供了工具类Executors(调度器)对象来创建线程池,可创建的线程池有四种:

  1. CachedThreadPool - 可缓存线程池
  2. FixedThreadPool - 定长线程池
  3. SingleThreadExecutor - 单线程池
  4. ScheduledThreadPool - 调度线程池

CachedThreadPool

public class ThreadPoolSample1 {
    public static void main(String[] args) {
        //调度器对象
        //ExecutorService用于管理线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个可缓存线程池
        //可缓存线程池的特点是,无限大,如果线程池中没有可用的线程则创建,有空闲线程则利用起来
        for(int i = 1 ; i <= 1000 ; i++) {
            final  int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + index);
                }
            });
        }
        try {
            Thread.sleep(1000); //跟线程足够的运行时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //shutdown() 代表关闭线程池(等待所有线程完成)
        //shutdownNow() 代表立即终止线程池的运行,不等待线程,不推荐使用
        threadPool.shutdown();
    }
}

FixedThreadPool

public class ThreadPoolSample2 {
    public static void main(String[] args) {
        //调度器对象
        //ExecutorService用于管理线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);//创建一个可创建一个定长线程池
        //定长线程池的特点是固定线程总数,空间线程用于执行任务,如果线程都在使用后续任务则处于等待状态,在线程池中的线程
        //如果任务处于等待的状态,备选的等待算法默认为FIFO(先进先出) LIFO(后进先出)
        //执行任务后再执行后续的任务。
        for(int i = 1 ; i <= 1000 ; i++) {
            final  int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + index);
                }
            });
        }
        try {
            Thread.sleep(1000); //跟线程足够的运行时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //shutdown() 代表关闭线程池(等待所有线程完成)
        //shutdownNow() 代表立即终止线程池的运行,不等待线程,不推荐使用
        threadPool.shutdown();
    }
}

SingleThreadExecutor

public class ThreadPoolSample3 {
    public static void main(String[] args) {
        //调度器对象
        //ExecutorService用于管理线程池
        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单线程线程池
        for(int i = 1 ; i <= 1000 ; i++) {
            final  int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + index);
                }
            });
        }
        try {
            Thread.sleep(1000); //跟线程足够的运行时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //shutdown() 代表关闭线程池(等待所有线程完成)
        //shutdownNow() 代表立即终止线程池的运行,不等待线程,不推荐使用
        threadPool.shutdown();
    }
}

ScheduledThreadPool

public class ThreadPoolSample4 {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool =  Executors.newScheduledThreadPool(5);//可调度线程池
        /*//延迟三秒执行一次Run方法
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟3秒执行");
            }
        } , 3 , TimeUnit.SECONDS);*/
        //Timer , 项目实际开发中scheduledThreadPool与Timer都不会用到,应为有成熟的调度框架Quartz,或者Spring自带调度,
        //程序的调度框架支持一种表达式叫做Cron表达式,有兴趣的童鞋可以了解一下。
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(new Date() + "延迟1秒执行,每三秒执行一次");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
}

12-JUC之CountDownLatch倒计时锁

CountDownLatch倒计时锁特别适合”总-分任务”,例如多线程计算后的数据汇总
CountDownLatch类位于java.util.concurrent(J.U.C)包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他3个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

举例:我们需要计算三个子线程完成计算的结果汇总,设置countDownLatch等于3,子线程执行完一次countdownLatch就会减一,当三个线程都执行完成,countDownLatch等于0时,就会执行汇总任务
在这里插入图片描述
案例:多线程的情况下计算10000

public class CountDownSample {
    private static int count = 0;
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(100);
        CountDownLatch cdl = new CountDownLatch(10000); //CDL总数和操作数保持一致
        for(int i = 1 ; i <= 10000 ; i++) {
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    synchronized (CountDownSample.class) {
                        try {
                            count = count + index;
                            //计数器减一
                        }catch(Exception e){
                            e.printStackTrace();
                        }finally {
                            cdl.countDown();
                        }
                    }
                }
            });
        }
/*        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        try {
            cdl.await(); //堵塞当前线程,直到cdl=0的时候再继续往下走
            //为了避免程序一致挂起,我们可以设置一个timeout时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
        threadPool.shutdown();
    }
}

13-JUC之Semaphore信号量

Semaphore信号量经常用于限制获取某种资源的线程数量,例如限制游戏服务器在线人数,降低服务器运行压力,避免系统扛不住导致宕机
在这里插入图片描述
案例:假设服务器只能容纳5人游戏,20人应该怎么处理(当5个信号量被占用时,其他的线程必须等到5人中有人释放信号量,才能执行)

public class SemaphoreSample1 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();
        Semaphore semaphore = new Semaphore(5);//定义5个信号量,也就是说服务器只允许5个人在里面玩
        for(int i = 1 ; i <= 20 ; i++) {
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();//获取一个信号量,“占用一个跑到”
                        play();
                        semaphore.release();//执行完成后释放这个信号量,“从跑道出去”
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });
        }
        threadPool.shutdown();
    }

    public static void play(){

        try {
            System.out.println(new Date() + " " + Thread.currentThread().getName() + ":获得紫禁之巅服务器进入资格");
            Thread.sleep(2000);
            System.out.println(new Date() + " " + Thread.currentThread().getName() + ":退出服务器");
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

14-JUC之CyclicBarrier循环屏障

可以让所有的线程同时执行,线程执行到barrier时会被拦住,知道所有的线程都准备就绪,然后同时执行
在这里插入图片描述

public class CyclicBarrierSample {
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for(int i = 1 ; i<=20 ; i++) {
            final int index = i;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    go();
                }
            });

        }
        executorService.shutdown();
    }

    private static void go(){
        System.out.println(Thread.currentThread().getName() + ":准备就绪" );
        try {
            cyclicBarrier.await();//设置屏障点,当累计5个线程都准备好后,才运行后面的代码
            System.out.println(Thread.currentThread().getName() + ":开始运行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }

    }
}
CyclicBarrier的应用场景

cpu性能测试
双11秒杀活动
抢票软件
在这里插入图片描述

15-JUC之ReentrantLock重入锁

什么是重入锁

重入锁是指任意线程在获取到锁之后,再次获取该锁而不会被该锁所阻塞
ReentrantLock设计的目标是用来替代synchronized关键字

ReentrantLock与synchronized的区别

在这里插入图片描述

public class ReentrantLockSample {
    public static int users = 100;//同时模拟的并发访问用户数量
    public static int downTotal = 50000; //用户下载的真实总数
    public static int count = 0 ;//计数器
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        //调度器,JDK1.5后提供的concurrent包对于并发的支持
        ExecutorService executorService  = Executors.newCachedThreadPool();
        //信号量,用于模拟并发的人数
        final Semaphore semaphore = new Semaphore(users);
        for(int i = 0 ; i < downTotal ; i++){
            executorService.execute(()->{
                //通过多线程模拟N个用户并发访问并下载
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();//关闭调度服务
        System.out.println("下载总数:" + count);
    }
    //线程不安全
    public static void add(){
        lock.lock();//上锁
        try {
            count++;
        }finally {
            lock.unlock(); //解锁,一定要放在finally里面否则会出现死锁
        }


    }


}

15.1-JUC之Condition线程等待与唤醒

condition条件唤醒

我们在并行程序中,避免不了某些线程要预先规定好的顺序执行,例如:先新增再修改,先买后卖,>先进后出…,对于这类场景,使用JUC的Condition对象再合适不过了。
JUC中提供了Condition对象,用于让指定线程等待与唤醒,按预期顺序执行。它必须和ReentrantLock重入锁配合使用。
Condition用于替代wait()/notify()方法

notify只能随机唤醒等待的线程,而Condition可以唤醒指定的线程,这有利于更好
的控制并发程序。

Condition核心方法

await() - 阻塞当前线程,直到singal唤醒
signal() - 唤醒被await的线程,从中断处继续执行
signalAll() - 唤醒所有被await()阻塞的线程

16-JUC之Callable_Future

Callable和Runnable一样代表着任务,区别在于Callable有返回值并且可以抛出异常。
Future 是一个接口。它用于表示异步计算的结果。提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。

案例:打印出10000以内的质数

public class FutureSample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for(int i = 2 ; i <= 10000 ; i++){
            Computor c = new Computor();
            c.setNum(i);
            //Future是对用于计算的线程进行监听,因为计算是在其他线程中执行的,所以这个返回结果的过程是异步的
            Future<Boolean> result = executorService.submit(c);//将c对象提交给线程池,如有空闲线程立即执行里面的call方法
            try {
                Boolean r = result.get(); //用于获取返回值,如果线程内部的call没有执行完成,则进入等待状态,直到计算完成
                if(r == true){
                    System.out.println(c.getNum());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        executorService.shutdown();
    }
}
class Computor implements Callable<Boolean>{
    private Integer num;

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    @Override
    public Boolean call() throws Exception {
        boolean isprime = true;
        for(int i = 2 ; i < num ; i++) {
            if (num % i == 0) {
                isprime = false;
                break;
            }
        }

        return isprime;
    }
}

17-JUC之同步容器

请写出线程安全的类

Vector是线程安全的,ArrayList、LinkedList是线程不安全的
Properties是线程安全的,HashSet、TreeSet是不安全的
StringBuffer是线程安全的,StringBuilder是线程不安全的
HashTable是线程安全的,HashMap是线程不安全的

线程安全-并发容器

ArrayList -> CopyOnWriteArrayList - 写复制列表
HashSet -> CopyOnWriteArraySet - 写复制集合
HashMap -> ConcurrentHashMap - 分段锁映射

CopyOnWriteArrayList案例

public class CopyOnWriteArrayListSample {
    public static void main(String[] args) {
        //写复制列表
        List<Integer> list = new CopyOnWriteArrayList<>();
        for(int i = 0 ; i < 1000 ; i++){
            list.add(i);
        }
        Iterator<Integer> itr = list.iterator();
        while (itr.hasNext()) {
            Integer i = itr.next();
            list.remove(i);
        }
        System.out.println(list);
    }
}

List和Set底层数据结构都是数组,只不过一个是有序一个是无序,在多线程的情况下,进行add()和remove()时,会报ConcurrentModificationException并发修改异常,推荐使用CopyOnWriteArrayList和CopyOnWriteArraySet,原理就是在新增一条数据的时候,新创建一个副本,对副本进行操作,然后讲原来的指针指向新的副本地址,源码
在这里插入图片描述

public boolean add(E e) {
		// 重入锁
        final ReentrantLock lock = this.lock;
        // 加锁
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 复制原来的list,并且长度加一
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            // 引用新的副本
            setArray(newElements);
            return true;
        } finally {
        	// 释放锁
            lock.unlock();
        }
    }

hashMap在多线程的情况下也是线程不安全的,线程安全的有hashTable,但是为什么实际不适用hashTable,而是用ConcurrentHashMap呢

  • hashTable底层时使用synchronized关键字实现,是多个线程操作同一个map,效率低
  • ConcurrentHashMap使用的是分段锁,将map以2*n次方分为多端长度,每个分段之间可以同时进行操作,但是单个分段还是单个操作,对于hashTable效率高
    在这里插入图片描述

18-JUC之Atomic与CAS算法(乐观锁)

回顾原子性

原子性:是指一个操作或多个操作要么全部执行,且执行的过程不会被任何因素打断,要么就都不执行。

Atomic包

Atomic包是java.util.concurrent下的另一个专门为线程安全设计的Java包,包含多个原子操作类。

Atomic常用类
– AtomicInteger
– AtomicIntegerArray
– AtomicBoolean
– AtomicLong
– AtomicLongArray

将Atomic之前,先说下乐观锁和悲观锁的概念,其实意如其名
在这里插入图片描述

Atomic的应用场景

虽然基于CAS的线程安全机制很好很高效,但要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小型,如计数器这样的需求用起来才有效,否则也不会有锁的存在了。

19-课程总结

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/949927.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Cadence网表导出常见错误

前言 好不容易绘制出来原理图&#xff0c;结果导出报了很多条错误&#xff0c;由于哥们还是小白&#xff0c;所以很多事情还不懂&#xff0c;有错误的地方希望大佬们能够指出&#xff0c;主要还是以我遇到的为主。 生成网表时候的常见错误 36002-封装名缺失 36003-多part器…

pdf怎么调整大小kb?一分钟学会pdf压缩

PDF是一种常见的文件格式&#xff0c;有时候我们需要将PDF文件的大小进行压缩&#xff0c;以便于传输或存储&#xff0c;那么怎么调整PDF文件的大小呢&#xff1f;接下来就给大家分享几个简单又实用的方法&#xff0c;帮助我们轻松解决PDF文件过大的问题。 方法一&#xff1a;嗨…

【高等数学重点题型篇】——一元函数微分学的应用

本文仅用于个人学习记录&#xff0c;使用的教材为汤家凤老师的《高等数学辅导讲义》。本文无任何盈利或者赚取个人声望的目的&#xff0c;如有侵权&#xff0c;请联系删除&#xff01; 文章目录 一、证明f ( n ) \ ^{(n)} (n)(ξ) 0二、待证结论中只有一个中值ξ&#xff0c;不…

【编译原理】课程一:编译原理入门

目录 1.为什么要学习编译原理 2.什么是编译原理 3.编译与计算机程序设计语言的关系 3.1.程序设计语言的转换方式 3.2.编译的转换过程 3.3.编译器在语言处理系统中的位置 3.4.编译系统的结构 3.4.1.词法分析(扫描) 3.4.2.语法分析(parsing) 3.4.1.1.语法分析的定义 3…

四轴飞行器的电池研究(MatlabSimulink仿真)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Python教程(11)——Python中的字典dict的用法介绍

dict的用法介绍 创建字典访问字典修改字典删除字典字典的相关函数 列表虽然好&#xff0c;但是如果需要快速的数据查找&#xff0c;就必须进行需要遍历&#xff0c;也就是最坏情况需要遍历完一遍才能找到需要的那个数据&#xff0c;时间复杂度是O(n)&#xff0c;显然这个速度是…

Canvas实现3D效果

3D 球 效果图 代码 var canvas document.getElementById("cas"),ctx canvas.getContext("2d"),vpx canvas.width / 2,vpy canvas.height / 2,Radius 150,balls [],angleX Math.PI / 1000,angleY Math.PI / 1000,factor 0.0001 //旋转因子var An…

解决方案 | 法大大电子签为保险行业加个“双保险”

近年来&#xff0c;人们自我保障意识的不断增强&#xff0c;带动了保险行业的蓬勃发展&#xff1b;数字化进程的加快&#xff0c;也让保险签署更加便捷高效。但与此同时&#xff0c;对于保险企业的安全合规化要求也在不断提升&#xff0c;电子签作为企业数字化转型的重要抓手&a…

vue3集成bpmn.js

1 安装依赖 npm install bpmn-js npm install bpmn-js-properties-panel npm install camunda-bpmn-moddle 注意依赖会有冲突&#xff0c;最好按照下列版本来安装&#xff1a; "bpmn-js": "^7.3.1", "bpmn-js-properties-panel": "^0.37.…

量子计算机使模拟化学反应减慢了1000亿倍!

悉尼大学(University of Sydney)的科学家们首次利用量子计算机设计并直接观测了化学反应中的一个关键过程&#xff0c;将其速度减慢了1000亿倍。 “Direct observation of geometric-phase interference in dynamics around a conical intersection” 主要作者Vanessa Olaya Ag…

免费可商用的高清视频素材库分享~

找视频素材绝对不能错过这个6个网站&#xff0c;免费可商用&#xff0c;视频剪辑、自媒体必备&#xff0c;赶紧收藏~ 1、菜鸟图库 https://www.sucai999.com/video.html?vNTYwNDUx 菜鸟图库不仅是一个设计网站&#xff0c;它还有非常丰富的视频和音频素材&#xff0c;视频素材…

[JDK8下的HashMap类应用及源码分析] 数据结构、哈希碰撞、链表变红黑树

系列文章目录 [Java基础] StringBuffer 和 StringBuilder 类应用及源码分析 [Java基础] 数组应用及源码分析 [Java基础] String&#xff0c;分析内存地址&#xff0c;源码 [JDK8环境下的HashMap类应用及源码分析] 第一篇 空构造函数初始化 [JDK8环境下的HashMap类应用及源码分…

Java 复习笔记 - 方法篇

文章目录 一&#xff0c;方法的定义二&#xff0c;最简单的方法定义和调用三&#xff0c;带参数的方法定义和调用四&#xff0c;带返回值方法的定义和调用五&#xff0c;小结六&#xff0c;方法的重载七&#xff0c;方法简单练习1&#xff0c;数组遍历2&#xff0c;数组最大值3…

【深入解读Redis系列】Redis系列(五):切片集群详解

首发博客地址 https://blog.zysicyj.top/ 系列文章地址[1] 如果 Redis 内存很大怎么办&#xff1f; 假设一台 32G 内存的服务器部署了一个 Redis&#xff0c;内存占用了 25G&#xff0c;会发生什么&#xff1f; 此时最明显的表现是 Redis 的响应变慢&#xff0c;甚至非常慢。 这…

Spring框架中如何处理事务管理

文章目录 **1. 声明式事务管理&#xff1a;****定义事务管理器&#xff1a;****配置事务通知&#xff1a;****将事务通知应用到方法&#xff1a;** **2. 编程式事务管理&#xff1a;****通过编程方式启动事务&#xff1a;** **3. 配置事务属性&#xff1a;** &#x1f388;个人…

S32K324芯片学习笔记-实时控制系统-ADC

文章目录 Analog-to-Digital Converter (ADC)用于内部供应监控的ANAMUXBCTU接口硬件触发ADC多路模式通道功能框图特点功能描述时钟转换正常触发注入触发BCTU接口BCTU Trigger modeBCTU Control mode 配置ADC时钟分压器和采样时间设置预采样启用每个通道的预采样 模拟看门狗功能…

阿里云轻量应用服务器Linux-Centos7下MySQL8.0.19的使用

初始环境&#xff1a;阿里云轻量应用服务器已经安装MySQL8.0.19 具体目标&#xff1a;使用MySQL 8.0.19 目录 第一步&#xff1a;初始化第二步&#xff1a;创建一些表第三步&#xff1a;添加一些数据第四步&#xff1a;详情一览第五步&#xff1a;检索数据第六步&#xff1a;排…

前端:js实现提示框(自动消失)

效果&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content&q…

正交实验如何进行方差分析?

一、案例介绍 欲研究温度&#xff08;℃&#xff09;、时间&#xff08;min&#xff09;、催化剂含量&#xff08;%&#xff09;对某物质转化率的影响&#xff0c;当前使用正交试验设计进行试验&#xff0c;试探究3个因素对转化率的影响是否显著&#xff0c;并找到能使转化率达…

Source Insight查看源码

新建一个工程 Project -> Synchnorize Files 有可能找不到文件 Options -> File Type Option 查看或者设置快捷键