什么是进程

进程是程序运行时的一个实例,包含了正在运行的程序代码和相关数据等。可以 把进程看做程序的一次运行过程。在操作系统内部,进程为操作系统资源分配最小单位。

进程是系统的软件管理资源,也叫任务,可以在windows任务管理器上看到。

什么是线程

包含在进程中,是进程中的实际执行单位,是操作系统的最小运行单位,独立在cpu上调度执行。一个线程就是一个执行流.一个进程中至少一个线程.

PCB(进程控制块):是操作系统用于管理和跟踪进程的核心数据结构,包含进程ID、指令信息、上下文以及各种标识符等关键信息 .

进程是以链表形式组织的一组PCB,线程是一个PCB;

为什么创建线程

  1.   单核CPU算力遇到瓶颈,需要使用多核CPU提高算力。并发编程可以充分利用CPU资源,所以并发编程成为刚需;

现代计算机通常配备有多核CPU,意味着能够并行执行多个指令流。创建多个线程,可以使不同的线程在不同的核心上并行运行,充分利用CPU加快处理速度。

   2.每次创建进程,都涉及到资源的分配和调度,频繁的创建销毁对系统的开销很大(时间,空间)。在进程中创建线程,让线程共享进程的系统资源(硬盘,内存,网络带宽等),又能相对独立地执行各自的指令序列,省去了频繁申请资源和释放资源的开销;

并发

并行:cpu 的多个核心同时运行,每个核心都可以执行一个线程, 微观时间上同时运行 

并发:一个核心根据时间片不断切换执行线程,宏观时间上同时执行,微观上串行执行

以上两种方式统称并发执行,因为在实际开发中不需要区分。

抢占式执行

当线程数量大于cpu核心数量时,就会抢占进程的cpu资源,在微观上无法满足所有线程的同时执行,此时线程之间会产生竞争,抢占资源的过程会影响效率。操作系统内核的调度器对线程执行的调度,可以近似看成“随机”的过程。

进程和线程的区别

1.进程包含线程,每个进程都至少有一个线程,就是主线程;

2.进程之间不共享系统资源,同一个进程里的线程共享资源(内存 ,硬盘,网络带宽等资源,内存资源就是存储的对象变量等);

3.进程是操作系统中资源分配的最小单位,线程是系统调度执行的最小单位;  

4.一个进程挂了一般不会影响到其他进程执行(称为隔离性),但一个线程挂了,可能会让整个进程里的线程都崩溃,因为所有线程共享进程里的资源 ,如果一个线程把所有资源都占用了,那其他线程也就崩了。

Thread类

   线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且提供了一些API供用户使用
,Java标准库中的Tread类是对操作系统提供的API进行了进一步的抽象和封装,用于多线程编程。Thread类是JVM用来管理线程的一个类 ,每个线程都对应唯一的Thread对象。
线程构造方法

Thread (); //创建 线程对象

Thread (String  name );  //创建并命名 

Thread(Runnable   target);  //用Runnable 对象创建

Thread(Runnable   target,String  name);

创建多线程的方式

1.继承Thread类

1.实现Thread里的抽象方法run

2.创建Thread对象

3.调用start()

public class thread {
 static  class MyThread extends Thread {//自定义内部类继承Thread类,就是为了重写run()
        @Override
        public void run() { //run()方法记录线程要执行的任务
            for (int i = 0; i < 4; i++) {
                System.out.println("t hello");
            }
        }
    }
    public static void main(String[] args) { //每个进程都至少有一个线程,主线程main
        Thread t = new MyThread();//再创建一个线程的对象
        t.start();  //调用start才在进程内部真正创建t线程,线程在内部自动调用run()方法执行任务
                    //执行完run里的任务,t线程结束被销毁
        System.out.println("main  hh"); //打印完这个语句main线程结束
//run()是回调函数,作为参数被传递,被动调用
    }
}

控制台打印结果

2.实现Runnable接口

实现Runnable 接口和继承Thread类都要重写里面的抽象方法run。但这种方法的好处是解耦合,将线程任务的制定和线程的启动过程分开。

Runnable接口代表一个可以由线程执行的任务。它只有一个方法run()。Runnable专注于定义“做什么”,而管理线程的生命周期(如启动、运行、停止等),可以是Thread类也可以交给线程池等。
避免单继承限制:Java不支持多重继承,但允许一个类实现多个接口。如果一个类已经继承了某个类,则无法再继承Thread类。但是,它仍然可以通过实现Runnable接口来定义一个可在线程中执行的任务

public class ThreadRunnable {
   static  class  MyThread implements Runnable{  //自定义内部类实现接口
       @Override
       public void run() {
           System.out.println(" t   hello");
       }
   }
    public static void main(String[] args) {
       //将MyThread对象传参给Thread构造对象
        Thread t = new Thread(new MyThread());
        t.start(); 
        System.out.println("main  xixi");
    }
}
用匿名内部类形式和lambda对上面两种方式的变形
public class nim {
    
  static   Thread t1 =new Thread(){ //匿名内部类继承Thread类,对象类型为Thread类(多态形式)
        @Override
        public void run() {
            System.out.println("nim");
        }
    };

  static Thread t2 = new Thread(new Runnable() {//匿名内部类实现Runnable接口,都是多态
      @Override
      public void run() {
          System.out.println("runnable");
      }
  });
  //lambda
  static Thread tt2 = new Thread(()->{   //对匿名内部类的简化,实现的接口和重写的方法名啥的全省略了,只有()构造函数和方法体
        System.out.println("lambda");
    });
    public static void main(String[] args) {
      t1.start();
      t2.start();
      tt2.start();
        System.out.println("main");

    }
}
3.实现Callable接口

获取结果时使用,任务有返回值,可以抛出受检异常



public class CallableBasic {
    public static void main(String[] args) throws Exception {
        // 创建Callable任务
        Callable<Integer> task = () -> {
            Thread.sleep(1000); 
            return 42;         // 返回计算结果
        };

        // 包装为FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(task);
        
        // 创建线程并启动
        Thread thread = new Thread(futureTask);
        thread.start();
        
        // 获取结果(会阻塞直到计算完成)
        Integer result = futureTask.get();
        System.out.println("计算结果: " + result);
    }
}

成员状态

不同系统命名方式可能不一样 ,java中是这样命名

线程的状态为枚举类型 :
NEW :线程对象有了,定义了任务,但没调用start(),系统底层线程并没有被真正创建
RUNNABLE:就绪的,分为正在CPU上运行和即将被调度的状态
BLOCKED: 进行锁竞争产生 的阻塞
WAITING:死等产生的阻塞; sleep(),join()没有指定时间的话会产生死等
TIMED_WAITING:超时等待产生的阻塞
TERMINATED:线程已经终止,内核中的线程被销毁,但线程对象还在

线程属性

属性的设置要在线程启动之前

属性 获取方法
id getId(); JVM为线程设置,不能手动设置ID,为线程的唯一标识,不同线程不会重复
name  getName(); 线程名字
state getState ();    状态jvm为线程设置,不能手动设置
priority getPriority(); 优先级高的线程更容易被调度到
是否存活    isAlive();   run()方法是否运行结束(不是NEW,TERMINATED状态都是活着的)
是否为后台线程 isDeamon(); 前台线程:自己没结束,进程也不能结束,所有线程都默认为前台线程;        后台线程:前台线程结束了, 进程和里面的后台线程都结束。
线程是否中断

isInterrupted();

interrupted();

在循环或阻塞操作中检查中断状态,确保线程能优雅退出

常用方法

run();//记录线程要执行的任务
start(); //调用这个方法才在系统底层真正创建线程,一个线程对象只能启动一次
join();//等待一个线程结束,a线程在b线程里调用join()方法,b等a
join(long millis);//等待指定时间
 public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
            for (int i = 0; i < 3; i++) {
                try {
                    System.out.println(Thread.currentThread().getName()
                            + ": 我还在⼯作!");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 我结束了!");
        };

        Thread thread1 = new Thread(target, "李四");
        Thread thread2 = new Thread(target, "王五");
      
        thread1.start();
        thread2.start();//同时开启两个线程,抢占式执行
        
        thread2.join();//在main线程里调用thread2.join(),没有指定时间的话,main线程阻塞等待,只有当thread2执行完了,后面的代码才能执行,main线程才能结束
       //这里不影响thread1的运行,因为没有在thread1里面调用thread2.join()
        thread1.join();//调用两个线程的join(),main等待这两个线程之间运行的最大时间

         System.out.println(12);
    //运行结果
李四: 我还在⼯作!
王五: 我还在⼯作!
李四: 我还在⼯作!
王五: 我还在⼯作!
李四: 我还在⼯作!
王五: 我还在⼯作!
李四: 我结束了!
王五: 我结束了!
12  //thread1执行完,main结束阻塞

   
//如果以下顺序 ,thread1和2串行执行
    thread1.start();
    thread1.join();//等thread1执行完才执行main线程后面的代码
    thread2.start();
    thread2.join();

    }

String  getName();//得到线程的名字
setName( String name  );//给线程取个名字,方便调试,知道哪个线程出了问题
static  void   sleep(long  millis)thows InterruptedException;//线程睡眠/阻塞指定时间
static  Thread  currentThread();// 返回当前线程对象的引用,Thread.currentThread()
 public class ThreadDemo {
  public static void main(String[] args) {
  Thread thread = Thread.currentThread();
   System.out.println(thread.getName());//main
   }
 }
isAlive();//指的是系统中的线程(PCB)是否存在,跟Thread对象生命周期不一样,用start()方法,可以判断线程被创建,但是线程是否被销毁,用isAlive()判断

//在多线程编程里 ,可以自定义一个变量记录当前线程是否被中断,但这样维护变量比较麻烦.在Thread的内部有一个Boolean变量作为是否被中断的标记位,默认为false,表示没有被中断,可以替代自定义

static  interrupted (); //Thread类调用,查看中断标志,清除标记,改为false
isInterrupted();  //对象调用 ,查看标记,但不清除标记,就是不改为false
interrupt(); //将标志位改为true,提示线程中断,不是强制性中断

thread 收到通知的方式有两种,分为此时正在阻塞(异常通知)或者没被阻塞(直接中断)

1.如果线程因为调用wait/join/sleep等方法而阻塞挂起,则以InterruptedException异常的形式通知,线程被唤醒,并清除中断标志,恢复为未被中断的状态(阻塞方法的擦除功能),这时捕获异常自定义处理方法(因为不强制中断),可以选择忽略这个异常, 也可以跳出循环结束线程.

2.如果没有sleep()这些阻塞方法,设置标记位为true,不会自动停止线程,需主动检查isInterrupted(),然后自行处理

总结

行为 interrupt() isInterrupted() interrupted()
作用 设置中断标志为 true 检查中断标志(不清除) 检查并清除中断标志
调用方式 实例方法 实例方法 静态方法
阻塞时的响应 抛出 InterruptedException 无特殊行为 无特殊行为
典型用途 通知线程终止 循环条件检查 一次性状态检查
  public static void main(String[] args) throws InterruptedException {
         Thread t1=new Thread(()->{
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
         });
          t1.start();
          t1=null;  //对象没有引用被GC回收,但内核线程还在sleep没被销毁

        Thread t2=new Thread(()->{

        });
        t2.start();
        Thread.sleep(1000);//t2线程瞬间执行完任务,内核的线程和PCB被销毁,但是线程对象要等休眠结束才会被GC回收
    }
public class MyRunnable implements Runnable{ 
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()){  //检查当前线程是否中断
            System.out.println("转帐");
        }

    }
}

public class Interrupted {
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ": 让李四开始转账。");
        thread.start();
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName()
                + ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");
        thread.interrupt();  //修改标准位,通知线程中断
    }
}

 Thread t=new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("转帐");
                try {
                    Thread.sleep(10);  //当线程处于阻塞状态时,会将线程唤醒,抛出中断异常,
                    // sleep()会擦除设置,将标志位恢复成false
                } catch (InterruptedException e) {//捕获异常,处理中断,可以直接break, 
                    //return,终止线程继续,
                    // 也可以忽略这个中断提醒继续执行
                break;
                }
            }
        });
        t.start();
        Thread.sleep(10);
        t.interrupt();

线程安全

这里对安全的简单定义:代码运行结果符合预期,有规律. 在多线程环境下,存在线程安全问题

不安全的原因

 1.线程抢占式执行(罪魁祸首,操作系统内核设计决定,我们不能改变)

2.导致操作非原子性;

3.多个线程对同一个变量/数据 进行修改就会因为操作非原子性导致结果具有随机性;

int count=0;

count++;

要实现count自增,计算机底层要完成三个步骤

第一步将内存中的变量加载到寄存器,第二步变量加一,第三步将变量再加载到内存中存储。

如果此时有两个线程想要让这个变量分别实现3次自增操作,当A线程完成了前两步,还没将count加载到内存时,B线程抢夺cpu资源实现count++,那等A线程将count加载到内存时会将B存的值覆盖,最后的值一定不等于6。

4.内存可见性问题

public class Volatile {
    static  int count=0;
    //static volatile int count=0;
    public static void main(String[] args) {
        Thread t1=new Thread(()->{  
             while (count==0){ //count变量第一次从内存被加载到寄存器后,就不会再次加载
//因为这里的while循环体为空,count的加载的时间速度比count的比较速度慢个几千倍,
//而while里没有比变量加载更耗时的操作,所以系统判断count并没有进行修改,
//后续比较就不再进行变量加载操作,以此加快运行时间。
//当这里有IO/sleep()/wait()的操作时,加载操作就不会被优化,因为这些操作耗费的时间远大于加载变量,那进行这样的优化就没有意义。
             }
            System.out.println("t1线程结束");
        });
        Thread t2=new Thread(()->{
            System.out.println("请输入");
            Scanner in=new Scanner(System.in);
             count=in.nextInt();//当t2 线程对count变量进行修改后,
//t1线程不能察觉到count的变化,所以就陷入while死循环,t1线程无法结束。
//可以给变量加上volatile的关键字,就可以让系统不进行编译优化
        });
        t1.start();
        t2.start();
    }
}

对于大型项目来说,编译器的优化确实可以节省很多时间 ,但是在多线程情况下容易bug.  volatile关键字表示不稳定的, 提醒编译器不要进行优化,确保每次操作变量都重新加载一次,强制读写内存,就能够保证线程之间的内存可见性,速度变慢了,但是数据变准确了。

编译器优化有很多种情况,volatile只是针对变量优化的情况。

5.指令重排序问题

编译期间为了加快运行速度会对一些指令进行重排序,但不会影响运行结果正确性。但在多线程环境下,指令重排序会概率性出 bug。加volatile关键字,不仅能解决内存可见性问题,也能禁止对变量的读写操作指令重排序,保证运行结果的正确。

解决线程不安全
volatile

表示不稳定的,修饰变量,在多线程情况下保证内存可见性,禁止指令重排序。编译器不确定啥时执行了优化,所以针对变量,volatile加上比较保险。

synchornized

表示锁,锁里的代码就只能一个线程执行,保证操作原子性。

1.使用synchornized关键字,给一个对象上锁,本质是修改指定对象的''对象头''。java中可以给任意对象上锁

语法:  synchornized(对象){ }

()括号里的对象仅仅是起标识作用,如果跟其他线程的锁的对象一样就会产生锁竞争/阻塞

  进代码块就已经加锁,出来就相当于解锁,  synchornized是可重入锁,对同一个对象嵌套加多个锁,不会产生死锁

修饰静态方法相当于是对类对象加锁,修饰普通方法相当于对this(当前调用方法的对象)加锁  ,当两个线程竞争同一把锁会产生阻塞等待. 

public class SynchronizedDemo {
 private Object locker = new Object();
 public void method() {
 synchronized (locker) {}
 }
}

2.Java标准库中的线程安全类:

ConCurrentHashMap:    高并发优化的线程安全哈希表

StringBuffer : 线程安全的长度可变字符串,所有方法用synchornized修饰,并发性很差,加锁系统开销大,适用于多线程环境

wait() 和notify()

多线程编程中每个线程是抢占式执行,执行顺序具有随机性,可以用wait和notify方法协调线程之间的执行顺序.

wait()/wait(long time) :让当前线程进入等待状态,可以指定等待时间

notify()/notifyAll()  :唤醒在当前对象上等待的线程/全部线程

注意

1.wait()和notity()都是Object的方法

2.搭配synchornized使用,不然会抛出异常

wait()结束等待

1.其他线程调用该对象的notity()唤醒

2.指定等待时间,超时就自动唤醒

3.其他线程调用该等待线程的interrupted(),wait()会抛出 InterruptedException 异常.

notify()

在synchornized的同步代码块中调用,通知那些可能等待该对象锁的其他线程停止等待,

如果有多个线程等待,则有线程调度器随机挑选出一个呈wait状态的线程,并不存在先来后到;

调用notify()后,当前线程不会立马释放该对象锁,而要等到该执行notify()的线程将程序执行完,退出同步代码块之后才释放对象锁.(现实场景就是一个人在厕所里随机喊外面的一个人,对他说我快上完了了,马上就是你了. 他可能是提前喊的,过一会才出厕所,把厕所让给别人)

notifAll()

唤醒所有等待同一个对象锁的线程,等释放锁后会产生锁竞争

sleep()和wait() 的区别

1.sleep()和wait()都能让线程阻塞一段时间,但sleep让当前线程休眠,wait()用于线程间通信;

2.sleep()是Thread的静态方法,wait()是Object的方法;

3.wait()需要搭配synchornized使用,sleep()不需要;

 static class WaitTask implements Runnable {
        private Object locker;

        public WaitTask(Object locker) {
            this.locker = locker;
        }

        @Override
        public void run() {
            synchronized (locker) {
                while (true) {
                    try {
                        System.out.println("wait 开始");
                        locker.wait();
                        System.out.println("wait 结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    static class NotifyTask implements Runnable {
        private Object locker;

        public NotifyTask(Object locker) {
            this.locker = locker;
        }

        @Override
        public void run() {
            synchronized (locker) {
                System.out.println("notify 开始");
                locker.notify();
                System.out.println("notify 结束");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread(new WaitTask(locker));
        Thread t2 = new Thread(new NotifyTask(locker));
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
死锁

四个条件同时满足产生死锁

(基本特性)

1.锁具有互斥性,对一个对象加锁了,其他线程就不能对这个对象同时加锁。

2.锁具有不可剥夺性,给对象加了锁,得执行完锁里的代码逻辑才能释放锁

(代码结构)

3.嵌套锁不同的对象

4.循环等待

避免死锁

锁的基本特性保证了操作的原子性,不能改变,只能改变代码结构,避免线程嵌套锁不同对象的同时,不让里面的线程循环等待,如果避免不了多个锁嵌套,就得规定加锁的执行顺序来避免相互之间循环等待。

设计模式

单例模式

某个类在进程中只有唯一实例,不允许创建多个实例.也常用饿汉模式和懒汉模式实现单例模式。

饿汉模式:

在程序加载的时候创建实例,实例为静态的。

 class Singleton {
  private   static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }
    private  Singleton(){
        //将构造方法私有化
    }
}
public class  demo{
    public static void main(String[] args) {
        Singleton  s1 =Singleton.getInstance();
        Singleton  s2=Singleton.getInstance();
        System.out.println(s1.equals(s2));//true
    }

}

懒汉模式:

首次使用的时候才创建,如果不使用,就节省一次创建的成本。

//单线程

public class SingletonLazy {  //单线程环境
    private static SingletonLazy instance=null;
    public static SingletonLazy getInstance(){
        if(instance==null){
            instance=new SingletonLazy();
        }
        return instance;
    }
     private SingletonLazy(){};//私有化构造方法
}

饿汉模式一开始对象就扎根创建,可能会把程序的启动拖慢,懒汉模式创建对象是分散的,可能 就不会出现程序启动卡顿。但是懒汉模式在多线程模式下可能会创建多个对象。

饿汉模式的实例是静态的,随着类的加载被创建,主线程main在调用时,对象就已经被创建好了,就不存在问题。但是懒汉模式在多个线程同时调用getInstance()方法时,由于创建对象操作不具有原子性,导致一个线程在if判断后new对象之前,别的线程就把对象创建好了,这样可能创建多个对象,需要给方法加锁。

//多线程

public class SingletonLazy {
    private static volatile SingletonLazy instance=null;//加上volatile更万无一失
    public static SingletonLazy getInstance(){
        if (instance!=null){//这个if,避免重复加锁导致增加不必要开销
            synchronized (instance){
                if (instance == null) {//第一次调用getInstance()创建对象时,多个线程通过第一个if判断后竞争锁,一个线程创建后释放锁,需要再用一个if判断对象是否存在
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){}//私有化构造方法,只能创建一个对象
}

这里的双重if判断,避免了多次加锁带来的开销(第一个if),也能保证只创建一次对象(第二个if);

volatile 关键字避免内存可见性问题,确保每次访问变量时都按照程序代码的顺序直接从内存读取或写入,而不是依赖可能过期的寄存器缓存值.

阻塞队列
阻塞队列是⼀种特殊的队列. 也遵守 "先进先出" 的原则.是⼀种线程安全的数据结构, 并且具有以下特性:
当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
阻塞队列的⼀个典型应用场景就是 "生产者消费者模型". 这是⼀种非常典型的开发模型.
生产者消费者模型
生产者消费者模型通过一个容器来解决生产者和消费者的强耦合问题,生产者和消费者彼此之间不
直接通讯,而通过阻塞队列来进行通讯.生产者生产完数据后不用等消费者处理,而是之间扔给阻塞队
列,消费者不找生产者要数据,而是直接从阻塞队列里取. 阻塞队列就相当于⼀个缓冲区,平衡了生产
者和消费者的处理能力. (削峰填谷)
Java标准库中的阻塞队列
BlockingQueue 是接口,LinkedBlockingQueue 实现类.
put 从队列中放,take从队列中取,带有阻塞特性;
offer, poll,peek等方法不具有阻塞特性.
class BlockingQueue {
        private int[] items = new int[1000];
        private volatile int size;
        private volatile int head;
        private volatile int tail;
        public void put(int value) throws InterruptedException {
            synchronized (this) {
                while (size == items.length) {
                    //不停判断队列是否有空位放,没有就wait阻塞
                    wait();
                }
                items[tail] = value;
                size++;
                tail = (tail + 1) % items.length;
                notifyAll();  //唤醒阻塞的线程
            }
        }
        public int take(int value) throws InterruptedException {
            int ret = 0;
            synchronized (this) {
                while (size == 0) {
                    wait();
                }
                ret = items[head];
                size--;
                head = (head + 1) % items.length;
                notifyAll();
            }
            return ret;
        }
    }

线程池

频繁创建销毁线程,对系统来说也是很大的开销。创建线程需要内核态和用户态进行交互。比较耗时,当创建线程放到线程池中,不着急销毁,当需要的时候就拿出来使用,线程长时间空闲就销毁,变成和用户态的交互,节省系统开销.

Executors 

创建线程池是运用了工程模式

工厂模式的核心思想是:将对象的创建与使用分离,通过专门的工厂类来负责对象的实例化过程.

Executors  创建各种线程池的工厂类,标准库中的线程池

返回值 ExecutorService

submit () ;//注册一个任务到线程池中,通过 ExecutorService.submit ()将注册⼀个任务到线程池中.

Executors 创建线程池的几种方式
newFixedThreadPool  :创建固定线程数的线程池
newCachedThreadPool  : 创建线程数目动态增长的线程池
newSingleThreadExecutor  :创建只包含单个线程的线程池
newScheduledThreadPool :  设定延迟时间后执行命令,或者定期执行命令,是进阶版的Timer
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
ThreadPoolExecutor 
Executor 本质是ThreadPoolExecutor类的封装
ThreadPoolExecutor 提供了更多的可选参数,可以进一步细化线程池的设定.
1.corePoolSize  :核心线程数(正式员⼯, ⼀旦录⽤, 永不辞退)
2.maximumPoolSize :最大线程数,核心+非核心(正式员⼯ + 临时⼯的数⽬,临时⼯: ⼀段时间不⼲活, 就被辞退).

cpu操作密集型:最大核心数不能超过

IO操作密集型:可以创建很多个

3.keepAliveTime  : 非核心线程空闲的最大时间
4.unit : 时间单位
5.workQueue  :任务的阻塞队列 其他线程submit方法提交Runnable对象给队列,后续就完成队列里的任务
6.threadFactory  : 创建线程的工厂,通过不同线程⼯⼚创建出的线程相当于对⼀些属性进⾏了不同的初始化设置.
7.RejectedExecutionHandler  :拒接策略,如果任务量超出公司的负荷了接下来怎么处理.
int corePoolSize = 5;    // 核心线程数
int maxPoolSize = 10;    // 最大线程数
long keepAliveTime = 60; // 空闲线程存活时间(秒)
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100); // 任务队列
ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 线程工厂
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); // 拒绝策略

ExecutorService customPool = new ThreadPoolExecutor(
    corePoolSize,
    maxPoolSize,
    keepAliveTime,
    TimeUnit.SECONDS,
    workQueue,
    threadFactory,
    handler
);
拒接策略
AbortPolicy(): 超过负荷, 直接抛出异常.所有任务都不干了
CallerRunsPolicy(): 调⽤者负责处理多出来的任务.由调用submit的线程执行,当线程submit提交任务到线程池发现满了,就自己执行里面的任务
DiscardOldestPolicy(): 丢弃队列中最⽼的任务.
DiscardPolicy(): 丢弃新来的任务

定时器

定时器是实际开发中常用组件,如网络通信中,对方100ms内没有返回数据,则断开连接尝试重连,如一个Map,希望里面的某个key在3s后自动过期(自动删除),这样的场景需要用到定时器.

标准库中实现定时器的类Timer,核心方法,schedule()
 

Timer timer = new Timer();
timer.schedule(new TimerTask() {
 @Override
 public void run() {
 System.out.println("hello");
 }
}, 3000);

schedule第一个参数指定要执行的任务,第二个参数指定等待的时间,单位毫秒.

TimerTask 为一个抽象类,实现了 Runnable 接口,因此本质上是一个可运行的任务.

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐