多线程的安全与应用

目录

 1.1进程与线程之间的区别与联系

 2-1 通过继承Thread类创建线程

2-2 选择练习 

 2-5 run方法和start方法的区别

​2-6 实现runnable方法接口创建线程

 2-7 选择练习

2-8 自由编程

 2-9 实现Callable接口创建线程

 2-10 线程的生命周期

 2-11 选择练习

3-1 初试线程同步并使用Synchronized实现线程同步

3-2 选择练习 

 3-3 Synchronized在不同场景下锁对象的区别

 3-4 选择练习

3-5 实现线程安全解决超卖现象

4-1 初识线程池及其基本应用

 4-2 线程池的四种类型

4-3 课程总结 

说明:本章的目的是弄明白在java中多线程程序开发有什么细节以及处理过程中有什么需要注意的地方。

 本章主要分为五个部分进行讲解

多线程的安全与应用_第1张图片

 1.1进程与线程之间的区别与联系

多线程的安全与应用_第2张图片

说明:程序就是一个静态的概念

当程序启动以后就会以进程的方式在操作系统的内存中进行驻留

多线程的安全与应用_第3张图片

说明:进程就是程序运行后在内存中的一个实例,进程的一个特性就是彼此之间相互隔离。

根据程序设计的需要有的程序是允许启动多个进程的。

每一个启动的进程在内存中都是独立存储和管理的。

两个记事本的进程彼此互不干扰,有的只允许启动一份,防止出现混乱。

 多线程的安全与应用_第4张图片

说明:进程的资源往往指的是内存文件这些东西,例如当迅雷运行时系统会为其分配128m的内存进行使用,其他的进程就不能对这128m内存进行读取或者写入的操作。这就是资源的隔离性。

 多线程的安全与应用_第5张图片

说明: 线程是进程内部一个一个要执行的任务,例如迅雷下载就是底层创建了3个线程,每一个线程都有一个任务去下载独自的文件,而且这些线程是可以并行执行的,为什么可以并行执行?我们稍后再说

多线程的安全与应用_第6张图片

 说明:如图所示

多线程的安全与应用_第7张图片

说明:因为cpu全局只有一个不可能对所有的线程进行同时执行,为了让我们看起来在同时执行,在操作系统层面上会有一个时间片的概念,时间片也就是cpu的一段执行时间,时间片送给那个线程,那个县城就有当前计算和执行的权力,其他的线程则处于等待状态,直到时间片被分配给其他线程才会执行。

因为时间片是以纳秒级别来分配,所以在感官上我们会觉得所有线程都是在同时运行的,这种情况我们叫做并发执行。

多线程的安全与应用_第8张图片

说明: 随之技术的发展,现在主流的民用cpu都是i5,i7四核八核十六核,这种多核心的情况下就是真正意义上的并行执行了。

不同的线程之间可以拥有不同的cpu分配的时间片,在物理层面上做到了真正的并行执行。

多线程的安全与应用_第9张图片

在java中进程线程是什么关系呢?

像这种只有一个main方法的代码,我们会将它称为java的“单线程”程序

 当程序启动以后会产生一个如图所示的进程,在这个进程中会包含java中所产生的的线程

多线程的安全与应用_第10张图片

 每一个java程序启动后会至少包含两个线程,除了main在java中我们支持垃圾自动回收,可以自动对垃圾对象进行回收,这个就交给了垃圾收集线程执行,对垃圾进行整理标记和收集。

垃圾收集程序因为是每一个java程序都自带的所以我们并不把他算在其中,通常我们提到的java多线程程序就是指通过咱们自己程序代码所创建出来的线程进行的计算和处理,这被称为多线程,这样的程序被称为多线程程序,只有main方法的主线程程序称为单线程程序。

 2-1 通过继承Thread类创建线程

 说明:本节开始进入实战的环节来了解在java语言环境下,如何创建多线程程序,以及如何实现多个任务同时处理

 说明:分别针对以上三种方式来进行讲解

多线程的安全与应用_第11张图片

 说明:为了方便理解我们模拟一个程序,创建一个主线程和额外的三个线程,让三位参赛者同时向前奔跑,最后得到每位参赛者在10秒内跑了多少距离,要怎么做呢?

 创建演示类

多线程的安全与应用_第12张图片

说明: 通过此案例来演示到底为什么需要多线程程序?以及如果我们直接写main方法他是怎么实现的?单线程和多线程的区别?

package com.imooc.thread;

import java.util.Random;
//继承Thread实现多线程程序
public class ThreadSample1 {
    class Runner extends Thread{
        @Override
        public void run() {
            Integer speed = new Random().nextInt(10);
            for(int i = 1 ; i <= 10 ; i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第" + i + "秒:" + this.getName() + "已跑到" + (i * speed) + "米(" + speed + "米/秒)");
            }
        }
    }

    public void start(){
        Runner threadA = new Runner();
        threadA.setName("参赛者A");
        Runner threadB = new Runner();
        threadB.setName("参赛者B");
        Runner threadC = new Runner();
        threadC.setName("参赛者C");
        Runner threadD = new Runner();
        threadD.setName("参赛者D");

        threadA.start();
        threadB.start();
        threadC.start();
        threadD.start();
    }

    public static void main(String[] args) {
        //System.out.println("参赛者A 10秒跑了100米");
        //System.out.println("参赛者B 10秒跑了60米");
        //System.out.println("参赛者C 10秒跑了80米");
        new ThreadSample1().start();
    }
}

本节代码实例

 多线程的安全与应用_第13张图片

 说明:我们的需求是参赛者同时跑,在main方法中显然是无法做到的,main方法作为主方法,前面的方法执行不完后面的语句就一直处于等待的状态。尽管是主线程带无法做到并行执行的效果。

 thread类来实行多线程程序

 class Runner extends Thread{
        @Override
        //新增内部类Runner重写父类方法run()
        public void run() {
            //书写模拟奔跑的程序,随机生成每秒奔跑的速度
            Integer speed = new Random().nextInt(10);
            //模拟10秒钟奔跑的距离
            for(int i = 1 ; i <= 10 ; i++){
                //getName()就是线程的名字
                System.out.println("第" + i + "秒:" + this.getName() + "已跑到" + (i * speed) + "米(" + speed + "米/秒)");
            }
        }
    }

    //模拟开始奔跑的方法
    public void start(){
        
        Runner threadA = new Runner();
        threadA.setName("参赛者A");

        //新线程的创建和运行
        threadA.start();

    }
    public static void main(String[] args) {
        new ThreadSample1().start();
    }

运行效果:

 多线程的安全与应用_第14张图片

模拟10秒钟跑的距离

 添加方法让线程沉睡1秒钟

多线程的安全与应用_第15张图片

 因为只有一个自定义线程,所以无法体现出同时跑,设置其他参赛者bcd


    public void start(){
        Runner threadA = new Runner();
        threadA.setName("参赛者A");
        Runner threadB = new Runner();
        threadB.setName("参赛者B");
        Runner threadC = new Runner();
        threadC.setName("参赛者C");
        Runner threadD = new Runner();
        threadD.setName("参赛者D");

        threadA.start();
        threadB.start();
        threadC.start();
        threadD.start();
    }

 可以看到每一秒abc都是在同时输出信息

多线程的安全与应用_第16张图片

 每增加一个,就增加一个同时跑的人

 每一秒的执行顺序并不相同,这是因为4个线程都会被操作系统分配时间片来进行执行,谁被先分配了时间片就由先执行的权力,cpu的调度各不相同。

2-2 选择练习 

 多线程的安全与应用_第17张图片

 ​​ 

 

 多线程的安全与应用_第18张图片

 

 2-5 run方法和start方法的区别

 多线程的安全与应用_第19张图片

 多线程的安全与应用_第20张图片

 分别调用start()方法和run()方法进行测试

多线程的安全与应用_第21张图片 

多线程的安全与应用_第22张图片2-6 实现runnable方法接口创建线程

 

 java是只支持单继承的,作为java更推崇的方式是实现接口的方式来进行处理

 新建java类

多线程的安全与应用_第23张图片

package com.imooc.thread;

import java.util.Random;
//实现Runnable接口实现多线程程序
public class ThreadSample2 {
    class Runner implements Runnable{
        @Override
        public void run() {
            Integer speed = new Random().nextInt(10);
            for(int i = 1 ; i <= 10 ; i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第" + i + "秒:" + Thread.currentThread().getName() + "已跑到" + (i * speed) + "米(" + speed + "米/秒)");
            }
        }
    }

    public void start(){
        Runner runner = new Runner();
        Thread threadA = new Thread(runner);
        threadA.setName("参赛者A");
        Thread threadB = new Thread(new Runner());
        threadB.setName("参赛者B");
        Thread threadC = new Thread(new Runner());
        threadC.setName("参赛者C");

        threadA.start();
        threadB.start();
        threadC.start();
    }

    public static void main(String[] args) {
        new ThreadSample2().start();
    }
}

 本节代码示例

    class Runner implements Runnable{
        @Override
        public void run() {
            Integer speed = new Random().nextInt(10);
            for(int i = 1 ; i <= 10 ; i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //Thread线程类.currentThread()静态方法,获取到当前执行的线程.getName()得到当前线程名称
                System.out.println("第" + i + "秒:" + Thread.currentThread().getName() + "已跑到" + (i * speed) + "米(" + speed + "米/秒)");
            }
        }
    }
public void start(){
        //实例化Runner对象
        Runner runner = new Runner();
        //实例化Thread类
        Thread threadA = new Thread(runner);
        //设置名称
        threadA.setName("参赛者A");
        Thread threadB = new Thread(new Runner());
        threadB.setName("参赛者B");
        Thread threadC = new Thread(new Runner());
        threadC.setName("参赛者C");
        //启动线程,然后去执行里面的run方法
        threadA.start();
        threadB.start();
        threadC.start();
    }
    //测试
    public static void main(String[] args) {
        new ThreadSample2().start();
    }
}

多线程的安全与应用_第24张图片

 传入实现类,当线程运行时就会去Run方法里规定的业务代码

执行效果

多线程的安全与应用_第25张图片 多线程的安全与应用_第26张图片

 谁获取时间片早就相对靠前

作为实现接口的方式在java中是友好的,所以在日常开发中十分常见。推荐此开发方式

 2-7 选择练习

 多线程的安全与应用_第27张图片

多线程的安全与应用_第28张图片

 

2-8 自由编程

多线程的安全与应用_第29张图片

package com.imooc.thread;

public class ThreadExercise {
    class Cat implements Runnable {
        @Override
        public void run() {
            for (int i = 1; i <= 3; i++) {
                System.out.println(Thread.currentThread().getName()+"A Cat");
            }
        }
    }
    class Dog implements Runnable {
        @Override
        public void run() {
            for (int i = 1; i <= 3; i++) {
                System.out.println(Thread.currentThread().getName()+"A Dog");
            }
        }
    }
    public void start(){
        for (int i = 1; i <= 3; i++) {
            System.out.println("main thread");
        }
        //实例化Runner对象
        Dog dog = new Dog();
        Cat cat = new Cat();
        //实例化Thread类
        Thread threadDog = new Thread(dog);
        Thread threadCat = new Thread(cat);
        threadDog.start();
        threadCat.start();
    }

    public static void main(String[] args) {
        ThreadExercise threadExercise = new ThreadExercise();
        threadExercise.start();
        
    }
}

 2-9 实现Callable接口创建线程

 创建类Threadsample3

package com.imooc.thread;

import java.util.Random;
import java.util.concurrent.*;
//实现Callable接口实现多线程程序
public class ThreadSample3 {
    class Runner implements Callable{
        public String name;
        @Override
        public Integer call() throws Exception {
            Integer speed = new Random().nextInt(10);
            Integer result = 0;
            for(int i = 1 ; i <= 10 ; i++){
                Thread.sleep(1000);
                result = i * speed;
                System.out.println("第" + i + "秒:" + this.name + "已跑到" + (i * speed) + "米(" + speed + "米/秒)");
            }
            return result;
        }
    }

    public void start() throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        Runner threadA = new Runner();
        threadA.name = "参赛者A";
        Runner threadB = new Runner();
        threadB.name = "参赛者B";
        Runner threadC = new Runner();
        threadC.name = "参赛者C";
        //利用Future对象获取每一个线程执行后的结果
        Future r1 = executorService.submit(threadA);
        Future r2 = executorService.submit(threadB);
        Future r3 = executorService.submit(threadC);
        //关闭线程池
        executorService.shutdown();
        System.out.println(threadA.name + "累计跑了" + r1.get() + "米");
        System.out.println(threadB.name + "累计跑了" + r2.get() + "米");
        System.out.println(threadC.name + "累计跑了" + r3.get() + "米");
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        new ThreadSample3().start();
    }
}
本节代码示例

//为什么要定义泛型?原因是利用Callable接口我们实现的应用程序,每一个线程在执行完以后是允许拥有返回值的
    class Runner implements Callable{
        //定义共有成员变量name
        public String name;
        //实现call方法,返回值是Integer
        @Override
        public Integer call() throws Exception {
            //获取随机速度
            Integer speed = new Random().nextInt(10);
            //result是总共要跑的距离,未来要作为执行结果进行返回,默认值为0
            Integer result = 0;
            for(int i = 1 ; i <= 10 ; i++){
                Thread.sleep(1000);
                result = i * speed;
                //name指向的是当前Runner对象的name,而不是线程的名字
                System.out.println("第" + i + "秒:" + this.name + "已跑到" + (i * speed) + "米(" + speed + "米/秒)");
            }
            //返回
            return result;
        }
    }

 如何创建多线程程序?

    public void start() throws ExecutionException, InterruptedException {
        //创建线程池,newFixedThreadPool采用定长线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        Runner threadA = new Runner();
        threadA.name = "参赛者A";
        Runner threadB = new Runner();
        threadB.name = "参赛者B";
        Runner threadC = new Runner();
        threadC.name = "参赛者C";
        //利用Future对象获取每一个线程执行后的结果 submit运行很快相当于同时进行
        //运行结果封装成Future对象
        Future r1 = executorService.submit(threadA);
        Future r2 = executorService.submit(threadB);
        Future r3 = executorService.submit(threadC);
        //关闭线程池,shutdown会等到所有执行结束后关闭线程池
        executorService.shutdown();
        //打印结果 get得到运行后的结果 也就是call方法return返回的数据
        System.out.println(threadA.name + "累计跑了" + r1.get() + "米");
        System.out.println(threadB.name + "累计跑了" + r2.get() + "米");
        System.out.println(threadC.name + "累计跑了" + r3.get() + "米");
    }

 调用查看结果

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        new ThreadSample3().start();
    }

 多线程的安全与应用_第30张图片

最后会出现 

 这种方法除了可以实现线程原本要做的事情以外,还允许出现返回值

 2-10 线程的生命周期

 什么是生命周期?一个事物由生到死的整个阶段。

 多线程的安全与应用_第31张图片

new 新建状态

runnable 可运行状态

blocked 阻塞状态

waiting 等待状态

timed_waiting 超时等待状态

terminated 结束状态也叫死亡状态

 针对这六种不同的线程状态是如何变化的呢?

多线程的安全与应用_第32张图片

什么是锁?

控制线程是否执行的一个标志,这里可以这么认为,锁本身是一个对象,在多线程情况下某一个线程得到了这个锁,当前线程就可以进入到可运行状态,同时cpu也会分配时间片让线程继续执行。

由于操作系统底层的任务调度还有其他的底层资源发生了变化,我们失去了锁,当前线程就会进入阻塞状态,cpu的时间片就不会分配给当前线程

 在线程执行的过程中如果我们按照大类来区分的话,其实只有两种状态可以获得时间片和不可以获得时间片的可运行和阻塞状态。

业界中通行的五种线程状态

 多线程的安全与应用_第33张图片

并不只针对java,线程这个概念是任何操作系统来进行任务调度的时候都会存在的

这个图是宏观的针对线程的状态

 2-11 选择练习

多线程的安全与应用_第34张图片

 多线程的安全与应用_第35张图片

3-1 初试线程同步并使用Synchronized实现线程同步

 什么是线程同步?

多线程的安全与应用_第36张图片

 多线程的安全与应用_第37张图片

 多个线程对同一个对象进行访问的时候,我们需要加入这个同步机制或同步锁来保证线程的有序执行

 创建SyncSample类演示

package com.imooc.thread;

public class SyncSample {

    class Printer{
        //锁对象
        Object lock = new Object();
        //synchronized代码块演示,对自定义对象lock上锁
        public void print1(){
            synchronized (lock) {
                try {
                    Thread.sleep(500);
                    System.out.print("魑");
                    Thread.sleep(500);
                    System.out.print("魅");
                    Thread.sleep(500);
                    System.out.print("魍");
                    Thread.sleep(500);
                    System.out.print("魉");
                    System.out.println();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        //synchronized方法 - 对this当前对象上锁
        public synchronized void print2(){
            try {
                //this
                Thread.sleep(500);
                System.out.print("魑");
                Thread.sleep(500);
                System.out.print("魅");
                Thread.sleep(500);
                System.out.print("魍");
                Thread.sleep(500);
                System.out.print("魉");
                System.out.println();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //synchronized静态方法 - 该类的字节码对象Printer.class
        /*
        public static synchronized void print3(){
            try {
                //Printer.class
                Thread.sleep(500);
                System.out.print("魑");
                Thread.sleep(500);
                System.out.print("魅");
                Thread.sleep(500);
                System.out.print("魍");
                Thread.sleep(500);
                System.out.print("魉");
                System.out.println();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        */
    }

    class PrintTask implements Runnable{
        public Printer printer;
        @Override
        public void run() {
            printer.print2();
        }
    }

    public void start(){
        Printer printer = new Printer();
        for(int i = 0 ; i < 10 ; i++){
            PrintTask task = new PrintTask();
            task.printer = printer;
            Thread thread = new Thread(task);
            thread.start();
        }
    }

    public static void main(String[] args) {
        SyncSample sample = new SyncSample();
        sample.start();
    }
}

首先验证不加同步锁代码会产生什么样的问题?

class Printer{
        public void print(){
                try {
                    Thread.sleep(500);
                    System.out.print("魑");
                    Thread.sleep(500);
                    System.out.print("魅");
                    Thread.sleep(500);
                    System.out.print("魍");
                    Thread.sleep(500);
                    System.out.print("魉");
                    System.out.println();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
 public void start(){
        Printer printer = new Printer();
        printer.print();
    }

 public static void main(String[] args) {
        SyncSample sample = new SyncSample();
        sample.start();
    }

 打印后魑魅魍魉四个字依次输出看起来没有问题

 因为我们此时只有一个对象,如果多线程情况下同时调用printer对象就会出现问题


 //增加新的内部类
 class PrintTask implements Runnable{
       //定义一个类的成员变量
        public Printer printer;
        @Override
        public void run() {
            printer.print();
        }
    }

进行实质的多线程输出

    public void start(){
        Printer printer = new Printer();
        for(int i = 0 ; i < 10 ; i++){
            PrintTask task = new PrintTask();
            task.printer = printer;
            Thread thread = new Thread(task);
            thread.start();
        }
    }

十个线程都是引用了同一个printer对象 ,大概在同一个时点内多个线程会同时对魑来进行输出然后依次输出,这与我们的预期相差非常多。为了解决这个问题我们要引入锁的机制让线程一个一个排队执行。

如何做到前面的线程没有执行完的话,后面所有的线程就一直处于阻塞等待的状态?

   class Printer{
        //锁对象,要有一个对象,那个线程抢到了这个锁那个线程就有执行这个代码的权力
        Object lock = new Object();
        //synchronized代码块演示,对自定义对象lock上锁
        public void print1(){
            //指明是那个锁
            synchronized (lock) {
                try {
                    Thread.sleep(500);
                    System.out.print("魑");
                    Thread.sleep(500);
                    System.out.print("魅");
                    Thread.sleep(500);
                    System.out.print("魍");
                    Thread.sleep(500);
                    System.out.print("魉");
                    System.out.println();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

重新运行

多线程的安全与应用_第38张图片

 定义锁对象,那个线程优先获取到锁,就优先执行,执行后锁会被释放,由其他线程进行争抢,直到所有的线程最后完成处理

3-2 选择练习 

多线程的安全与应用_第39张图片

 多线程的安全与应用_第40张图片

 

 3-3 Synchronized在不同场景下锁对象的区别

 学习Synchronize3种不同的使用办法,通过这三种不同的使用办法了解他的锁对象有哪些

 多线程的安全与应用_第41张图片

 Synchronize代码块:

//锁对象
        Object lock = new Object();
        //synchronized代码块演示,对自定义对象lock上锁
        public void print1(){
            synchronized (lock) {
                try {
                    Thread.sleep(500);
                    System.out.print("魑");
                    Thread.sleep(500);
                    System.out.print("魅");
                    Thread.sleep(500);
                    System.out.print("魍");
                    Thread.sleep(500);
                    System.out.print("魉");
                    System.out.println();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

最典型的特性就是自己自定义一个对象对其进行加锁和保护

新人很容易犯得的错误

我们在实例化的时候对task和thread各自创建了十个对象 ,但我们把十个对象全都关联到一个printer对象里面,为什么呢?

因为在多线程访问时只有对同一个对象来进行访问时才可以获取到同一个锁对象。

多线程的安全与应用_第42张图片

假如我们 注释掉printer在每一次创建新的任务的时候去实例化一个新的printer对象

多线程的安全与应用_第43张图片

 运行后会发现整个代码失去了原有的控制,我们每执行一次循环,都创建了一个全新的printer对象,这也就意味着在代码块执行的过程中每一个printer对象都内置了一个lock

 每一个线程都拥有一个自己的锁对象,因此无法达到我们想要的效果

我们需要的效果是所有线程都去共享一把锁,所以我们全局引用一个printer对象,只有一把锁所有线程去争取这一把锁。

使用synochronized方法

        //synchronized方法 - 对this当前对象上锁
        public synchronized void print2(){
            try {
                //this
                Thread.sleep(500);
                System.out.print("魑");
                Thread.sleep(500);
                System.out.print("魅");
                Thread.sleep(500);
                System.out.print("魍");
                Thread.sleep(500);
                System.out.print("魉");
                System.out.println();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

写在方法上进行描述,同时也不需要我们手动指明那个对象来进行加锁

运行测试

多线程的安全与应用_第44张图片

说明可以进行有效的线程同步
我们并没有指定特定的锁对象,就实现了锁的同步这里的原理是什么?

 开发过程中有一个内置的关键字叫做this对象,this关键字代表了当前类的对象

我们调用多少次的方法作为synochronized对象都是对this对象进行加锁

可以看做是synochronized块的简化书写

静态方法进行描述

//synchronized静态方法 - 该类的字节码对象Printer.class
        /*
        public static synchronized void print3(){
            try {
                //Printer.class
                Thread.sleep(500);
                System.out.print("魑");
                Thread.sleep(500);
                System.out.print("魅");
                Thread.sleep(500);
                System.out.print("魍");
                Thread.sleep(500);
                System.out.print("魉");
                System.out.println();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        */

 静态方法是属于类不属于对象的,因为我们现在使用的是一个内部类

 内部类是不允许使用static的所以写上以后就会报错

static描述的方法是属于类的而不是属于方法的,在这个过程中他的锁对象什么呢?

static无法使用this,按照java的规则会使用当前类的class类printer.class来作为锁对象

对static来说是使用的字节码对象来进行加锁谁得到字节码对象谁有执行权限

 3-4 选择练习

 多线程的安全与应用_第45张图片

 多线程的安全与应用_第46张图片

多线程的安全与应用_第47张图片

 

3-5 实现线程安全解决超卖现象

 什么是线程安全?

多线程的安全与应用_第48张图片

 代码实例

创建mall子包演示

多线程的安全与应用_第49张图片

//消费者类
class Consumer implements Runnable{
    //所有消费者都来到同一个商城
    public Mall mall;
    @Override
    public void run() {
        //商城为每一名消费者销售商品
        mall.sale();
    }
}

//库存类
public class Stock {
    //当前商品库存剩余3个
    public static int count = 3;
}

//模拟商城销售商品
public class Mall {
    public void sale(){
        if(Stock.count > 0 ){
            try {
                //模拟商城办理销售业务,用时5毫秒
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //销售成功库存减少
            Stock.count--;
            System.out.println("商品销售成功");
        }else{
            System.out.println("商品库存不足,请下次再来吧!");
        }
    }

    public static void main(String[] args) {
        //实例化唯一的商城对象
        Mall mall = new Mall();
        //模拟5名顾客同时涌入商城购买商品
        for(int i = 0 ; i < 100 ; i++){
            Consumer consumer = new Consumer();
            consumer.mall = mall;
            Thread thread = new Thread(consumer);
            thread.start();
        }
        try {
            //模拟下班后判断库存
            Thread.sleep(1000);
            System.out.println("当前商品库存为:" + Stock.count);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

只有一名顾客时处理的逻辑

 5名时就变成了

这时就会产生超卖现象 倒赔2台手机

这就是典型的线程安全问题

在单线程处理的时候运行逻辑是正常的,但是一旦到多线程运行的时候就会出现没有预估到的效果,就是线程安全问题

 多线程的安全与应用_第50张图片

 如何解决?

所有的线程安全问题都是多个线程访问一个对象,而我们没有对着一个对象进行有效规划造成的,引入之前学的线程同步解决这个问题 增加synchronized关键字

public class Mall {
    public synchronized void sale(){
        if(Stock.count > 0 ){
            try {
                //模拟商城办理销售业务,用时5毫秒
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //销售成功库存减少
            Stock.count--;
            System.out.println("商品销售成功");
        }else{
            System.out.println("商品库存不足,请下次再来吧!");
        }
    }

运行

多线程的安全与应用_第51张图片

 如何快速打开java自带类的源码?

ctrl+n键

输入类名 

打开ArrayList

多线程的安全与应用_第52张图片

并没有synchronized 关键字进行描述

但是在Vector却使用了synchronized 关键字进行描述

多线程的安全与应用_第53张图片

 加和不加就意味着当前方法是不是线程安全的,加上就是没加就不是

为什么开发时都在使用ArrayList而不再使用早期的Vector呢?

原因是通过ArrayList可以有效通过我们执行的速度,平时工作过程中多线程还是少见的,我们要快速的完成对数据的处理和操作。所以会优先选择线程不安全的方法

4-1 初识线程池及其基本应用

 首先来了解一个包

多线程的安全与应用_第54张图片

 在并发包中最核心的组件就是线程池了

多线程的安全与应用_第55张图片

多线程的安全与应用_第56张图片

 多线程的安全与应用_第57张图片

 4-2 线程池的四种类型

 通过代码介绍四种线程池的不同类型和特点

创建pool包

多线程的安全与应用_第58张图片

 新建第一个案例ThreadPoolSample1 定长线程池

public class ThreadPoolSample1 {
    public static void main(String[] args) {
        //创建一个可创建一个定长线程池,最大只允许在线程池中有10个线程同时运行
        //定长线程池的特点是固定线程总数,空闲线程用于执行任务,如果线程都在使用,后续任务则处于等待状态
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        //验证一下下线程池做法 
        for(int i = 1; i <= 1000 ; i++){
            final int index = i;
            //不需要返回值,使用execute方法执行Runnable对象
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    //获取当前线程名字
                    System.out.println(Thread.currentThread().getName()+":" + index );
                }
            });
        }
        //处理完毕关闭线程池
        threadPool.shutdown();
    }
}

运行看是否最多10个线程执行任务

多线程的安全与应用_第59张图片

 可以看到只有10个线程的名字,通过10个固定长度的线程处理了1000个任务

 如果需要返回值呢?

            /*
            需要返回值,使用submit方法执行Callable对象,利用Future对象接收返回值
            Future ret = threadPool.submit(new Callable() {
                @Override
                public Object call() throws Exception {
                    return null;
                }
            });
            */

增加其他三种类型代码案例

public class ThreadPoolSample2 {
    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);
                }
            });
        }
        threadPool.shutdown();
    }
}

多线程的安全与应用_第60张图片 

作为可缓存线程池主要代码和定长线程池几乎一样,唯一不同的是Executors.newCachedThreadPool();//创建一个可缓存线程池,同时并没有传入任何线程的总量

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

多线程的安全与应用_第61张图片 

单线程代表所有的任务都通过一个任务排队来执行

Executors.newSingleThreadExecutor();//单线程线程池

public class ThreadPoolSample4 {
    public static void main(String[] args) {
        //调度线程池
        ScheduledExecutorService scheduledThreadPool =  Executors.newScheduledThreadPool(5);//可调度线程池
        //延迟1秒执行,每三秒执行一次
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(new Date() + "延迟1秒执行,每三秒执行一次");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
}

 

调度线程池就是定时执行某个任务

Executors.newScheduledThreadPool(5);//可调度线程池
所有任务都是以定时的时长间隔来执行run方法中所规定的东西

4-3 课程总结 

 多线程的安全与应用_第62张图片

你可能感兴趣的:(多线程,java)