多线程、Thread类、线程安全、线程同步、线程池

1、多线程创建

方式一:继承Thread类

定义一个子类 MyThread 继承线程类 java.lang.Thread ,重写 run() 方法
创建 MyThread 类的对象
调用线程对象的 start() 方法启动线程(启动后还是执行 run 方法的)
优缺点:
优点:编码简单
缺点:线程类已经继承 Thread ,无法继承其他类,不利于扩展。
/**
   目标:多线程的创建方式一:继承Thread类实现。
 */
public class ThreadDemo1 {
    public static void main(String[] args) {
        // 3、new一个新线程对象
        Thread t = new MyThread();
        // 4、调用start方法启动线程(执行的还是run方法)
        t.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程执行输出:" + i);
        }

    }
}

/**
   1、定义一个线程类继承Thread类
 */
class MyThread extends Thread{
    /**
       2、重写run方法,里面是定义线程以后要干啥
     */
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程执行输出:" + i);
        }
    }
}

实现方案二:实现Runnable接口

定义一个线程任务类 MyRunnable 实现 Runnable 接口,重写 run() 方法
创建 MyRunnable 任务对象
MyRunnable 任务对象交给 Thread 处理。
调用线程对象的 start() 方法启动线程

Thread的构造器

构造器

说明

public Thread(String name)

可以为当前线程指定名称

public Thread(Runnable target)

封装Runnable对象成为线程对象

public Thread(Runnable target String name )

封装Runnable对象成为线程对象,并指定线程名称

优缺点:

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。
/**
   目标:学会线程的创建方式二,理解它的优缺点。
 */
public class ThreadDemo2 {
    public static void main(String[] args) {
        // 3、创建一个任务对象
        Runnable target = new MyRunnable();
        // 4、把任务对象交给Thread处理
        Thread t = new Thread(target);
        // Thread t = new Thread(target, "1号");
        // 5、启动线程
        t.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

/**
   1、定义一个线程任务类 实现Runnable接口
 */
class MyRunnable  implements Runnable {
    /**
       2、重写run方法,定义线程的执行任务的
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程执行输出:" + i);
        }
    }
}

实现方案二:实现Runnable接口(匿名内部类形式)

可以创建 Runnable 的匿名内部类对象。
交给 Thread 处理。
调用线程对象的 start() 启动线程。
/**
   目标:学会线程的创建方式二(匿名内部类方式实现,语法形式)
 */
public class ThreadDemo2Other {
    public static void main(String[] args) {
        Runnable target = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程1执行输出:" + i);
                }
            }
        };
        Thread t = new Thread(target);
        t.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程2执行输出:" + i);
                }
            }
        }).start();

        new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程3执行输出:" + i);
            }
        }).start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

实现方案三:利用CallableFutureTask接口实现。

、得到任务对象
1. 定义类实现 Callable 接口,重写 call 方法,封装要做的事情。
2. FutureTask Callable 对象封装成线程任务对象。
、把线程任务对象交给 Thread 处理。
、调用 Thread start 方法启动线程,执行任务
、线程执行完毕后、通过 FutureTask get 方法去获取任务执行的结果。
FutureTask API

方法名称

说明

public FutureTask<>(Callable call)

Callable对象封装成FutureTask对象。

public V get() throws Exception

获取线程执行call方法返回的结果。

方式三优缺点:

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
   目标:学会线程的创建方式三:实现Callable接口,结合FutureTask完成。
 */
public class ThreadDemo3 {
    public static void main(String[] args) {
        // 3、创建Callable任务对象
        Callable<String> call = new MyCallable(100);
        // 4、把Callable任务对象 交给 FutureTask 对象
        //  FutureTask对象的作用1: 是Runnable的对象(实现了Runnable接口),可以交给Thread了
        //  FutureTask对象的作用2: 可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果
        FutureTask<String> f1 = new FutureTask<>(call);
        // 5、交给线程处理
        Thread t1 = new Thread(f1);
        // 6、启动线程
        t1.start();


        Callable<String> call2 = new MyCallable(200);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
            // 如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果。
            String rs1 = f1.get();
            System.out.println("第一个结果:" + rs1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            // 如果f2任务没有执行完毕,这里的代码会等待,直到线程2跑完才提取结果。
            String rs2 = f2.get();
            System.out.println("第二个结果:" + rs2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/**
    1、定义一个任务类 实现Callable接口  应该申明线程任务执行完毕后的结果的数据类型
 */
class MyCallable implements Callable<String>{
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }

    /**
       2、重写call方法(任务方法)
     */
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n ; i++) {
            sum += i;
        }
        return "子线程执行的结果是:" + sum;
    }
}

方式

优点

缺点

继承Thread

编程比较简单,可以直接使用Thread类中的方法

扩展性较差,不能再继承其他的类,不能返回线程执行的结果

实现Runnable接口

扩展性强,实现该接口的同时还可以继承其他的类。

编程相对复杂,不能返回线程执行的结果

实现Callable接口

扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果

编程相对复杂

2、Thread的常用方法

Thread常用API说明

Thread 常用方法:获取线程名称 getName () 、设置名称 setName () 、获取当前线程对象 currentThread ()
至于 Thread 类提供的诸如 yield join interrupt 、不推荐的方法 stop 、守护线程 、线程优先级等线程的控制方法,在开发中很少使用,这些 方法 会在高级篇以及后续需要用到的时候再为大家讲解。

Thread获取和设置线程名称

方法名称

说明

String getName​()

获取当前线程的名称,默认线程名称是Thread-索引

void setName​(String name)

将此线程的名称更改为指定的名称,通过构造器也可以设置线程名称

Thread的构造器

方法名称

说明

public Thread(String name)

可以为当前线程指定名称

public Thread(Runnable target)

封装Runnable对象成为线程对象

public Thread(Runnable target String name )

封装Runnable对象成为线程对象,并指定线程名称

Thread类获得当前线程的对象

方法名称

说明

public static Thread currentThread()

返回对当前正在执行的线程对象的引用

1 、此方法是 Thread 类的静态方法,可以直接使用 Thread 类调用。
2 、这个方法是在哪个线程执行中调用的,就会得到哪个线程对象。
public class MyThread extends Thread{
    public MyThread() {
    }

    public MyThread(String name) {
        // 为当前线程对象设置名称,送给父类的有参数构造器初始化名称
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println( Thread.currentThread().getName() + "输出:" + i);
        }
    }
}
/**
    目标:线程的API
 */
public class ThreadDemo01 {
    // main方法是由主线程负责调度的
    public static void main(String[] args) {
        Thread t1 = new MyThread("1号");
        // t1.setName("1号");
        t1.start();
        System.out.println(t1.getName());

        Thread t2 = new MyThread("2号");
        // t2.setName("2号");
        t2.start();
        System.out.println(t2.getName());

        // 哪个线程执行它,它就得到哪个线程对象(当前线程对象)
        // 主线程的名称就叫main
        Thread m = Thread.currentThread();
        System.out.println(m.getName());
        m.setName("最牛的线程");

        for (int i = 0; i < 5; i++) {
            System.out.println( m.getName() + "输出:" + i);
        }
    }
}

Thread类的线程休眠方法

方法名称

说明

public static void sleep(long time)

让当前线程休眠指定的时间后再继续执行,单位为毫秒。

/**
    目标:线程的API
 */
public class ThreadDemo02 {
    // main方法是由主线程负责调度的
    public static void main(String[] args) throws Exception {
        for (int i = 1; i <= 5; i++) {
            System.out.println("输出:" + i);
            if(i == 3){
                // 让当前线程进入休眠状态
                // 段子:项目经理让我加上这行代码,如果用户愿意交钱,我就注释掉。
                Thread.sleep(3000);
            }
        }
    }
}
Thread 常用方法、构造器

方法名称

说明

String getName​()

获取当前线程的名称,默认线程名称是Thread-索引

void setName​(String name)

设置线程名称

public static Thread currentThread()

返回对当前正在执行的线程对象的引用

public static void sleep(long time)

让线程休眠指定的时间,单位为毫秒。

public void run()

线程任务方法

public void start()

线程启动方法

Thread常用方法、构造器

构造器

说明

public Thread(String name)

可以为当前线程指定名称

public Thread(Runnable target)

Runnable对象交给线程对象

public Thread(Runnable target String name )

Runnable对象交给线程对象,并指定线程名称

3、线程安全

多个线程同时操作同一个共享资源 的时候可能会出现 业务 安全问题 ,称为线程安全问题。
取钱模型演示:

需求:

小明和小红是一对夫妻,他们有一个共同的账户,余额是 10 万元,模拟 2 人同时去取钱 10 万。

分析:

①:需要提供一个账户类,创建一个账户对象代表2个人的共享账户。

②:需要定义一个线程类,线程类可以处理账户对象。

③:创建2个线程对象,传入同一个账户对象。

④:启动2个线程,去同一个账户对象中取钱10万。

public class Account {
    private String cardId;
    private double money; // 账户的余额

    public Account(){

    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    /**
       小明 小红
     */
    public void drawMoney(double money) {
        // 0、先获取是谁来取钱,线程的名字就是人名
        String name = Thread.currentThread().getName();
        // 1、判断账户是否够钱
        if(this.money >= money){
            // 2、取钱
            System.out.println(name + "来取钱成功,吐出:" + money);
            // 3、更新余额
            this.money -= money;
            System.out.println(name + "取钱后剩余:" + this.money);
        }else {
            // 4、余额不足
            System.out.println(name +"来取钱,余额不足!");
        }

    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

}
/**
   取钱的线程类
 */
public class DrawThread extends Thread {
    // 接收处理的账户对象
    private Account acc;
    public DrawThread(Account acc,String name){
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
        // 小明 小红:取钱
        acc.drawMoney(100000);
    }
}
/**
    需求:模拟取钱案例。
 */
public class ThreadDemo {
    public static void main(String[] args) {
        // 1、定义线程类,创建一个共享的账户对象
        Account acc = new Account("ICBC-111", 100000);

        // 2、创建2个线程对象,代表小明和小红同时进来了。
        new DrawThread(acc, "小明").start();
        new DrawThread(acc, "小红").start();
    }
}

4、线程同步

线程同步的核心思想

加锁把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。

同步代码块

作用 :把出现线程安全问题的核心代码给上锁
原理 每次只能一个线程进入 执行完毕后自动解锁,其他线程才可以进来执行。

synchronized(同步锁对象) {
  操作共享资源的代码(核心代码)
}

/**
    账户类:余额,卡号
 */
public class Account {
    private String cardId;
    private double money; // 余额 关键信息

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

//    // 100个线程人
//    public static void run(){
//        synchronized (Account.class){
//
//        }
//    }


    /**
      小明 小红
     */
    public void drawMoney(double money) {
        // 1、拿到是谁来取钱
        String name = Thread.currentThread().getName();
        // 同步代码块
        // 小明 小红
        // this == acc 共享账户
        synchronized (this) {
            // 2、判断余额是否足够
            if(this.money >= money){
                // 钱够了
                System.out.println(name+"来取钱,吐出:" + money);
                // 更新余额
                this.money -= money;
                System.out.println(name+"取钱后,余额剩余:" + this.money);
            }else{
                // 3、余额不足
                System.out.println(name+"来取钱,余额不足!");
            }
        }
    }
}

锁对象的规范要求

规范上: 建议使用共享资源作为锁对象。
对于实例方法建议使用 this 作为锁对象。
对于静态方法建议使用 字节码(类名 .class 对象作为锁对象。

同步方法

作用 :把出现线程安全问题的核心 方法 给上锁
原理 每次只能一个线程进入 执行完毕以后自动解锁,其他线程才可以进来执行。

修饰符 synchronized 返回值类型 方法名称(形参列表) {
  操作共享资源的代码
}

同步方法底层原理

同步方法其实底层也是有隐式锁对象的 ,只是锁的范围是整个方法代码。
如果方法是实例方法:同步方法默认用 this 作为的锁对象。但是代码要高度面向对象!
如果方法是静态方法:同步方法默认用 类名.class 作为的锁对象。
/**
    账户类:余额,卡号
 */
public class Account {
    private String cardId;
    private double money; // 余额 关键信息

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    /**
      小明 小红
       this == acc
     */
    public synchronized void drawMoney(double money) {
        // 1、拿到是谁来取钱
        String name = Thread.currentThread().getName();
        // 2、判断余额是否足够
        // 小明  小红
        if(this.money >= money){
            // 钱够了
            System.out.println(name+"来取钱,吐出:" + money);
            // 更新余额
            this.money -= money;
            System.out.println(name+"取钱后,余额剩余:" + this.money);
        }else{
            // 3、余额不足
            System.out.println(name+"来取钱,余额不足!");
        }
    }
}

Lock

为了更清晰的表达如何加锁和释放锁, JDK5 以后提供了一个新的锁对象 Lock ,更加灵活、方便。
Lock实现提供比使用 synchronized 方法和语句可以获得更广泛的锁定操作。
Lock是接口不能直接实例化,这里采用它的实现类 ReentrantLock 来构建 Lock 锁对象。

方法名称

说明

public ReentrantLock​()

获得Lock锁的实现类对象

LockAPI

方法名称

说明

void lock()

获得锁

void unlock()

释放锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
    账户类:余额 , 卡号。
 */
public class Account {
    private String cardId;
    private double money; // 余额 关键信息
    // final修饰后:锁对象是唯一和不可替换的,非常专业
    private final Lock lock = new ReentrantLock();

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    /**
      小明 小红
     */
    public void drawMoney(double money) {
        // 1、拿到是谁来取钱
        String name = Thread.currentThread().getName();
        // 2、判断余额是否足够
        // 小明  小红
        lock.lock(); // 上锁
        try {
            if(this.money >= money){
                // 钱够了
                System.out.println(name+"来取钱,吐出:" + money);
                // 更新余额
                this.money -= money;
                System.out.println(name+"取钱后,余额剩余:" + this.money);
            }else{
                // 3、余额不足
                System.out.println(name+"来取钱,余额不足!");
            }
        } finally {
            lock.unlock(); // 解锁
        }

    }
}

5、线程通信

线程通信案例模拟

模拟客服系统,系统可以不断的接入电话 和 分发给客服。
线程通信的前提 :线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全。

Object类的等待和唤醒方法:

方法名称

说明

void wait​()

让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法

void notify​()

唤醒正在等待的单个线程

void notifyAll​()

唤醒正在等待的所有线程

上述方法应该使用当前同步锁对象进行调用。
/**
   呼叫系统。
 */
public class CallSystem {
    // 定义一个变量记录当前呼入进来的电话。
    public static int number = 0; // 最多只接听一个。

    /* 接入电话
     */
    public synchronized static void call() {
        try {
            number++;
            System.out.println("成功接入一个用户,等待分发~~~~");

            // 唤醒别人 : 1个
            CallSystem.class.notify();
            // 让当前线程对象进入等待状态。
            CallSystem.class.wait();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
       分发电话
     */
    public synchronized static void receive() {
        try {
            String name = Thread.currentThread().getName();
            if(number == 1){
                System.out.println(name + "此电话已经分发给客服并接听完毕了~~~~~");
                number--;
                // 唤醒别人 : 1个
                CallSystem.class.notify();
                CallSystem.class.wait(); // 让当前线程等待
            }else {
                // 唤醒别人 : 1个
                CallSystem.class.notify();
                CallSystem.class.wait(); // 让当前线程等待
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class CallThread extends Thread{
    @Override
    public void run() {
        // 不断的打入电话
        while (true){
            CallSystem.call();
        }
    }
}
/**
   接电话线程类
 */
public class ReceiveThread extends Thread{
    @Override
    public void run() {
        // 1号  2号
        while (true){
            CallSystem.receive();
        }
    }
}
public class TestDemo {
    public static void main(String[] args) {
        // 1、生产者线程:负责不断接收打进来的电话
        CallThread call = new CallThread();
        call.start();

        // 2、消费者线程:客服,每个客服每次接听一个电话
        ReceiveThread r1 = new ReceiveThread();
        r1.start();
    }
}

6、线程池

JDK 5.0 起提供了代表线程池的接口: ExecutorService
得到线程池对象
方式一:使用 ExecutorService 的实现类 ThreadPoolExecutor 自创建一个线程池对象
方式二:使用Executors (线程池的工具类)调用方法返回不同特点的线程池对象

ThreadPoolExecutor构造器的参数说明

public ThreadPoolExecutor(int corePoolSize,
                         
int maximumPoolSize,
                         
long keepAliveTime,
                         
TimeUnit unit,
                         
BlockingQueue<Runnable> workQueue,
                         
ThreadFactory threadFactory,
                         
RejectedExecutionHandler handler)

参数一:指定线程池的线程数量(核心线程): corePoolSize      不能小于0

参数二:指定线程池可支持的最大线程数: maximumPoolSize    最大数量 >= 核心线程数量

参数三:指定临时线程的最大存活时间: keepAliveTime              不能小于0

参数四:指定存活时间的单位(秒、分、时、天) unit                   时间单位

参数五:指定任务队列: workQueue                                             不为null

参数六:指定用哪个线程工厂创建线程: threadFactory                不为null

参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler        不为null

线程池常见面试题

临时线程什么时候创建啊?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

什么时候会开始拒绝任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。

线程池处理Runnable任务

ThreadPoolExecutor创建线程池对象示例

ExecutorService pools = new ThreadPoolExecutor(3, 5
       
, 8 , TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),
           
Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy());

ExecutorService的常用方法

方法名称

说明

void execute(Runnable command)

执行任务/命令,没有返回值,一般用来执行 Runnable 任务

Future<T> submit(Callable<T> task)

执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务

void shutdown()

等任务执行完毕后关闭线程池

List<RunnableshutdownNow()

立刻关闭,停止正在执行的任务,并返回队列中未执行的任务

新任务拒绝策略

策略

详解

ThreadPoolExecutor.AbortPolicy

丢弃任务并抛出RejectedExecutionException异常。是默认的策略

ThreadPoolExecutor.DiscardPolicy:

丢弃任务,但是不抛出异常 这是不推荐的做法

ThreadPoolExecutor.DiscardOldestPolicy

抛弃队列中等待最久的任务 然后把当前任务加入队列中

ThreadPoolExecutor.CallerRunsPolicy

由主线程负责调用任务的run()方法从而绕过线程池直接执行

import java.util.concurrent.*;

/**
    目标:自定义一个线程池对象,并测试其特性。
 */
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        // 1、创建线程池对象
        /**
         public ThreadPoolExecutor(int corePoolSize,
                                 int maximumPoolSize,
                                 long keepAliveTime,
                                 TimeUnit unit,
                                 BlockingQueue<Runnable> workQueue,
                                 ThreadFactory threadFactory,
                                 RejectedExecutionHandler handler)
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
                6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
               new ThreadPoolExecutor.AbortPolicy() );

        // 2、给任务线程池处理。
        Runnable target = new MyRunnable();
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        // 创建临时线程
        pool.execute(target);
        pool.execute(target);
//        // 不创建,拒绝策略被触发!!!
//        pool.execute(target);

        // 关闭线程池(开发中一般不会使用)。
        // pool.shutdownNow(); // 立即关闭,即使任务没有完成,会丢失任务的!
        pool.shutdown(); // 会等待全部任务执行完毕之后再关闭(建议使用的)
    }
}
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld ==> "  + i);
        }
        try {
            System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,线程进入休眠了~~~");
            Thread.sleep(10000000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

线程池处理Callable任务

ExecutorService的常用方法

方法名称

说明

void execute(Runnable command)

执行任务/命令,没有返回值,一般用来执行 Runnable 任务

Future<T> submit(Callable<T> task)

执行Callable任务,返回未来任务对象获取线程结果

void shutdown()

等任务执行完毕后关闭线程池

List<RunnableshutdownNow()

立刻关闭,停止正在执行的任务,并返回队列中未执行的任务

import java.util.concurrent.Callable;

/**
    1、定义一个任务类 实现Callable接口  应该申明线程任务执行完毕后的结果的数据类型
 */
public class MyCallable implements Callable<String>{
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }

    /**
       2、重写call方法(任务方法)
     */
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n ; i++) {
            sum += i;
        }
        return Thread.currentThread().getName()
                + "执行 1-" + n+ "的和,结果是:" + sum;
    }
}
import java.util.concurrent.*;

/**
    目标:自定义一个线程池对象,并测试其特性。
 */
public class ThreadPoolDemo2 {
    public static void main(String[] args) throws Exception {
        // 1、创建线程池对象
        /**
         public ThreadPoolExecutor(int corePoolSize,
                                 int maximumPoolSize,
                                 long keepAliveTime,
                                 TimeUnit unit,
                                 BlockingQueue<Runnable> workQueue,
                                 ThreadFactory threadFactory,
                                 RejectedExecutionHandler handler)
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
                6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
               new ThreadPoolExecutor.AbortPolicy() );

        // 2、给任务线程池处理。
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));
        Future<String> f5 = pool.submit(new MyCallable(500));

//        String rs = f1.get();
//        System.out.println(rs);

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
        System.out.println(f5.get());
    }
}

Executors得到线程池对象的常用方法

Executors :线程池的工具类通过调用方法返回不同类型的线程池对象。

方法名称

说明

public static ExecutorService newCachedThreadPool()

线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。

public static ExecutorService newFixedThreadPool​(int nThreads)

创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。

public static ExecutorService newSingleThreadExecutor ()

创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。

public static ScheduledExecutorService newScheduledThreadPool​(int corePoolSize)

创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的。

import java.util.concurrent.*;

/**
    目标:使用Executors的工具方法直接得到一个线程池对象。
 */
public class ThreadPoolDemo3 {
    public static void main(String[] args) throws Exception {
        // 1、创建固定线程数据的线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);

        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable()); // 已经没有多余线程了
    }
}

Executors使用可能存在的陷阱

大型并发系统环境中使用 Executors 如果不注意可能会出现系统风险。

方法名称

 

存在问题

public static ExecutorService newFixedThreadPool​(int nThreads)

允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误( java.lang.OutOfMemoryError

public static ExecutorService newSingleThreadExecutor()

public static ExecutorService newCachedThreadPool()

创建的线程数量最大上限是Integer.MAX_VALUE

线程数可能会随着任务1:1增长,也可能出现OOM错误( java.lang.OutOfMemoryError

public static ScheduledExecutorService newScheduledThreadPool​(int corePoolSize)

7、定时器

定时器是一种控制任务延时调用,或者周期调用的技术。
作用 :闹钟、定时邮件发送。

定时器的实现方式

方式一: Timer
方式二: ScheduledExecutorService

Timer定时器

构造器

说明

public Timer()

创建Timer定时器对象

方法

说明

public void schedule​(TimerTask task, long delay, long period)

开启一个定时器,按照计划处理TimerTask任务

Timer定时器的特点和存在的问题

1Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入

2、可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
    目标:Timer定时器的使用和了解。
 */
public class TimerDemo1 {
    public static void main(String[] args) {
        // 1、创建Timer定时器
        Timer timer = new Timer();  // 定时器本身就是一个单线程。
        // 2、调用方法,处理定时任务
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行AAA~~~" + new Date());
//                try {
//                    Thread.sleep(5000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }
        }, 0, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行BB~~~"+ new Date());
                System.out.println(10/0);
            }
        }, 0, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行CCC~~~"+ new Date());
            }
        }, 0, 3000);
    }
}

ScheduledExecutorService定时器

ScheduledExecutorService是 jdk1.5中引入了并发包,目的是为了弥补Timer的缺陷, ScheduledExecutorService内部为线程池。

Executors的方法

说明

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

得到线程池对象

ScheduledExecutorService的方法

说明

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,
                                             
TimeUnit unit)

周期调度方法

ScheduledExecutorService的优点

1、基于线程池,某个任务的执行情况不会影响其他定时任务的执行

import java.util.Date;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
    目标:ScheduledExecutorService定时器的使用和了解。
 */
public class TimerDemo2 {
    public static void main(String[] args) {
        // 1、创建ScheduledExecutorService线程池,做定时器
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);

        // 2、开启定时任务
        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行输出:AAA  ==》 " + new Date());
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 0, 2, TimeUnit.SECONDS);


        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行输出:BBB  ==》 " + new Date());
                System.out.println(10 / 0);
            }
        }, 0, 2, TimeUnit.SECONDS);


        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行输出:CCC  ==》 " + new Date());
            }
        }, 0, 2, TimeUnit.SECONDS);

    }
}

8、线程的生命周期

Java线程的状态

Java 总共定义了 6 种状态
6 种状态都定义在 Thread 类的内部枚举类中。

public class Thread{

     ...
     public enum State {
   
  NEW,                                        新建
      RUNNABLE,                            可运行
      BLOCKED,                              锁阻塞
      WAITING,                                无限等待
      TIMED_WAITING,                   计时等待
      TERMINATED;                        被终止
    }

     ...

}

线程的6种状态总结

线程状态

描述

NEW(新建)

线程刚被创建,但是并未启动。

Runnable(可运行)

线程已经调用了start()等待CPU调度

Blocked(锁阻塞)

线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;。

Waiting(无限等待)

一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒

Timed Waiting(计时等待)

waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。带有超时参数的常用方法有Thread.sleep Object.wait

Teminated(被终止)

因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

目录

1、多线程创建

2、Thread的常用方法

3、线程安全

4、线程同步

5、线程通信

6、线程池

7、定时器

8、线程的生命周期


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值