有关多线程的学习(内附线程池的内容补充)

楔子

进程:每个进程都是独立的代码和数据的空间(进程上下文)  进程间的切换会有较大的开销   --->  一个进程包含1~n个线程

注:进程是资源分配的最小单元

线程:同一类线程共享代码和数据空间  每个线程有独立的运行站和程序计数器(PC)线程切换开销小

注:线程是cpu调度的最小单元

线程/进程的五个阶段:  创建  就绪  运行  阻塞  终止

多进程和多线程:
	 多进程:  指操作系统同时运行多个程序(任务)
	 多线程:  指同一个程序中有多个顺序流在执行 

关于多线程的说明:
	在进程启动运行main方法的时候 JVM会启动一个主进程  主进程中的主线程(main)随之启动
	但在JVM中另一个线程也会随之启动-->垃圾回收线程
	===> 也就是说 在Java中 每次程序的运行至少需要启动两个线程 main线程+垃圾回收线程
	同时要注意,在Java中所有的线程都是同时启动的,至于什么时候,哪个线程先执行,取决于谁先抢到cpu资源

有关并行与并发:
	并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时
	并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu的操作层面不是真正的同时

名词解释:
	主线程:JVM调用main()时所产生的线程
	守护线程(后台线程):为其他线程提供服务的线程 ---> JVM垃圾回收线程
	当前线程:通过Thread.currentThread()得到的线程
	前台线程:接受后台线程服务的线程,与后台线程联合使用

1.关于线程生命周期及五种基本状态
	1.创建状态(New): 创建一个线程对象 --> Thread t = new MyThread()
	2.就绪状态(Runnable):当调用线程对象的start()后,线程就进入就绪状态.
	注:处于就绪状态的线程,这是说明这一线程已经做好了准备,随时准备抢占cpu的调度执行权限,也就是说是出于一种"备战"状态
	3.运行状态(Running):当一个处于Runnable状态的线程抢到了cpu的调度执行权限后,这一线程开始真正执行,转变为运行状态
	注:Runnable状态是Running状态的唯一入口  也就是说,线程想要变为Running状态,就必须事先转变为Runnable状态
	4.阻塞状态(Blocked):处于Running状态的线程,由于某种原因,暂时放弃对cpu的调度使用权限,停止当前线程的的执行操作,进入阻塞状态. 直到这一线程重新进入就绪状态,才有机会再次争抢cpu调度使用权,进而进入Running状态
	
	根据产生阻塞的原因,大致分为三种:
		1.**等待阻塞:**运行状态中的线程执行wait()方法,当前线程转入阻塞状态
		2.**同步阻塞**:某一线程在争抢同步锁(synchronized)时失败,自动转入同步阻塞状态
		3.**其他阻塞**:通过当前线程的sleep()/join()/发出了I/O请求时,线程会转入阻塞状态.伴随着sleep()状态超时/join()等待终止/超时或者I/O处理完毕,该线程重新转回Runnable状态
		
	5.死亡状态(Dead):线程执行完毕或因一场退出了run()方法,该线程结束当前生命周期

线程状态转换(多线程操作核心):
多线程操作核心
2.实现多线程的两种方式
一 继承Thread类
二 实现Runnable接口
三 实现Callable接口,与Future 线程池结合使用
四 通过线程池创建线程

线程常用的调用方法:

在这里插入图片描述

实现多线程方式一  extends Thread
public class MyThread extends Thread {

    /**
     * 这里创建一个构造方法是为了能够在创建线程的时候做上标记
     * @param name
     */
    public MyThread(String name) {
        super(name);
    }

    /**
     * 创建一个总数 num 作为线程的总数限制
     */
    private Integer num = 150;

    @Override
    public void run() {
        for (int i = 0; i < 150; i++){
            System.out.println(super.getName()+"抢到了"+ num-- +"线程");
        }
    }
}
-------------------------------------------
public class ThreadTest {

    public static void main(String[] args) {
        // 创建三个线程 并调用Thread的start() 进入Runnable状态
        new MyThread("A").start();
        new MyThread("B").start();
        new MyThread("C").start();
        new MyThread("D").start();
        new MyThread("E").start();
    }
}
运行上述实现线程的方式,我们可以发现在多线程执行的过程中是乱序执行,同时存在一个非常严重的问题 ==> 每个线程都执行了 num 次

	实现多线程方式二   implements Runnable
public class ThreadDemo implements Runnable {
    private Integer num = 500;

    @Override
    public void run() {
        for (int i = 0; i < 500; i++) {
            if (num >= 0) {
                System.out.println(Thread.currentThread().getName() + "抢到了编号为:" + num-- + "的执行权限");
            }
        }
    }
}
-------------------------------
public class RunnableDemo {

    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        // 创建五个线程用来并发
        new Thread(td, "AA").start();
        new Thread(td, "BB").start();
        new Thread(td, "CC").start();
        new Thread(td, "DD").start();
        new Thread(td, "EE").start();
    }
}
	运行这种程序我们得到的结果接近预期(总数是我们设定的值),但是仍然存在乱序执行的问题,也就是说线程安全性还是没有得到有效保障
		注:实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的

		总结Thread和Runnable的区别:
			调用单独一个线程完成一件事情 ==> 调用Thread的方式实现更为简洁高效
			需要实现资源共享 ==> 实现Runnable接口实现多线程并发

		实现Runnable接口比继承Thread类所具有的优势:
			1.适合多个相同的程序代码的线程去处理同一个资源
			2.可以避免Java中的单继承限制问题
			3.增加程序的健壮性 代码可以被多个线程共享 代码和数据独立
			4.线程池只能放入实现Runnable或Callable类的线程 不能直接放入继承Thread类的
https://baike.baidu.com/item/%E7%BA%BF%E7%A8%8B%E6%B1%A0/4745661?fr=aladdin


	实现多线程方式三  
/**
 * 定义一个类 实现Callable接口
 */
public class Ticket<Object> implements Callable<Object> {
    /**
     * 覆写Callable中的方法
     * @return
     * @throws Exception
     */
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"-->我是通过实现Callable接口通过FutureTask包装器来实现的线程");
        return null;
    }
}
class ThreadDemo3{
    public static void main(String[] args) {
        Callable oneCallable = new Ticket();
        FutureTask<Object> oneFuture = new FutureTask<Object>(oneCallable);

        Thread thread = new Thread(oneFuture);
        thread.start();
        System.out.println(Thread.currentThread().getName());
    }
}
	实现多线程方式四 :  通过线程池创建线程
			详见后面四种通过线程池创建多线程的方式

	关于多线程并发的线程安全性问题:
		在通过实现Runnable接口造成高并发的示例中,我们得到的计算结果任然是乱序的,也就是说此时的多线程的高并发操作还是存在着线程安全的问题
	
	关于线程优先级:
		1.调整线程优先级
			Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级
				Java线程的优先级用整数表示,取值范围1~10,Thread类有一下三个静态常量:
					static int MAX_PRIORITY	线程可以具有的最高优先级,取值为10。
					static int MIN_PRIORITY	线程可以具有的最低优先级,取值为1。
	   				static int NORM_PRIORITY 分配给线程的默认优先级,取值为5。
	   				
		2.线程优先级特性
			每个线程都有默认的优先级 主线程的默认优先级为 Thread.NORM_PRIORITY
			线程的优先级具有继承关系,如A线程中创建了B线程,那么B将具有和A同样等级的线程
			Java提供了是个线程优先级,但是在和系统交互的过程中经常出现各种各样的映射问题,如果希望多线程能够一直到各个操作系统中,应该使用上述三个静态常量作为优先级,增强交互友好性
			
		3.关于线程阻塞方法的介绍
			1)线程睡眠[Thread.sleep()]:设定睡眠时间(毫秒为单位),睡眠结束,转为Runnable状态. 注:sleep()平台移植性好
			2)线程等待[Object.wait()]:当前线程调用这一方法之后线程转变为阻塞状态(等待队列),直至其他线程调用此对象的notify()或notifyAll()唤醒方法,这一线程转变为阻塞状态(锁池).
			3)线程让步[Thread.yield()]:暂停当前线程的执行状态,将执行机会让给同等级或更高优先级的线程,当前线程转变为Runnable状态.
			4)线程加入[t1.join()]:当前线程调用join(),等待其他线程结束;在当前线程中调用另一个线程的join(),则当前线程转入阻塞状态,直至被调用线程执行完毕,当前状态再由阻塞状态转变为Runnable状态
			5)线程唤醒[Object.notify()]:唤醒等待队列中的一个线程(随机),被唤醒的线程进入锁池状态.类似的方法还有notifyAll(),唤醒在此对象上等待的所有线程

		4.常用方法的使用说明:
			1)sleep():当前线程休眠指定时间,时间结束转为Runnable状态
			2)join():通常用作主线程等待子线程的终止.
			说明:在多数情况下,主线程启动并启动了子线程,如果子线程里进行大量的耗时的运算,主线程往往在子线程结束之前结束.很多时候,主线程处理事务需要用到子线程的处理结果,此时就需要join()实现

代码演示:

public class JoinDemo {

    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + "主线程开始...");
        JoinTest ta = new JoinTest("子线程A");
        JoinTest tb = new JoinTest("子线程B");
        JoinTest tc = new JoinTest("子线程C");
        ta.start();
        tb.start();
        tc.start();
        
        System.out.println(Thread.currentThread().getName() + "主线程结束...");
    }
}
------------------------------------
public class JoinTest extends Thread {
    public JoinTest(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始...");
        for (int i = 0; i < 30; i++) {
            System.out.println(Thread.currentThread().getName() + "正在执行...");
        }
        System.out.println(Thread.currentThread().getName() + "结束...");
    }
}
	根据掩饰代码,我们可以很清楚的看到在子线程尚未结束的情况下主线程已经执行完毕,也就是说这种情况下主线程中的程序是无法调用子程序的运行结果的,无法满足实际生产中的需求
	如果想改进这一现状,我们需要在主线程执行完毕之前等待子线程执行的结果 ---> 在主线程中调用子线程的join()
public class JoinDemo {

    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + "主线程开始...");
        JoinTest ta = new JoinTest("子线程A");
        JoinTest tb = new JoinTest("子线程B");
        JoinTest tc = new JoinTest("子线程C");
        ta.start();
        tb.start();
        tc.start();
        // 在主线程中调用其他线程的join()  观察是否等待
        ta.join();
        tb.join();
        tc.join();
        System.out.println(Thread.currentThread().getName() + "主线程结束...");
    }
}
		这样执行的结果就能看到主线程是在子线程运行结束之后才结束的,也就是说此时主线程的执行过程中可以调用到子线程的运算结果
			3)yield():让当前运行的线程回到Runnable状态,以允许具有相同优先等级的的其他线程获得运行的机会.但在实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度再次选中
			注:yield()从未导致线程转到等待/睡眠/阻塞状态
public class YieldDemo extends Thread{
    /**
     * 定义线程执行次数
     */
    private Integer amount = 70;

    public YieldDemo(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < amount; i ++){
            System.out.println(this.getName()+"执行当前线程编号为:"+ i);
            // 根据限定条件执行让行操作 当前线程重新回归Runnable状态 和其他的线程一起争夺cpu的调度权限
            if ( i == 45){
                Thread.yield();
            }
        }
    }
}

class MianTest{
    public static void main(String[] args) {
        YieldDemo ta = new YieldDemo("A");
        YieldDemo tb = new YieldDemo("B");
        YieldDemo tc = new YieldDemo("C");
        ta.start();
        tb.start();
        tc.start();
    }
}
		对比sleep()与yield():
			sleep() --> 当前线程进入停止状态 指定时间内不会执行 空闲时间由程序执行
			yield() --> 当前线程重回Runnable状态 重新开始争抢cpu调度使用权限 空闲时间不可限定

		关于yield()执行过程的补充:
			当前线程执行yield()后,先检测当前是否具有相同优先等级的线程处于Runnable状态,如果有,会在相同优先级的线程中随机选出一个线程让其执行,否则,当前线程继续执行 ==> 同等优先级之间的"退让"

		关于sleep()执行过程的补充:
			当前线程执行sleep()后,当前所有处于Runnable状态的线程都有机会获得cpu的调度使用权限,也就是说,在一个运行系统中,如果较高优先级的线程没有调用sleep(),又没有I/O阻塞产生,那么较低优先等级的线程只能在较高优先等级的线程运行之后才有机会执行
					
		4)setPriority(): 设置线程的优先等级(按照优先等级的从大到小的顺序依次执行)
static int MAX_PRIORITY	 线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY	 线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY  分配给线程的默认优先级,取值为5。
	使用方法:
YieldDemo ta = new YieldDemo("A");
YieldDemo tb = new YieldDemo("B");
YieldDemo tc = new YieldDemo("C");
ta.setPriority(Thread.MAX_PRIORITY);  // 第一个执行
tb.setPriority(Thread.MIN_PRIORITY);  // 第三个执行
tc.setPriority(Thread.NORM_PRIORITY); // 第二个执行
ta.start();
tb.start();
tc.start();
		注:线程的优先等级的设置只是在一定程度上造成某些现成的执行顺序的概率,但在实际执行过程中并不一定完全按照设定好的优先等级执行 ==> 概率性问题,并非绝对

		5)interrupt() --> 中断当前线程的执行
			线程被中断的方式有两种:自我打断 被其他线程打断
				自我中断 --> 一定会执行成功
				其他线程中断 --> 限制性权限检查,这一过程可能会抛出异常 SecurityException
			如果当前线程处于中断状态(前面调用过interrupt())再调用现成的阻塞方法[wait() join() sleep()],那么当前的中断状态会被清除,同时会抛出异常 InterruptedException

		interrupt()通常被用来让一个线程再无限等待时(例如死锁)能够抛出异常,从而结束线程

		6)wait() --> 释放执行锁,进入等待状态
			 -->①在synchronized修饰的同步代码块或方法里面调用wait() 与  notify/notifyAll()方法
			 -->②当一个线程执行wait()时,会将当前的锁释放,让出cpu,进入等待状态

		当这一线程执行notify()/notifyAll()时,会唤醒当前处于等待对象锁的线程,这一线程会继续往下执行,执行完毕退出锁同步区(synchronized代码块),然后释放锁
		注:执行notify()/notifyAll()方法不会立即释放对象的锁,需要等到线程执行完成之后才会释放   但执行wait()方法就会立即释放对象的锁,然后当前线程就会停留在wait()处

示例
public class WaitNotifyDemo implements Runnable {
    private int number;
    public byte res[];
    public static int count = 5;
    public WaitNotifyDemo(int number, byte res[]){
        this.number = number;
        this.res = res;
    }
    public void run(){
        synchronized (res){
            while(count-- > 0){
                try {
                    res.notify();//唤醒等待res资源的线程,把锁交给线程(该同步锁执行完毕自动释放锁)
                    System.out.println(" "+number);
                    res.wait();//释放CPU控制权,释放res的锁,本线程阻塞,等待被唤醒。
                    System.out.println("--线程"+Thread.currentThread().getName()+"获得锁,wait()后的代码继续运行:"+number);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }//end of while
            return;
        }//synchronized

    }
}

class WaitNotify {
    public static void main(String args[]){
        //以该对象为共享资源
        final byte a[] = {0};
        new Thread(new WaitNotifyDemo((1),a),"1").start();
        new Thread(new WaitNotifyDemo((2),a),"2").start();
    }
}
		流程的理解:
			1. pre线程[得到同步锁] --> notify() -[唤醒]-> wait() --> 释放cpu调度执行权限 释放同步锁 --> 阻塞状态(等待)
			2. current线程[得到同步锁] --> notify() -[唤醒前面等待的线程]-> wait() --> 释放cpu调度执行权限 释放同步锁 ==> 阻塞状态(等待)
			3. pre线程被唤醒 -->  继续执行wait()之后的代码 --> pre线程执行完毕 --> 进入第二轮循环操作[此时同步锁还在 pre线程] --> notify() -[唤醒前面等待的线程]-> wait() ...

		个人理解:
			上述方法类似于"推箱子"游戏,需要后面来的箱子"顶走"前面的箱子
			notify()作用 --> 赋予线程同步锁的使用期限[同步锁内有效]
			wait()作用 --> 释放当前线程的cpu调度执行权限 同步锁 转而进入阻塞状态


		对比wait()和sleep()
		共同点:
			1.调用上述两种方法之后当前线程都会转入阻塞状态 ==> active --> block
			2.处于wait()和sleep()状态的线程都可以通过interrupt()中断当前状态,并立刻抛出异常InterruptedException
		补充说明:
			如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt()如果此刻线程B处在阻塞状态[wait()/sleep() /join()],则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程
			注:需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException

		不同点:
			1.sleep() yield() ---> Thread类的方法
			wait() notify() notifyAll() ---> Object的方法
		
		  2.thread.wait() --> 释放cpu的调度执行权限 释放同步锁
			 object.sleep() ---> 释放cpu的调度执行权限 仍然占有同步锁
		 
		  3.wait() notify() notifyAll() ---> 只能在Synchronized{}中执行
			sleep()  ---> anywhere
		
		 4. sleep() ---> 必须捕获异常
			wait() notify() notifyAll()  ---> 不需要捕获异常 

		线程同步:
			1.同步代码块 ---> 使用Synchronized修饰的代码块
			因为Synchronized关键字修饰的语句块会自动被加上内置锁,从而实现同步
			如: synchronized(object){
					// TODO
				}
public class ThreadDemo implements Runnable {
    private Integer num = 500;

    @Override
    public void run() {
        for (int i = 0; i < 500; i++) {
            synchronized (this) {
                if (num >= 0) {
                    System.out.println(Thread.currentThread().getName() + "抢到了编号为:" + num-- + "的执行权限");
                }
            }
        }
    }
}
	注:同步是一种高开销的操作,会消耗大量资源,如果一定需要使用,尽量减少代码块覆盖的代码范围

	2.使用Synchronized关键字修饰的方法 
		synchronized锁针对的是类的对象  对于同步,要时刻清醒在哪个对象上同步 ==> 关键
		如:public synchronized void save(){
			// TODO
			}

		3.使用重入锁实现线程同步
			ReentrantLock类是可重入 互斥 实现了Lock接口的锁
			与使用synchronized方法和块具有相同的基本行为和语义,并且扩展了能力

			ReentrantLock类的常用方法有:
			ReentrantLock():实例化ReentrantLock
			lock():获得锁
			unlock():释放锁
				注:ReentrantLock()还有一个可以创建公平锁的构造方法,但会造成大量的资源损耗,不推荐使用

		代码示例:
class Bank {
            
    private int account = 10
    //需要声明这个锁
    private Lock lock = new ReentrantLock();
   //这里不再需要synchronized 
   public void save(int money) {
       lock.lock();
       try{
           account += money;
           }finally{
           lock.unlock();
           }              
        }
    }
		关于锁机制的说明:
			锁机制会产生较大的资源开销,无论是上述哪种锁机制,能不使用都不推荐使用
			如果synchronized关键字能够满足需求,就是用synchronized,简化代码
			如果需要实现更高级的功能,就使用ReentrantLock
				使用ReentrantLock需要注意:一定要注意释放锁,否则会出现死锁,通常放在finally{}中释放锁

	 	 线程数据传递:
			1.通过构造器实现数据传递
public class ThreadDemo extends Thread {
    public ThreadDemo(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(super.getName()+"多线程实现业务代码的区域");
    }
}

class ThreadTest{
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo("hudie");
        threadDemo.start();
    }
}
		优点:
			创建对象的同时完成了数据的传递,运行数据完备
		缺点:
			传输的数据比较复杂的情况下,就比较复杂
		解决方案:
			数据量不大的情况 --> 可以使用集合 类等数据结构
			数据量大的情况下 --> 类方法或类变量

	2.通过变量和方法传递数据
			在类中定义一系列属性的过程中完成数据传递
public class ThreadDemo1 extends Thread {
    private String name;
    public void setThreadDemo1(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(super.getName()+"多线程实现业务代码的区域");
    }
}

class ThreadTest1{
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo("hudie");
        threadDemo.start();
    }
}
	3.通过回调函数传递数据
			传递动态数据
class Data {
    public int value = 0;
}

class Work {
    public void process(Data data, Integer[] numbers) {
        for (Integer n : numbers) {
            data.value += n;
        }
    }
}

class MyThread3 extends Thread {
    private Work work;

    public MyThread3(Work work) {
        this.work = work;
    }

    @Override
    public void run() {
        Random random = new Random();
        Data data = new Data();
        int n1 = random.nextInt(1000);
        int n2 = random.nextInt(2000);
        int n3 = random.nextInt(3000);
        // 使用回调函数
        work.process(data, new Integer[]{n1,n2,n3});
        System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+"
                + String.valueOf(n3) + "=" + data.value);
    }

    public static void main(String[] args) {
        Thread thread = new MyThread3(new Work());
        thread.start();
    }
}
	线程池技术补充:
		1.线程池概念:
				线程池可以简单的理解为在程序运行之前,首先创建出一些线程,他们的集合称为线程池.使用线程池可以很好的提高程序的性能,线程池在系统启动是即创建出大量空闲的形成,程序见一个任务传给线程池,线程池就会启动一个线程执行这一任务,任务结束之后,该线程再次返回线程池中变为空闲状态,与其他空闲线程一同等待执行下一个任务

		2.线程池工作机制
			1.在线程池的编程模式下,程序将任务提交给整个线程池,线程池在获得任务后,在内部寻找是否存在空闲线程,如果有,则将任务随机交给某个空闲线程
			2.一个线程同时只能执行一个任务,但可以先同一个线程池提交多个任务
		
		3.线程池的优势
				多线程运行期间,系统不断创建和销毁线程,消耗了大量的内存资源,同时存在过度切换现成的危险,容易导致系统崩溃,多线程的出现有效的解决了这一问题

		4.四种常见的线程池
			ExecutorService是Java提供的用于管理线程池的类
				-- 控制线程数量
				-- 重用线程
			1.Executor.newCacheThreadPool() --> 可缓存线程池  先查看线程池中有没有之前建立的形成,如果有 --> 直接使用  如果没有 --> 创建新的线程加入线程池
public class ThreadPool {
    public static void main(String[] args) {
        // 创建一个可缓存线程池 创建的过程中明确提示使用的是已有的线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    // 打印执行的缓存信息
                    System.out.println("当前线程:" + Thread.currentThread().getName());
                }
            });
        }
    }
}
	线程池为无限大,当执行当前任务时,上一个任务已完成,会复用执行上一个任务的线程,并不是每次都创建线程

			2.Executors.newFixedThreadPool(int num):创建一个可重用的固定个数的线程池,以共享的无界队列方式来运行这些线程
public class ThreadPool1 {

    public static void main(String[] args) {
        // 创建一个可重用固定个数的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    // 打印正在执行的缓存线程信息
                    try {
                        System.out.println("当前线程:" + Thread.currentThread().getName());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}
	3.Executors.newScheduledThreadPool(int num):创建一个定长线程池,支持定时及周期性任务执行
public class ThreadPool2 {

    public static void main(String[] args) {
        // 创建一个定长线程池,支持定时即周期性任务执行 -- 定期执行
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(7);
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟1秒后每3秒执行一次");
            }
        },1,3, TimeUnit.SECONDS);
    }
}
	4.Executors.newSingleThreadExecutor():创建一个单线程化的线程池,只是用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)来执行
public class ThreadPool3 {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("当前线程" + Thread.currentThread().getName() + index);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}
		5.缓冲队列BlockingQueue和自定义线程池ThreadPoolExecutor
			1.BlockingQueue是缓冲队列,内部有两条队列,允许两个线程同时向队列执行任务 :一个存储 一个取出,在保证并发安全的同时,提高了队列的存取效率
			2.常用的几种BlockingQueue
				ArrayBlockingQueue(int num):指定长度的缓冲队列,所含的对象是按照FIFO顺序排序
				LinkedBlockingQueue([int i]):若指定队列大小,则按照指定大小执行,若不指定,由Integer.MAX_ALUE来决定 按照FIFO顺序排序
				PriorityBlockingQueue()或者(int i):类似于LinkedBlockingQueue,但是其所含对象的排序不是FIFO,而是依据对象的自然顺序或者构造函数的Comparator决定
				SynchronizedQueue():特殊的BlockingQueue,对其的操作必须是放和取交替完成

	
		自定义线程池:ThreadPoolExecutor和BlockingQueue连用
public class ThreadPool4 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("当前线程:" + Thread.currentThread().getName());
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class ThreadPoolDemo{
    public static void main(String[] args) {
        // 创建数组型缓冲等待队列
        ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(10);
        // ThreadPoolExecutor:创建自定义线程池,池中保存的线程数为3,允许最大的线程数为6
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 6, 50, TimeUnit.MILLISECONDS, blockingQueue);

        // 创建6个任务
        ThreadPool4 t1 = new ThreadPool4();
        ThreadPool4 t2 = new ThreadPool4();
        ThreadPool4 t3 = new ThreadPool4();
        ThreadPool4 t4 = new ThreadPool4();
        ThreadPool4 t5 = new ThreadPool4();
        ThreadPool4 t6 = new ThreadPool4();

        // 6个任务分别是在6个线程上执行
        threadPoolExecutor.execute(t1);
        threadPoolExecutor.execute(t2);
        threadPoolExecutor.execute(t3);
        threadPoolExecutor.execute(t4);
        threadPoolExecutor.execute(t5);
        threadPoolExecutor.execute(t6);

        // 释放资源
        threadPoolExecutor.shutdown();
    }
}

后记:多线程问题在实际生产中属于举足轻重的问题,建议有兴趣的道友们可以多看看源码加强学习,与君共勉!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值