彻底搞懂Java多线程(三)

目录
  • Java线程池
    • 线程池的优点
    • 线程池的6种创建方式
    • 创建单个线程池的作用是什么?
    • 线程池的第七种创建方式
    • ThreadPoolExecutor的执行方式
    • ThreadPoolExecutor的执行流程
    • 线程池的终止
    • 线程池的状态
    • 异步、同步
      • 1.Java 线程 同步与异步
    • 线程工厂
  • 总结

Java线程池

线程的缺点:

1.线程的创建它会开辟本地方法栈、JVM栈、程序计数器私有的内存,同时消耗的时候需要销毁以上三个区域,因此频繁的创建和销毁线程比较消耗系统的资源。

2.在任务量远远大于线程可以处理的任务量的时候,不能很好的拒绝任务。

所以就有了线程池:

使用池化的而技术来管理和使用线程。

线程池的优点

1.可以避免频繁的创建和销毁线程

2.可以更好的管理线程的个数和资源的个数。

3.线程池拥有更多的功能,比如线程池可以进行定时任务的执行。

4.线程池可以更友好的拒绝不能处理的任务。

线程池的6种创建方式

一共有7种创建方式

创建方式一:

创建固定个数的线程池:

package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 10:24;
 */
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        //创建一个固定个数的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //执行任务
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名" + Thread.currentThread().getName());
                }
            });
        }
    }
}

那么如果执行次数大于10次呢?

线程池不会创建新的线程,它会复用之前的线程。

那么如果只执行两个任务呢?它创建了是10个线程还是两个线程呢?

我们可以使用Jconsole来看一看:

结果是只有2个线程被创建。

创建方式二:

创建带有缓存的线程池:

适用于短期有大量的任务的时候使用

public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        //创建带缓存的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}

方式三:

创建执行定时任务的线程池

package ThreadPoolDemo;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 11:32;
 */
public class ThreadPoolDemo3 {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        System.out.println("执行定时任务前的时间:" + new Date());
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行任务的时间:" + new Date());
            }
        },1,2, TimeUnit.SECONDS);
    }
}

执行任务的四个参数的意义:

参数1:延迟执行的任务

参数2:延迟一段时间后执行

参数3:定时任务执行的频率

参数4:配合前两个参数使用,是2、3参数的时间单位

还有两种执行的方法:

只会执行一次的方法:

第三种的执行方式:

那么这种的执行方式和第一种的执行方式有什么区别呢?

当在两种执行的方式中分别加上sleep()之后:

方式一:

方式三:

结论很明显了:

第一种方式是以上一个任务的开始时间+定时的时间作为当前任务的开始时间

第三种方式是以上一个任务的结束时间来作为当前任务的开始时间。

创建方式四:

package ThreadPoolDemo;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 12:38;
 */
public class ThreadPoolDemo4 {
    public static void main(String[] args) {
        //创建单个执行任务的线程池
        ScheduledExecutorService scheduledExecutorService
                = Executors.newSingleThreadScheduledExecutor();
        System.out.println("执行任务之前" + new Date());
        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是SingleThreadSchedule"+ new Date());
            }
        },3,1, TimeUnit.SECONDS);
    }
}

创建方式五:

创建单个线程的线程池

package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 12:55;
 */
public class ThreadPoolDemo5 {
    public static void main(String[] args) {
        //创建单个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 20; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名 " +  Thread.currentThread().getName());
                }
            });
        }
    }
}

创建单个线程池的作用是什么?

1.可以避免频繁创建和销毁线程所带来的性能的开销

2.它有任务队列,可以存储多余的任务

3.可以更好的管理任务

4.当有大量的任务不能处理的时候,可以友好的执行拒绝策略

创建方式六:

创建异步线程池根据当前CPU来创建对应个数的线程池

package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 13:12;
 */
public class ThreadPoolDemo6 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newWorkStealingPool();
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名" + Thread.currentThread().getName());
                }
            });
        }
    }
}

运行结果为什么什么都没有呢?

看下面的异步与同步的区别就知道了。

加上这个

就可以输出结果了

线程池的第七种创建方式

前六种的创建方式有什么问题呢?

1.线程的数量不可控(比如带缓存的线程池)

2.工作任务量不可控(默认的任务队列的大小时Integer.MAX_VALUE),任务比较大肯会导致内存的溢出。

所以就可以使用下面的创建线程池的方式了:

package ThreadPoolDemo;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 15:05;
 */
public class ThreadPoolDemo7 {
    private static int threadId = 0;
    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("我是threadPool-" + ++threadId);
                return thread;
            }
        };
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 100,
                TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(12),
                threadFactory, new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 15; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}

参数说明:

  • 参数一:核心线程数|线程池正常情况下的线程 数量
  • 参数二:最大线程数|当有大量的任务的时候可以创建的最多的线程数
  • 参数三:最大线程的存活时间
  • 参数四:配合参数三一起使用的表示参数三的时间单位
  • 参数五:任务队列
  • 参数六:线程工厂
  • 参数七:决绝策略

注意事项:最大的线程数要大于等于核心的线程数

五种拒绝策略

为什么拒绝策略可以舍弃最新的任务或者最旧的任务呢?

因为LinkedBlockingDeque时FIFO的。

第五种:自定义的拒绝策略

ThreadPoolExecutor的执行方式

package ThreadPoolDemo;
import java.util.concurrent.*;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 16:58;
 */
public class ThreadPoolDemo9 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 100,
                TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.DiscardOldestPolicy());

        //线程池的执行方式一
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("使用了execute()执行了线程池");
            }
        });
        //线程池的执行方式二
        Future<String> futureTask =
                threadPoolExecutor.submit(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        return "使用submit(new Callable<>())执行了线程池";
                    }
                });
        System.out.println(futureTask.get());

    }
}

无返回值的执行方式

有返回值的执行方式

ThreadPoolExecutor的执行流程

当任务量小于核心线程数的时候,ThreadPoolExecutor会创建线程来执行任务

当任务量大于核心的线程数的时候,并且没有空闲的线程时候,且当线程池的线程数小于最大线程数的时候,此时会将任务存

放到任务队列中

如果任务队列也被存满了,且最大线程数大于线程池的线程数的时候,会创建新的线程来执行任务。

如果线程池的线程数等于最大的线程数,并且任务队列也已经满了,就会执行拒绝策略。👇

线程池的终止

shutdown()

线程池的任务会执行完

shutdownNow()

立即终止线程池,线程池的任务不会执行完

线程池的状态

异步、同步

1.Java 线程 同步与异步

多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理。显然这是由于全局资源造成的,有时为了解决此问题,优先考虑使用局部变量,退而求其次使用同步代码块,出于这样的安全考虑就必须牺牲系统处理性能,加在多线程并发时资源挣夺最激烈的地方,这就实现了线程的同步机制

同步

A线程要请求某个资源,但是此资源正在被B线程使用中,因为同步机制存在,A线程请求不到,怎么办,A线程只能等待下去

异步

A线程要请求某个资源,但是此资源正在被B线程使用中,因为没有同步机制存在,A线程仍然请求的到,A线程无需等待同步的方式:

1.发送请求

2.等待执行完成

3.有结果的返回

异步的方式

1.发请求

2.执行完成

3.另一个线程异步处理

4.处理完成之后返回回调结果

显然,同步最最安全,最保险的。而异步不安全,容易导致死锁,这样一个线程死掉就会导致整个进程崩溃,使用异步的机制,性能会有所提升

线程工厂

设想这样一种场景,我们需要一个线程池,并且对于线程池中的线程对象,赋予统一的线程优先级、统一的名称、甚至进行统一的业务处理或和业务方面的初始化工作,这时工厂方法就是最好用的方法了

package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/**
 * user:ypc;
 * date:2021-06-13;
 * time: 11:12;
 */
public class ThreadFactoryDemo {
    public static void main(String[] args) {
        MyThreadFactory myThreadFactory = new MyThreadFactory();
        ExecutorService executorService =  Executors.newFixedThreadPool(10,myThreadFactory);
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("使用线程工厂设置的线程名:"+ Thread.currentThread().getName() +
                            " 使用线程工厂设置的线程的优先级" + Thread.currentThread().getPriority());
                }
            });
        }

    }
    private static int count = 0;
     static class MyThreadFactory implements ThreadFactory{
         @Override
         public Thread newThread(Runnable r) {
             Thread thread = new Thread(r);
             thread.setPriority(8);
             thread.setName("thread--" + count++);
             return thread;
         }
     }
}

总结

本篇文章就到这里了,希望可以对你有所帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 彻底搞懂Java多线程(四)

    目录 SimpleDateFormat非线程安全问题 ThreadLocal ThreadLocal的原理 ThreadLocal常用方法 ThreadLocal的初始化 InheritableThreadLocal的使用 总结 SimpleDateFormat非线程安全问题 实现1000个线程的时间格式化 package SimpleDateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java

  • 彻底搞懂Java多线程(二)

    目录 Java中的锁 1.synchronized锁(jvm层的解决方案,也叫监视器锁) 2.手动锁Lock synchronized锁 synchronized使用场景 1.使用synchronized来修饰代码块(可以给任意的对象进行加锁操作) 2.使用synchronized来修饰静态方法(对当前的类进行加锁的操作) 3.使用synchronized来修饰普通的方法(对当前类的实例来进行加锁) synchronized注意事项 1.加锁的时候一定要使用同一把锁对象 Lock锁使用的注意事项

  • 彻底搞懂Java多线程(五)

    目录 单例模式与多线程 立即加载/饿汉模式 延时加载/懒汉模式 饿汉/懒汉对比 阻塞队列的实现 常见的锁策略 乐观锁 CAS CAS在java中的应用 CAS 的ABA问题 ABA 问题的解决 悲观锁 独占锁.共享锁.自旋锁.可重入锁 详解synchronized锁的优化问题 Semaphore Semaphore的作用: Semaphore实现原理: Semaphore的使用: CountDownLatch\CyclicBarrier CountDownLatch CountDownLatch

  • 彻底搞懂Java多线程(一)

    目录 Java多线程 线程的创建 线程常用方法 线程的终止 1.自定义实现线程的终止 2.使用Thread的interrupted来中断 3.Thraed.interrupted()方法和Threaed.currentThread().interrupt()的区别 线程的状态 线程的优先级 守护线程 线程组 线程安全问题 volatile关键字 总结 Java多线程 线程的创建 1.继承Thread 2.实现Runnable 3.实现Callable 使用继承Thread类来开发多线程的应用程序

  • 彻底搞懂Java多线程(三)

    目录 Java线程池 线程池的优点 线程池的6种创建方式 创建单个线程池的作用是什么? 线程池的第七种创建方式 ThreadPoolExecutor的执行方式 ThreadPoolExecutor的执行流程 线程池的终止 线程池的状态 异步.同步 1.Java 线程 同步与异步 线程工厂 总结 Java线程池 线程的缺点: 1.线程的创建它会开辟本地方法栈.JVM栈.程序计数器私有的内存,同时消耗的时候需要销毁以上三个区域,因此频繁的创建和销毁线程比较消耗系统的资源. 2.在任务量远远大于线程可

  • 一文彻底搞懂java多线程和线程池

    目录 什么是线程 一. Java实现线程的三种方式 1.1.继承Thread类 1.2.实现Runnable接口,并覆写run方法 二. Callable接口 2.1 Callable接口 2.2 Future接口 2.3 Future实现类是FutureTask. 三. Java线程池 3.1.背景 3.2.作用 3.3.应用范围 四. Java 线程池框架Executor 4.1.类图: 4.2 核心类ThreadPoolExecutor: 4.3 ThreadPoolExecutor逻辑结

  • Java基础:彻底搞懂java多线程

    目录 进程与线程 使用多线程的优势 线程的状态 创建线程 线程中断 总结 进程与线程 进程 进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位.进程可以被看作程序的实体,同样,它也是程序的容器. 线程 线程是操作系统调度的最小单元,也叫作轻量级进程.在一个进程中可以创建多个线程,这些线程都拥有各自的计数器.堆栈和局部变量等属性. 使用多线程的优势 使用多线程可以减少程序的响应时间 如果某个操作很耗时,或者陷入长时间的等待,此时程序将不会响应鼠标和键盘等

  • 一文搞懂Java创建线程的五种方法

    目录 题目描述 解题思路 代码详解 第一种 继承Thread类创建线程 第二种:实现Runnable接口创建线程 第三种:实现Callable接口,通过FutureTask包装器来创建Thread线程 第四种:使用ExecutorService.Callable(或者Runnable).Future实现返回结果的线程 第五种:使用ComletetableFuture类创建异步线程,且是据有返回结果的线程 题目描述 Java创建线程的几种方式 Java使用Thread类代表线程,所有线程对象都必须

  • 一文搞懂Java并发AQS的共享锁模式

    目录 概述 自定义共享锁例子 核心原理机制 源码解析 成员变量 共享锁获取acquireShared(int) 共享释放releaseShared(int) 概述 这篇文章深入浅出理解Java并发AQS的独占锁模式讲解了AQS的独占锁实现原理,那么本篇文章在阐述AQS另外一个重要模式,共享锁模式,那什么是共享锁呢? 共享锁可以由多个线程同时获取, 比较典型的就是读锁,读操作并不会产生副作用,所以可以允许多个线程同时对数据进行读操作而不会有线程安全问题,jdk中的很多并发工具比如ReadWrite

  • 一文彻底搞懂Java和JDK的版本命名问题

    Java是面向对象的编程语言,在我们开发Java应用的程序员的专业术语里,Java这个单词其实指的是Java开发工具,也就是JDK(Java Development Kit).所以我们常常在CSDN等各大程序员论坛讨论到安装Java8或者JDK8或者JDK1.8或J2SE8或J2SE1.8或J2SE8或J2SE1.8,其实这3个专业词汇的概念是一样的. 告诉庆哥,你对Java的版本号以及JDK的命名真正清楚嘛?比如: Java8 Java SE 8.0 JDK1.8 -- 知道这些是怎么回事嘛?

随机推荐