Java多线程笔记

Java多线程笔记

1. 多线程概述

多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是在进程的基础上进行的进一步划分。所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程可以同时存在、同时运行,一个进程可能包含了多个同时执行的线程。

1.1进程与线程

  • 进程
    • 正在运行的应用程序:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,即每个进程都有着自己的堆、栈等且是互不共享的。
  • 线程
    • 进程中的一个执行路径(一段程序从执行到结束的整个过程),共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
    • 线程实际上是在进程的基础上进一步划分的,一个进程执行后,里面的若干执行路径又可以划分为若干个线程

1.2 线程调度

  1. 分时调度
  • 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  1. 抢占式调度
  • 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度
  • CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉很快,看上去就是在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

1.3 同步与异步&并发与并行

同步:排队执行,效率低但安全

异步:同时执行,效率高但数据不安全

并发:指两个或多个事件在同一个时间段内发生。

并行:指两个或多个事件在同一时刻发生(同时发生)。

2. 多线程的实现方式

2.1继承Thread类

步骤:

  1. 创建一个自定义类并继承Thread类;

  2. 重写run()方法,创建新的执行任务(通过thread对象的start()方法启动任务,一般不直接调用run()方法)

  3. 创建自定义类对象实例,调用start(),让线程执行

代码如下:

//MyThread.java
public class MyThread extends Thread{
    @Override
    public void run() {			//run()方法就是线程要执行的任务的方法
        for (int i = 0; i < 10; i++) {
            System.out.println("MyThread" + i);
        }
    }
}
//ThreadTest.java
public class ThreadTest {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();				//启动线程任务
        for (int i = 0; i < 5; i++) {
            System.out.println("MainThread" + i);
        }
    }
}

运行结果:

可以看到顺序并不统一,两个线程在交替执行而且各自所占的时间不完全相同,这是线程在抢时间片,谁先抢到谁就执行。

在这里插入图片描述

时序图:

在这里插入图片描述

运行过程中子线程任务中调用的方法都在子线程中运行

在上述代码中。如果Thread对象只需要调用1次,也可以通过使用匿名内部类的方式进行简化:

public class ThreadTest {
   public static void main(String[] args) {
       new Thread(){
           public void run() {
               for (int i = 0; i < 5; i++) {
                   System.out.println("MyRunnable" + i);
               }
           }
       }.start();
       for (int i = 0; i < 5; i++) {
           System.out.println("MainThread" + i);
       }
   }
}

2.2 实现Runnable接口

Runnable接口代码:

public interface Runnable {
	public abstract void run();
}

步骤:

  1. 创建一个自定义类实现Runnable接口,并实现其抽象方法run(),编写线程要执行的任务
  2. 创建自定义类对象实例
  3. 用Thread类创建一个对象实例,并将第二步中的自定义类对象实例作为参数传给其构造函数
  4. 调用Thread类实例的start()方法执行线程。
//MyRunnable.java
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("MyRunnable" + i);
        }
    }
}
//RunnableTest.java
public class RunnableTest {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread t = new Thread(mr);
        t.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("MainRunnable" + i);
        }
    }
}
//运行效果应该跟上面继承Thread类实现多线程效果差不多。

上述代码也可以通过使用匿名内部类的方式进行简化:

public class RunnableTest {
   public static void main(String[] args) {
       new Thread(new Runnable() {
           public void run() {
               for (int i = 0; i < 5; i++) {
                   System.out.println("MyRunnable" + i);
               }
           }
       }).start();
       for (int i = 0; i < 5; i++) {
           System.out.println("MainRunnable" + i);
       }
   }
}

上面两种方式的比较

继承Thread类

  • 优点:直接使用Thread类中的方法,代码简单
  • 弊端:如果已有父类,不可用(Java不可多继承)

实现Runnable接口(更常用):

与继承Threadl类相比具有以下优势:

  • 通过创建任务,给线程分配任务实现多线程,更适合多个线程同时执行相同任务的情况
  • 可以避免单继承带来的局限性(Java允许实现多个接口,但不能继承多个父类)
  • 任务和线程分离,提高程序健壮性
  • 后续学到的线程池技术,它只接收Runnable类型任务,不接收Thread类型线程

Thread类API

  1. 常用构造方法
构造器描述
Thread()分配新的 Thread对象。
Thread(Runnable target)分配新的 Thread对象。
Thread(Runnable target, String name)分配新的 Thread对象。
Thread(String name)分配新的 Thread对象。
  1. 常用其他方法
变量和类型方法描述
longgetId()返回此Thread的标识符。
StringgetName()返回此线程的名称。
intgetPriority()返回此线程的优先级。
voidsetPriority(int newPriority)更改此线程的优先级。
Thread.StategetState()返回此线程的状态。
static ThreadcurrentThread()返回对当前正在执行的线程对象的引用。
voidstart()导致此线程开始执行; Java虚拟机调用此线程的run方法。
static voidsleep(long millis)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
static voidsleep(long millis, int nanos)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。
voidsetDaemon(boolean on)将此线程标记为 daemon线程或用户线程。
  1. 特殊字段:控制线程抢到时间片的几率
变量和类型字段描述
static intMAX_PRIORITY线程可以拥有的最大优先级。
static intMIN_PRIORITY线程可以拥有的最低优先级。
static intNORM_PRIORITY分配给线程的默认优先级。

其他的可以参考Java的API手册

2.3 实现Callable接口

Callable接口代码:

public interface Callable<V> {
	V call() throws Exception;
}

步骤:

  1. 创建一个自定义类实现Callable接口,并实现其抽象方法call(),编写线程要执行的任务

    class XXX implements Callable<T> {
    	@Override
    	public <T> call() throws Exception {
    		return T;
    	}
    }
    
  2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象

    FutureTask<Integer> future = new FutureTask<>(callable);
    
  3. 通过Thread,启动线程

    new Thread(future).start();
    
//MyCallable.java
import java.util.concurrent.Callable;
public class MyCallable<T> implements Callable<T> {
    @Override
    public T call() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println("MyCallable:" + i);
        }
        return null;
    }
}
//CallableTest.java
import java.util.concurrent.FutureTask;
public class CallableTest {
    public static void main(String[] args) {
        MyCallable<String> mc = new MyCallable<> ();
        FutureTask<String> future = new FutureTask<> (mc);
        new Thread(future).start();
        for (int i = 0; i < 5; i++) {
            System.out.println("main" + i);
        }
    }
}

上述代码也可以通过使用匿名内部类的方式进行简化:

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableTest {
   public static void main(String[] args) {
       new Thread(new FutureTask<>(new Callable<String>() {
           @Override
           public String call() throws Exception {
               for (int i = 0; i < 5; i++) {
                   System.out.println("MyCallable:" + i);
               }
               return null;
           }
       })).start();
       for (int i = 0; i < 5; i++) {
           System.out.println("main" + i);
       }
   }
}

Runnable 与 Callable比较

  • 相同点:

    • 都是接口
    • 都可以编写多线程程序
    • 都采用Thread.start()启动线程
  • 不同点

    • Runnable没有返回值;Callable可以返回执行结果
    • Callable接口的call()允许抛出异常;Runnable的run()不能抛出

Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执
行,如果不调用不会阻塞。

3.多线程的应用实例

3.1设置和获取线程名称

currentThread() 可以获取当前正在执行的线程对象

//MyRunnable.java
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());//获取当前线程对象的名称
    }
}
//GetThread.java
public class GetThread {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());//获取当前线程对象的名称
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable(),"answer").start();	//给线程指定一个名称 (方法一)
        Thread t = new Thread(new MyRunnable());
        t.setName("anotherWay");					//给线程指定一个名称 (方法二)
        t.start();
    }
}

执行结果:

在这里插入图片描述

3.2 线程休眠sleep

sleep(long millis)是Thread类的静态方法,类名直接调用即可,单位ms。

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            System.out.print(i + " ");
            Thread.sleep(1000);
        }
    }
}

运行结果:每隔1秒打印一个数字。

在这里插入图片描述

线程阻塞:所有较耗时的操作都能称为阻塞。也叫耗时操作。

3.3线程的中断

一个线程是一个独立的执行路径,它是否应该结束,由其自身决定

因为线程执行过程会有很多资源需要使用或释放,如果干涉它的结束,很可能导致资源没能来得及释放,一直占用,从而产生无法回收的内存垃圾。

Java以前提供stop()方法可以结束线程,现在已经过时(不再使用)。现在出了新的方法,给线程打中断标记interrupt)来控制它的结束。

具体方法就是 调用interrupt()方法,子线程执行时捕获中断异常,并在catch块中,添加处理释放资源的代码。

如下代码所示:main线程执行完后不管子线程是否执行完都中断掉它

//MyRunnable.java
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //线程任务:打印1-10
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {//发现中断标记,进入catch块中,进行释放资源处理
                System.out.println(Thread.currentThread().getName()+":发现中断标记,我自杀了");
                return;	//为了演示,直接结束
            }
        }
    }
}
//InterruptTest.java
public class InterruptTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.setName("myThread");
        t1.start();
        //main线程 打印1-5
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }   
        }
        t1.interrupt(); //给线程t1添加中断标记
    }
}

运行结果:
在这里插入图片描述

3.4 守护线程

线程分为守护线程用户线程

  • 用户线程:当一个进程不包含任何存活的用户线程时,进行结束。
  • 守护线程:守护用户线程,当最后一个用户线程结束时,所有守护线程自动死亡。

直接创建的都是用户线程,

设置线程为守护线程:在启动之前设置 ,语法为:线程对象.setDaemon(true);

//MyRunnable.java
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //线程任务:打印1-10
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;	//为了演示,直接结束
            }
        }
    }
}
//InterruptTest.java
public class InterruptTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.setName("myThread");
        t1.setDaemon(true);//设置t1为守护线程
        t1.start();
        //main线程 打印1-5
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }   
        }
    }
}

运行结果

在这里插入图片描述

3.5 线程安全问题

3.5.1 问题引入

我们先来看个例子:三个窗口(线程)同时卖5张票。

public class Demo1 {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
    static class Ticket implements Runnable{
        private int count = 5;  //票数
        @Override
        public void run() {
            while (count > 0) {
                //卖票
                System.out.println(Thread.currentThread().getName()+"正在卖票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
            }
        }
    }
}

运行结果部分截图:

在这里插入图片描述

我们看到余票出现了负数,显然这是不合理的,这就是线程不安全导致的。出现这种情况的原因:线程争抢,导致线程不安全。 多线程在进行同一卖票任务时,没人干涉,各个窗口疯狂买票,最终导致卖的票超出总票数,余票出现负数。

线程不安全的原因:

当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。

  • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
  • 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

多个线程争抢同一个数据,使得数据在判断和使用时出现不一致的情况。那如何解决呢?

解决方法

保证一段数据同时只能被一个线程使用(排队使用),也就是线程同步,给线程加锁(synchronized)

我们有以下三种方法解决线程不安全的问题:同步代码块、同步方法、显示锁

3.5.2 同步代码块

使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块

多个同步代码块如果使用相同的锁对象, 那么他们就是同步的

语法格式:synchronized(锁对象) {}

任何对象都可以作为锁对象存在。

还以上面卖票的代码为例,给卖票的线程加锁

在这里插入图片描述

3.5.3 同步方法

以方法为单位进行加锁。把synchronized关键字修饰在方法中。

还以上面卖票的代码为例,写一个synchronized修饰的方法sale()执行卖票任务,

在这里插入图片描述

3.5.4 显式锁

以上方法中,同步代码块和同步代码都是隐式锁

Lock l = new ReentrantLock():自己创建一把锁

lock():加锁 unlock():解锁

还以上面卖票的代码为例
在这里插入图片描述

** 显式锁和隐式锁的区别:**

区别synchronizedlock
原始构成Java关键字,由JVM维护,是JVM层面的锁JDK1.5之后的类,使用lock是在调用API,是API层面的锁
使用方式隐式锁,不需要手动获取和释放锁,只需要写synchronized,不用进行其他操作显式锁,需要手动获取和释放锁,如果没有释放锁,可能会出现死锁
等待中断不会中断,除非抛出异常或正常运行完成可以中断,1:调用设置超时方法tryLock(long timeout ,timeUnit unit);2:调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
加锁公平非公平锁可以是公平锁也可以是非公平锁,默认是非公平锁。可以在其构造方法传入Boolean值,true公平锁,false非公平锁
绑定多个条件没有。不能精确唤醒线程,要么随机唤醒一个线程,要么唤醒所有等待线程用来实现分组唤醒需要唤醒的线程,可以精确唤醒线程
性能JDK1.5时性能较低,JDK1.6时性能优化,与lock相较无异JDK1.5时性能更高,JDK1.6时synchronized优化赶上lock
加锁方式线程获取独占锁(CPU悲观锁机制),只能依靠阻塞等待线程释放锁。在CPU转换线程阻塞时会引起线程上下文切换,当竞争锁的线程过多时,会引起CPU频繁上下文切换导致效率低下使用乐观锁机制(CAS操作 Computer and Swap),假设不会发生冲突,一旦发生冲突失败就重试,直到成功为止。

公平锁:先来先得,排队执行

非公平锁:抢占式的,谁抢到是谁的

更多关于线程安全的问题可以看下面这篇某大佬的文章

如果你这样回答“什么是线程安全”,面试官都会对你刮目相看(建议珍藏)

3.6 线程死锁

3.6.1 概述

当两个或两个以上的线程在执行过程中,因为争夺资源而造成的一种相互等待的状态,由于存在一种环路的锁依赖关系而永远地等待下去,如果没有外部干涉,他们将永远等待下去,此时的这个状态称之为死锁。

多个线程相互占用对方的资源的锁,而又相互等对方释放锁,此时若无外力干预,这些线程则一直处于阻塞的假死状态,形成死锁。

举个例子,如下图所示,在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么这两个线程将永远地等待下去,这种情况就是死锁形式。

在这里插入图片描述

死锁产生的条件:

  • 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用完释放。
  • 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
  • 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
  • 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{A,B,C,···,Z} 中的A正在等待一个B占用的资源;B正在等待C占用的资源,……,Z正在等待已被A占用的资源。
3.6.2 如何避免死锁
  1. 按顺序加锁:如果每个线程都按同一个的加锁顺序这样就不会出现死锁。

  2. 给锁加时限:每个线程获取锁的时候加上个时限,如果超过某个时间就放弃锁。

  3. 死锁检测:按线程间获取锁的关系检测线程间是否发生死锁,如果发生死锁就执行一定的策略,如终断线程或回滚操作等。

更多关于线程死锁的问题可以看下面这篇某大佬的文章,以上内容也是来自这篇文章:

多线程 死锁详解

3.7 多线程通信

Object方法中提供了一些线程间相互通信的方法

变量和类型方法描述
voidnotify()唤醒正在此对象监视器上等待的单个线程。
voidnotifyAll()唤醒等待此对象监视器的所有线程。
voidwait()导致当前线程等待它被唤醒,通常是 通知或 中断 。
voidwait(long timeoutMillis)导致当前线程等待它被唤醒,通常是 通知或 中断 ,或者直到经过一定量的实时。
voidwait(long timeoutMillis, int nanos)导致当前线程等待它被唤醒,通常是 通知或 中断 ,或者直到经过一定量的实时。

什么时候需要通信:

多个线程并发执行时, 在默认情况下CPU是随机切换线程的,如果我们希望他们有规律的执行, 就可以使用通信。

3.7.1生产者与消费者

看下面代码,有Cooker类,Waiter类,Food类

厨师cooker为生产者线程,服务员waiter为消费者线程,食物food为生产与消费的物品;

假设目前只有一个厨师,一个服务员,一个盘子。理想状态是:厨师生产一份饭菜,服务员端走一份,且饭菜的属性不能发生错乱;

厨师可以制作两种口味的饭菜,制作100次;

服务员可以端走饭菜100次;

public class Demo {
    public static void main(String[] args) {
        Food f = new Food();
        new Cooker(f).start();
        new Waiter(f).start();
    }
    static class Cooker extends Thread{		//生产者线程
        private Food f;
        public Cooker(Food f){
            this.f = f;
        }
        public void run() {
            //生产100个菜
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0){
                    f.setNameAndTaste("菜1","味道1");
                } else {
                    f.setNameAndTaste("菜2","味道2");
                }
            }
        }
    }
    static class Waiter extends Thread {  //消费者线程
        private Food f;
        public Waiter(Food f){			  
            this.f = f;
        }
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }
    static class Food {
        private String name;
        private String taste;
        public void setNameAndTaste(String name,String taste){		//生产
            this.name = name;			//先设定名称
            try {
                Thread.sleep(100);		//为使线效果明显,中间休眠一段时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.taste = taste;			//休眠后设定味道
        }
        public void get(){ //消费
            System.out.println("服务员端走的菜名称是:" + name + ",味道:" + taste);
        }
    }
}

运行结果

在这里插入图片描述

原因:我们在设定菜名和味道的setNameAndTaste方法中,先设定名称,然后休眠一段时间,再设定的味道,中间休眠的那段时间很可能发生时间片丢失,使得菜属性产生混乱。

解决方式一

为了防止在生产过程中setNameAndTaste出现时间片切换,可以用synchronized修饰此方法;

public synchronized void setNameAndTaste(String name,String taste){
    //...
}
public synchronized void get(){  // 消费
	//...
}

运行结果

在这里插入图片描述

可以看出,依然不符合实际情况,这是因为synchronized只是确保了方法内部不会发生线程切换,但并不能保证生产一个消费一个的逻辑关系

解决方式二

在解决方案一的基础上,进行线程之间的通信

厨师做完饭后喊醒服务员,自己睡着。服务员送完饭后喊醒厨师,自己睡着;将Food类左如下修改

在这里插入图片描述
运行结果,做一道菜,端走一道。

在这里插入图片描述

4. 线程的六种状态

Enum Thread.State描述了六种线程的状态,如下表所示

Enum Constant描述
BLOCKED线程的线程状态被阻塞等待监视器锁定。(阻塞)
NEW尚未启动的线程的线程状态。(创建)
RUNNABLE可运行线程的线程状态。(就绪和运行)
TERMINATED终止线程的线程状态。(消亡)
TIMED_WAITING具有指定等待时间的等待线程的线程状态。(有限期等待)
WAITING等待线程的线程状态。(无限期等待)

5. 线程池Executors

普通线程的执行流程:

创建线程 → 创建任务 → 执行任务 → 关闭线程

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间。 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建和销毁线程对象的操作,节省了大量的时间和资源。

线程池的好处

  • 降低资源消耗。
  • 提高响应速度。
  • 提高线程的可管理性。

Java中有四种线程池(ExecutorService):缓存线程池、定长线程池、单线程线程池、周期性任务定长线程池

5.1 缓存线程池

长度无限制

执行流程:

  1. 判断线程池是否存在空闲线程

  2. 存在则使用

  3. 不存在,则创建线程 并放入线程池, 然后使用

ExecutorService service = Executors.newCachedThreadPool(); //获取缓存线程池对象
//向线程池中 加入 新的任务
service.execute(new Runnable() {
	@Override
	public void run() {
		//线程任务代码
	}
});

5.2 定长线程池

长度是指定的数值

步骤:

  1. 判断线程池是否存在空闲线程
  2. 存在则使用
  3. 不存在空闲线程,线程池未满的情况下,则创建线程 并放入线程池, 然后使用
  4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
	public void run() {
		//线程任务代码
	}
});

5.3 单线程线程池

步骤:

  1. 判断线程池的那个线程是否空闲
  2. 空闲则使用
  3. 不空闲则等待池中的单个线程空闲后使用
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
	public void run() {
        //线程任务代码
    }
});

5.4 周期性任务定长线程池

步骤:

  1. 判断线程池是否存在空闲线程
  2. 存在则使用
  3. 不存在空闲线程,且线程池未满的情况下,则创建线程,并放入线程池后使用
  4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

周期性任务执行时:定时执行, 当某个时机触发时, 自动执行某任务

ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字   5 
* 参数3. 时长数字的单位 TimeUnit.SECONDS
*/
service.schedule(new Runnable() {
	public void run() {
		//线程任务代码
	}
},5,TimeUnit.SECONDS);
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)	5
* 参数3. 周期时长(每次执行的间隔时间) 2
* 参数4. 时长数字的单位  TimeUnit.SECONDS
*/
service.scheduleAtFixedRate(new Runnable() {
	public void run() {
		//线程任务代码
	}
},5,2,TimeUnit.SECONDS);
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AnswerCoder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值