• 【Java进阶】泛型和多线程


    1. 泛型

    1.1 初始泛型的应用

    • 泛型就是允许在定义类、接口、方法时使用类型形参(泛型),这个类型形参将在声明变量、创建对象、调用方法时动态地指定,进而让程序具备编译时检查的能力
    • 泛型是JDK1.5提供的1特性,最常见的泛型应用场景便是在使用集合时通过泛型指定集合内对象的类型,为程序代码提供了编译时纠错的能力
    package com.ricky.parameterized;
    
    import java.util.ArrayList;
    
    public class ListSample {
    
        public static void main(String[] args) {
            ArrayList list = new ArrayList();
            list.add("hello");
            list.add("haha");
            list.add(12345);
    
            String str = (String)list.get(1);
            System.out.println(str.substring(0, 2));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    不使用泛型同样可以插入元素,但编译器不会对元素的类型进行检查,取出元素时返回的都是Object对象

    package com.ricky.parameterized;
    
    import java.util.ArrayList;
    
    public class ListSample {
    
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList();
            list.add("hello");
            list.add("haha");
            // list.add(12345); // 报错
    
            System.out.println(list.get(1).substring(0, 2));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    1.2 自定义泛型及其应用

    ①创建自定义泛型类

    自定义泛型类,需要在类名后增加"<标识符>":如:public class SampleClass {...}

    的区别,标识符的字母本身并无强制要求,常见写法有两种:

    • 是Type单词的首字母,说明传入的是类型
    • 是Element元素的首字母,代表是集合中的元素
    package com.ricky.parameterized;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    
    public class CollectionUtils<E> {
    
        private List<E> data = new ArrayList();
        public void add(E element) {
            data.add(element);
        }
        public E randomSelect() {
            Random random = new Random();
    
            return data.get(random.nextInt(data.size()));
        }
    
        public static void main(String[] args) {
            CollectionUtils<String> utils = new CollectionUtils<>();
            utils.add("haha");
            utils.add("xixi");
            utils.add("heihei");
            utils.add("huhu");
            System.out.println(utils.randomSelect());
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    泛型方法

    JDK 1.5以后还提供了泛型方法的支持,允许在类没有声明泛型的前提下让方法独立使用泛型进行开发

    package com.ricky.parameterized;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class PtMethod {
        public <T> List<T> transferToList(T[] array) {
            List<T> list = new ArrayList<>();
            for (T item : array) {
                list.add(item);
            }
            return list;
        }
    
        public static void main(String[] args) {
            PtMethod ptMethod = new PtMethod();
            String[] array = new String[]{"A", "B", "C", "D"};
            List<String> list = ptMethod.transferToList(array);
            System.out.println(list);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    1.3 泛型通配符

    泛型的匹配规则

    • 当明确指定泛型类型后,就必须强制使用该类类型传入,该类型的子类也同样会报“类型不匹配”错误
    • 为了增加泛型的匹配范围,泛型通配符应运而生
    • 代表所有类型均可传入

    通过extends与super限定范围

    • extends关键字代表必须传入Shape或者子类才通过检查

      public void doSth(List<? extends Shape> shapeList)
      
      • 1
    • super关键字代表必须传入Rectangle或者其父类才能通过检查

      public void doSth(List<? super Rectangle> shapeList)
      
      • 1

    2. 线程

    2.1 多线程的概念

    程序是静态文件,进程是程序运行后在内存中的实例

    • 当一个程序执行进入内存运行时,即变成一个进程
    • 进程的资源是彼此隔离的,其他进程不允许访问

    线程是进程内执行的“任务”

    • 线程是进程内的一个“基本任务”,每个线程都有自己的功能,它是CPU分配与调度的基本单位
    • 一个进程内可以包含多个线程,反之一个线程只能隶属于某一个进程
    • 进程内至少拥有一个“线程”,这个线程叫“主线程”,主线程消亡则进程结束

    Java中进程与线程

    • 一般称只有一个main主线程的程序为“单线程”程序,又垃圾收集线程不考虑
    • 由main主线程创建出来线程,成为多线程程序
    • 在这里插入图片描述

    2.2 创建多线程的三种方式

    ① 继承Thread类创建线程
    package com.ricky.thread;
    
    import java.util.Random;
    
    public class ThreadSample {
        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) {
            ThreadSample threadSample = new ThreadSample();
            threadSample.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    此方法不太推荐,因为Java是单继承的,继承了Thread类就无法再继承其它的类了,因而我们更偏向于使用接口

    ② 实现Runnable接口创建线程

    更为推荐的方式

    package com.ricky.thread;
    
    import java.util.Random;
    
    public class ThreadSample {
        class Runner implements Runnable {
    
            @Override
            public void run() {
                Integer speed = new Random().nextInt(9) + 1;
                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");
            threadA.start();
            Thread threadB = new Thread(runner);
            threadB.setName("B");
            threadB.start();
            Thread threadC = new Thread(runner);
            threadC.setName("C");
            threadC.start();
            Thread threadD = new Thread(runner);
            threadD.setName("D");
            threadD.start();
        }
    
        public static void main(String[] args) {
            ThreadSample threadSample = new ThreadSample();
            threadSample.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    ③ Callable接口创建线程

    允许线程执行后将值进行返回,这是Callable接口的特点

    package com.ricky.thread;
    
    import java.util.Random;
    import java.util.concurrent.*;
    
    public class ThreadSample {
        class Runner implements Callable<Integer> { // 有泛型是因为使用Callable接口支持返回值
            public String name;
    
            @Override
            public Integer call() {
                Integer speed = new Random().nextInt(9) + 1;
                Integer totalMeter = 0;
                for (int i = 1; i <= 10; i++) {
                    Thread.sleep(1000);
                    totalMeter += speed;
                    System.out.println("第" + i + "秒:" + this.name + "已跑到" + (i * speed) + "米(" + speed + "米/秒)");
                }
                return totalMeter;
            }
        }
    
        public void start() throws ExecutionException, InterruptedException {
            ExecutorService executorService = Executors.newFixedThreadPool(4);
            Runner threadA = new Runner();
            threadA.name = "A";
            Runner threadB = new Runner();
            threadB.name = "B";
            Runner threadC = new Runner();
            threadC.name = "C";
            Runner threadD = new Runner();
            threadD.name = "D";
            Future<Integer> r1 = executorService.submit(threadA);
            Future<Integer> r2 = executorService.submit(threadB);
            Future<Integer> r3 = executorService.submit(threadC);
            Future<Integer> r4 = executorService.submit(threadD);
            executorService.shutdown();
            System.out.println(threadA.name + "累计跑了" + r1.get() + "米");
            System.out.println(threadB.name + "累计跑了" + r2.get() + "米");
            System.out.println(threadC.name + "累计跑了" + r3.get() + "米");
            System.out.println(threadD.name + "累计跑了" + r4.get() + "米");
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ThreadSample threadSample = new ThreadSample();
            threadSample.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    ④ 总结
    • 继承Thread,Java对继承不友好,不推荐使用
    • 实现Runnable接口,Java编程友好,但无法返回执行后数据
    • 实现Callable接口,可以返回多线程执行结果,编程稍显复杂

    2.3 线程同步

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

    ① synchronized代码块 - 任意对象即可
    package com.ricky.thread;
    
    public class SyncSample {
        class Printer {
            Object lock = new Object(); // 锁对象
            public void print() {
                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 (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        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();
            }
        }
    
        public static void main(String[] args) {
            SyncSample syncSample = new SyncSample();
            syncSample.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    ② synchronized方法 - this当前对象

    最常用

    package com.ricky.thread;
    
    public class SyncSample {
        class Printer {
            public synchronized void print() { // 不需要指名锁对象,默认使用this关键字作为锁对象
                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 (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        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();
            }
        }
    
        public static void main(String[] args) {
            SyncSample syncSample = new SyncSample();
            syncSample.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    ③ synchronized静态方法 - 该类的字节码对象

    静态方法同样也是直接在static后加上synchronized关键字即可,但要注意这里的锁对象不再是this,静态方法没有this,默认使用类名.class字节码对象来加锁

    2.4 线程安全

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

    “超卖”现象

    进行库存判断的时候其余线程仍未进行库存递减

    在这里插入图片描述

    这时候就需要给售卖这个函数加锁,让每个线程一个一个有序的执行

    2.5 线程池

    并发是伴随着多核处理器的诞生而产生的,为了充分利用硬件资源,诞生了多线程技术。但是多线程又存在资源竞争的问题,引发了同步和互斥的问题,JDK 1.5推出的java.util.concurrent(并发工具包)来解决这些问题

    Runnable接口的弊端

    • Runnable新建线程,性能差
    • 线程缺乏统一管理,可能无限制的新建线程,相互竞争,严重时会占用过多系统资源导致死机或内存溢出

    ThreadPool线程池

    • 重用存在的线程,减少线程对象创建、消亡的开销
    • 线程总数可控,提高资源的利用率
    • 提供额外功能,定时执行、定期执行、监控等

    JUC支持的线程池种类

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

    • FixedThreadPool - 定长线程池
    • CachedThreadPool - 可缓存线程池
    • SingleThreadExecutor - 单线程池
    • ScheduledThreadPool - 调度线程池
    ① FixedThreadPool
    package com.ricky.thread;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class ThreadPool {
    
        public static void main(String[] args) {
            // 创建一个定长的线程池
            // 定长线程池的特点是固定线程总数,空闲线程用于执行任务,如果线程都在使用,后续任务则处在等待状态
            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);
                    }
                });
                /*
                // 需要返回值,使用submit方法执行Callable对象,利用Future对象接收返回值
                Future ret = threadPool.submit(new Callable() {
                    @Override
                    public Object call() throws  Exception {
                        return null;
                    }
                });
                 */
            }
            threadPool.shutdown();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    ② CachedThreadPool
    package com.ricky.thread;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ThreadPool {
    
        public static void main(String[] args) {
            // 调度器对象
            // ExecutorService用于管理线程池
            ExecutorService threadPool = Executors.newCachedThreadPool(); // 创建一个可缓存线程池
            // 了缓存线程池的特点是:无限大,如果线程池中没有可用的线程则创建,有空闲线程则利用起来
            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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    ③ SingleThreadExecutor

    用的比较少

    package com.ricky.thread;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ThreadPool {
    
        public static void main(String[] args) {
            // 调度器对象
            // ExecutorService用于管理线程池
            ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 单线程线程池
            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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    ④ ScheduledThreadPool
    package com.ricky.thread;
    
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    public class ThreadPool {
    
        public static void main(String[] args) {
            // 调度线程池
            ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
            // 延迟1秒执行,每3秒执行一次
            scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println(new Date() + " 延迟1秒执行,每三秒执行一次");
                }
            }, 1, 3, TimeUnit.SECONDS);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    轻量级神经网络算法系列文章-ShuffleNet v2
    java:jvm参数设置
    yolov5 通过视频进行目标检测
    高效理解 FreeSql WhereDynamicFilter,深入了解设计初衷[.NET ORM]
    string的应用及模拟实现
    Python 全栈系列209 so_pack
    QT基本对话框(基本对话框、工具盒类、进度条、调色板与电子钟、可扩展对话框、程序启动画面)
    网络安全(黑客技术)2024年三个月自学手册
    【大数据离线开发】6.2 MapReduce的高级特性
    记一次线上bug排查-----SpringCloud Gateway组件 请求头accept-encoding导致响应结果乱码
  • 原文地址:https://blog.csdn.net/weixin_52665939/article/details/127841109