当前位置: 首页 > news >正文

一天一个java知识点----多线程

多线程

什么是线程?

  • 线程(Thread)是一个程序内部的一条执行流程。
  • 程序中如果只有一条执行流程,那这个程序就是单线程的程序。

什么是多线程?

  • 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。

创建线程

  • 一共有四种方式:继承Thread类、实现Runnable接口、实现Callable接口、线程池创建

方式一:继承Thread类

创建线程步骤:

  1. 定义一个子类MyThread继承线程类java.lang.Thread重写run()方法
  1. 创建MyThread类的对象
  1. 调用线程对象的start()方法启动线程(启动后还是执行run方法的

Java
    //main方法的执行也是一个线程,默认是主线程
    public static void main(String[] args) {
        //目标:创建线程方式1:集成Thread
    
        //3.创建线程对象
        MyThread myThread = new MyThread();
    
        //4.启动线程运行(就会运行里面run方法代码)
        myThread.start();//注意这里不可以调用myThread.run(),否则没有使用新的线程运行,而只是一个普通对象调用一个方法
        //注意:目前上面两个线程是一起运行的,在同一时间点一个cpu只能执行一个线程
        //     当有多个线程cpu采用轮片执行
    }    

//1.定义类,继承Thread
class MyThread extends Thread{

    //2.重写线程任务执行的run方法
    @Override
    public void run() {
        System.out.println("子线程正在运行...");
    }
}

方式一优缺点:

  • 优点:编码简单
  • 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展

创建线程的注意事项

  1. 启动线程必须是调用start方法,不是调用run方法。
  • 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
  • 只有调用start方法才是启动一个新的线程执行。
  1. 不要把主线程任务放在启动子线程之前。
  • 这样主线程一直是先跑完的,相当于是一个单线程的效果了。

方式二:实现Runnable接口

创建线程步骤:

  1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
  1. 创建MyRunnable任务对象
  1. 把MyRunnable任务对象交给Thread处理。

  1. 调用线程对象的start()方法启动线程

Java
    public static void main(String[] args) {
        //目标:创建线程方式2:实现Runnable接口
    
        //3.创建Runnable对象
        MyRunnable myRunnable = new MyRunnable();
    
        //4.创建线程对象,基于Runnable创建
        Thread thread = new Thread(myRunnable);
    
        //5.启动线程运行(就会运行里面run方法代码)
        thread.start();
    
        //简化1
        new Thread(new MyRunnable()).start();
        
        System.out.println("主线程运行。。。");
    }


//1.创建类实现Runnable接口
class MyRunnable implements Runnable {
    //2.重写线程任务执行的run方法
    @Override
    public void run() {
        System.out.println("子线程运行。。。");
    }
}

方式二的优缺点:

  • 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
  • 缺点:需要多一个Runnable对象。

方式二的另一种写法(匿名内部类)

  1. 可以创建Runnable的匿名内部类对象。
  1. 再交给Thread线程对象。
  1. 再调用线程对象的start()启动线程。

Java
//简化2:匿名内部类
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("子线程运行。。。");
    }
});

//简化3:lambda表达式
new Thread(() ->{
    for (int i = 0; i < 50; i++) {
        System.out.println("子线程运行。。。"+i+"次");
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}).start();

for (int i = 0; i < 50; i++) {
    System.out.println("主线程运行。。。"+i+"次");
    try {
        Thread.sleep(20);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

方式三:实现Callable接口(jdk5.0以后才有)

这种方式最大的优点:可以返回线程执行完毕后的结果。

线程创建步骤:

  1. 创建任务对象
  • 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据
  • 把Callable类型的对象封装成FutureTask(线程任务对象)。
  1. 把线程任务对象交给Thread对象。
  1. 调用Thread对象的start方法启动线程。
  1. 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果

FutureTask的作用:

  • Callable 的 call() 方法可以返回结果,FutureTask 提供了获取这个结果的机制。通过 FutureTask 的 get() 方法可以获取 Callable 计算的结果。

用一个线程计算:

Java
    public static void main(String[] args) throws Exception {
        //目标:创建线程第3种方式:实现Callable接口

        //一个子线程完成任务呢
        long start = System.currentTimeMillis();
        //3.创建Callable对象
        MyCallable myCallable = new MyCallable(0,1500);
        //4.创建FutureTask任务对象,传入Callable对象
        FutureTask<Integer> f1 = new FutureTask<>(myCallable);
        //5.创建线程对象,基于FutureTask创建,并启动线程
        new Thread(f1).start();
        //6.获取线程结果
        Integer result = f1.get();//会阻塞等待子线程运行完成得到结果才会往下执行主线程
        long end = System.currentTimeMillis();
        System.out.println("子线程运行结果:"+result+",共耗时"+(end-start)+"ms");
        //得出结论:一个线程做需要4000多毫秒
    }
//1.定义实现Callable接口
class MyCallable implements Callable<Integer> {
    private Integer start;
    private Integer end;
    public MyCallable(Integer start, Integer end) {
        this.start = start;
        this.end = end;
    }

    //2.重写call方法
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (Integer i = start; i <= end; i++) {
            sum += i;
            System.out.println("第"+i+"次累加,结果"+sum);
            Thread.sleep(2);
        }
        return sum;
    }
}

用三个线程计算:

Java
    public static void main(String[] args) throws Exception {
        //目标:创建线程第3种方式:实现Callable接口
        
        // 多个子线程完成这个计算任务,第一个线程完成0~500,第二个线程完成501~1000,第三个线程完成1001~1500
        long start = System.currentTimeMillis();
        //第一个线程任务
        FutureTask<Integer> f1 = new FutureTask<>(new MyCallable(0, 500));
        new Thread(f1).start();
        FutureTask<Integer> f2 = new FutureTask<>(new MyCallable(501, 1000));
        new Thread(f2).start();
        FutureTask<Integer> f3 = new FutureTask<>(new MyCallable(1001, 1500));
        new Thread(f3).start();

        //6.获取线程结果
        Integer result1 = f1.get();
        Integer result2 = f2.get();
        Integer result3 = f3.get();
        long end = System.currentTimeMillis();
        System.out.println("计算结果:"+(result1+result2+result3)+",共耗时"+(end-start)+"ms");
        //得出结果:3个线程做需要1000多毫秒
    }
    
//1.定义实现Callable接口
class MyCallable implements Callable<Integer> {
    private Integer start;
    private Integer end;
    public MyCallable(Integer start, Integer end) {
        this.start = start;
        this.end = end;
    }

    //2.重写call方法
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (Integer i = start; i <= end; i++) {
            sum += i;
            System.out.println("第"+i+"次累加,结果"+sum);
            Thread.sleep(2);
        }
        return sum;
    }
}

第三种方式的优缺点:

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

三种方式的对比

线程的常用方法

Java
public class Test {
    public static void main(String[] args) throws InterruptedException {
        //目标:理解线程常用的方法
        MyThread myThread = new MyThread("aa");
        myThread.start();

        Thread bb = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"正在运行");
                try {
                    Thread.sleep(10);//让线程休眠挂起,线程执行会阻塞,模拟线程执行较长时间
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"bb");
        bb.start();

        //Thread.sleep(10);
        //Thread.currentThread() 获取当前执行的线程对象Thread
        //Thread对象.getName() 获取线程名称
        for (int i = 0; i < 10; i++) {
            if(i==3){
                bb.join();//阻塞等待当前线下运行完当前线程(主线程)才能继续运行(优先运行当前子线程)
            }
            System.out.println(Thread.currentThread().getName()+"正在运行");
            Thread.sleep(10);
        }
    }
}
//1.定义类,继承Thread
class MyThread extends Thread {
    public MyThread(String name) {
        super(name);//给线程起名字
    }
    public void run() {
        System.out.println(Thread.currentThread().getName()+"子线程运行。。。");//thread-0,如果起名字就按照起的名字
    }
}

线程安全(面试高频)

认识线程安全

多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。

线程安全出现的原因:

  • 存在多个线程在同时执行
  • 同时访问一个共享资源
  • 存在修改该共享资源

模拟线程安全问题

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

Java
Account
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    private String cardNo;// 卡号
    private double money;// 余额
}

Java
ATM
public class ATM {
    /**
     * 模拟取款
     *
     * @param account
     * @param money
     */
    public void drawMoney(Account account, double money) {
        if (account.getMoney() >= money) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 取款
            account.setMoney(account.getMoney() - money);
            System.out.println(Thread.currentThread().getName() + "取款成功,取了:" + money);
            System.out.println(Thread.currentThread().getName()+"取款后的余额:"+account.getMoney());
        } else {
            System.out.println(Thread.currentThread().getName() + "取款失败,余额不足");
        }
    }
}

Java
MyRunnable
public class MyRunnable implements Runnable{
    private Account account;
    private ATM atm;
    public MyRunnable(Account account, ATM atm) {
        this.account = account;
        this.atm = atm;
    }

    @Override
    public void run() {
        atm.drawMoney(account,100000);
    }
}

Java
Test
public class Test {
    public static void main(String[] args) {
        //目标:模拟小红、小明取钱
        //1.创建一个账户
        Account account = new Account("ICBC-123456", 100000.0);

        //2、创建一个ATM机给小明
        ATM atm1 = new ATM();

        //3、创建一个ATM机给小红
        ATM atm2 = new ATM();

        //4、创建第一个线程让第一个ATM执行取钱任务
        new Thread(new MyRunnable(account, atm1),"小明").start();

        //5、创建第二个线程让第二个ATM执行取钱任务
        new Thread(new MyRunnable(account, atm2),"小红").start();
    }
}

线程同步

  • 线程同步是线程安全问题的解决方案。
  • 让多个线程先后依次访问共享资源,这样就可以避免出现线程安全问题。

认识线程同步

常用的线程同步方案:

  • 加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。

方式一:同步代码块

Java
synchronized(同步锁) {
    访问共享资源的核心代码(不安全的代码)
}

作用:把访问共享资源的核心代码给上锁,以此保证线程安全。

原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。

Java
Account
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    private String cardNo;// 卡号
    private double money;// 余额
}

Java
ATM
public class ATM {
    /**
     * 模拟取款
     *
     * @param account
     * @param money
     */
    public void drawMoney(Account account, double money) {
        //解决多线程并发安全问题方式1:同步代码块
        //语法:synchronized (同步锁){存在不安全的代码}
        //同步锁说明:
        //    共享资源对象(推荐), 例如Account对象,小红和小明竞争这个第一个账户锁, 小张和小李竞争第二个账户锁
        //    this, 调用当前方法的对象,这里就是ATM对象,有4个ATM就会有4个锁
        //    类.class, 类对象,全局只有一份,所有人都竞争这一个锁
        synchronized (account) {
            if (account.getMoney() >= money) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 取款
                account.setMoney(account.getMoney() - money);
                System.out.println(Thread.currentThread().getName() + "取款成功,取了:" + money);
                System.out.println(Thread.currentThread().getName() + "取款后的余额:" + account.getMoney());
            } else {
                System.out.println(Thread.currentThread().getName() + "取款失败,余额不足");
            }
        }
    }
}

Java
MyRunnable
public class MyRunnable implements Runnable{
    private Account account;
    private ATM atm;
    public MyRunnable(Account account, ATM atm) {
        this.account = account;
        this.atm = atm;
    }

    @Override
    public void run() {
        atm.drawMoney(account,10000);
    }
}

Java
Test
public class Test {
    public static void main(String[] args) {

        //1.第一对夫妻
        Account account = new Account("ICBC-123456", 100000.0);

        //2、创建一个ATM机给小明
        ATM atm1 = new ATM();

        //3、创建一个ATM机给小红
        ATM atm2 = new ATM();

        //4、创建第一个线程让第一个ATM执行取钱任务
        new Thread(new MyRunnable(account, atm1),"小明").start();

        //5、创建第二个线程让第二个ATM执行取钱任务
        new Thread(new MyRunnable(account, atm2),"小红").start();

        //第二对夫妻
        // 1、创建一个共享账户
        Account account2 = new Account("ICBC-123457", 100000.0);

        // 2、创建一个ATM机给小明用
        ATM atm3 = new ATM();

        // 3、创建一个ATM机给小红用
        ATM atm4 = new ATM();

        // 4. 创建第一个线程让第一个ATM执行取钱任务
        new Thread(new MyRunnable(account2, atm3), "小张").start();

        // 5. 创建第二个线程让第二个ATM执行取钱任务
        new Thread(new MyRunnable(account2, atm4), "小李").start();
    }
}

同步锁的注意事项:

  • 对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。

锁对象的使用规范:

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

问:锁对象随便选择一个唯一的对象好不好呢?

答:不好,会影响其他无关线程的执行。

方式二:同步方法

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

作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

同步方法底层原理

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  • 如果方法是实例方法:同步方法默认用this作为的锁对象。
  • 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

Java
Account
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    private String cardNo;// 卡号
    private double money;// 余额
}

Java
ATM
public class ATM {
    /**
     * 模拟取款
     *
     * @param account
     * @param money
     */
    //synchronized 这里是实例方法锁对象是this,有4个ATM对象,所以这里不安全
    public static synchronized void drawMoney(Account account, double money) {
        //解决多线程并发安全问题方式2:同步方法
        //语法:public synchronized [static] 返回值 方法名(参数列表){存在不安全的代码}
        //同步锁说明:
        //    public synchronized 返回值 方法名(参数列表){存在不安全的代码} 同步实例方法锁对象是this
        //    public synchronized static 返回值 方法名(参数列表){存在不安全的代码} 同步实例方法锁对象是 当前类.class
        if (account.getMoney() >= money) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 取款
            account.setMoney(account.getMoney() - money);
            System.out.println(Thread.currentThread().getName() + "取款成功,取了:" + money);
            System.out.println(Thread.currentThread().getName()+"取款后的余额:"+account.getMoney());
        } else {
            System.out.println(Thread.currentThread().getName() + "取款失败,余额不足");
        }
    }
}

Java
MyRunnable
public class MyRunnable implements Runnable{
    private Account account;
    private ATM atm;
    public MyRunnable(Account account, ATM atm) {
        this.account = account;
        this.atm = atm;
    }

    @Override
    public void run() {
        atm.drawMoney(account,100000);
    }
}

Java
Test
public class Test {
    public static void main(String[] args) {

        //1.第一对夫妻
        Account account = new Account("ICBC-123456", 100000.0);

        //2、创建一个ATM机给小明
        ATM atm1 = new ATM();

        //3、创建一个ATM机给小红
        ATM atm2 = new ATM();

        //4、创建第一个线程让第一个ATM执行取钱任务
        new Thread(new MyRunnable(account, atm1),"小明").start();

        //5、创建第二个线程让第二个ATM执行取钱任务
        new Thread(new MyRunnable(account, atm2),"小红").start();

        //第二对夫妻
        // 1、创建一个共享账户
        Account account2 = new Account("ICBC-123456", 100000.0);

        // 2、创建一个ATM机给小明用
        ATM atm3 = new ATM();

        // 3、创建一个ATM机给小红用
        ATM atm4 = new ATM();

        // 4. 创建第一个线程让第一个ATM执行取钱任务
        new Thread(new MyRunnable(account2, atm3), "小张").start();

        // 5. 创建第二个线程让第二个ATM执行取钱任务
        new Thread(new MyRunnable(account2, atm4), "小李").start();
    }
}

同步代码块和同步方法:推荐同步代码块

  • 范围上:同步代码块锁的范围更小,同步方法锁的范围更大
  • 可读性:同步方法更好

方式三:lock锁

  • Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
  • Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象

Lock的常用方法:

  • 如果实例成员对象或局部对象锁对象是this
  • 如果静态变量锁对象是class

Java
Account
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    private String cardNo;// 卡号
    private double money;// 余额
}                                                                                           

Java
ATM
public class ATM {
    /**
     * 模拟取款
     *
     * @param account
     * @param money
     */
    //创建Lock锁对象
    static final Lock lock = new ReentrantLock();
    //lock如果是静态的,同步锁是 类.class对象
    //lock如果是非静态的,同步锁是 this
    public void drawMoney(Account account, double money) {
        //在存在安全的代码前面
        lock.lock();//上锁,默认锁当前对象,this是ATM
        try {
            if (account.getMoney() >= money) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 取款
                account.setMoney(account.getMoney() - money);
                System.out.println(Thread.currentThread().getName() + "取款成功,取了:" + money);
                System.out.println(Thread.currentThread().getName()+"取款后的余额:"+account.getMoney());
            } else {
                System.out.println(Thread.currentThread().getName() + "取款失败,余额不足");
            }
        } finally {
            lock.unlock();//锁释放
            //注意:释放锁,一定要放在finally里面,否则有可能出现死锁的情况。
        }
    }
}

Java
MyRunnable
 public class MyRunnable implements Runnable{
    private Account account;
    private ATM atm;
    public MyRunnable(Account account, ATM atm) {
        this.account = account;
        this.atm = atm;
    }

    @Override
    public void run() {
        atm.drawMoney(account,100000);
    }
}

Java
Test
public class Test {
    public static void main(String[] args) {
        //目标:模拟小红、小明取钱
        //1.创建一个账户
        Account account = new Account("ICBC-123456", 100000.0);

        //2、创建一个ATM机给小明
        ATM atm1 = new ATM();

        //3、创建一个ATM机给小红
        ATM atm2 = new ATM();

        //4、创建第一个线程让第一个ATM执行取钱任务
        new Thread(new MyRunnable(account, atm1),"小明").start();

        //5、创建第二个线程让第二个ATM执行取钱任务
        new Thread(new MyRunnable(account, atm2),"小红").start();

        //第二对夫妻
        // 1、创建一个共享账户
       Account account2 = new Account("ICBC-123456", 100000.0);

        // 2、创建一个ATM机给小明用
        ATM atm3 = new ATM();

        // 3、创建一个ATM机给小红用
        ATM atm4 = new ATM();

        // 4. 创建第一个线程让第一个ATM执行取钱任务
        new Thread(new MyRunnable(account2, atm3), "小张").start();

        // 5. 创建第二个线程让第二个ATM执行取钱任务
        new Thread(new MyRunnable(account2, atm4), "小李").start();
    }
}

注意事项:

  • 建议使用final修饰,防止被别人篡改
  • 建议将释放锁的操作放到finally代码块中,确保锁用完了一定会被释放。(防止死锁

线程池

  • 线程池就是一个可以复用线程的技术

不使用线程池的问题:

  • 用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,  创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。

认识线程池

线程池的工作原理

先结束工作的线程会先去做下一个任务。

任务接口RunnableCallable

创建线程池

JDK 5.0起提供了代表线程池的接口ExecutorService

  • 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。(推荐)

  • 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

方式一:通过ThreadPoolExecutor创建线程池

Java
public ThreadPoolExecutor(int corePoolSize,  //核心线程数,稳定常用的线程。核心线程创建后不会被销毁
                          int maximumPoolSize,  //最大线程数,最大线程数-核心线程数=临时线程数,临时线程数在接到任务,会临时执行任务,当不忙时会销毁临时线程
                          long keepAliveTime,   //临时线程的存活时间,临时线程空闲了多少秒/毫秒就销毁
                          TimeUnit unit,   //临时线程存活时间单位(SECONDS等)
                          BlockingQueue<Runnable> workQueue,  //阻塞队列,工作队列,核心线程忙不过来会存入工作队列
                          ThreadFactory threadFactory,  //线程工厂,创建核心线程或临时线程
                          RejectedExecutionHandler handler)   //拒绝策略,当临时线程也忙不过来会拒绝任务抛出异常

  • 参数一:corePoolSize : 指定线程池的核心线程的数量。                          
  • 参数二:maximumPoolSize:指定线程池的最大线程数量
  • 参数三:keepAliveTime :指定临时线程的存活时间
  • 参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)
  • 参数五:workQueue:指定线程池的任务队列
  • 参数六:threadFactory:指定线程池的线程工厂
  • 参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)

Java
ExecutorService pool = new ThreadPoolExecutor(
                3,// 参数1:核心线程数,相当于正式工,稳定常用的线程。核心线程创建后不会被销毁
                5,// 参数2:最大线程数,最大线索数-核心线程数=临时线程数,临时线程看成临时工,当不忙的时候会销毁临时线程
                     //什么时候使用临时线程?答:核心线程没有空闲,工作队列也满了,才会使用临时线程
                10,// 参数3:临时线程存活时间,临时线程空闲了多少秒/毫秒就销毁
                TimeUnit.SECONDS,// 参数4:临时线程存活时间的单位
                new ArrayBlockingQueue<>(3),//参数5:设置工作队列,核心线程忙不过来就会存入工作对象
                Executors.defaultThreadFactory(),//参数6:设置线程工厂,创建核心线程或临时线程
                //new ThreadPoolExecutor.AbortPolicy()//参数7:设置拒绝策略,当临时线程也忙不过来,会拒绝任务抛出异常
                new ThreadPoolExecutor.CallerRunsPolicy()//参数7:设置拒绝策略,将任务交给调佣线程池的线程去处理(这里是main主线程)
        );

临时线程的出现时机??核心线程在忙,任务队列满了,新线程过来才会开临时线程。

处理Runnable任务

ExecutorService的常用方法

Java
public class Test {


    public static void main(String[] args) {
        //目标:使用ExecutorService接口实现类ThreadPoolExecutor创建线程池。

        //1.创建线程池
        ExecutorService pool = new ThreadPoolExecutor(
                3,// 参数1:核心线程数,相当于正式工,稳定常用的线程。核心线程创建后不会被销毁
                5,// 参数2:最大线程数,最大线索数-核心线程数=临时线程数,临时线程看成临时工,当不忙的时候会销毁临时线程
                     //什么时候使用临时线程?答:核心线程没有空闲,工作队列也满了,才会使用临时线程
                10,// 参数3:临时线程存活时间,临时线程空闲了多少秒/毫秒就销毁
                TimeUnit.SECONDS,// 参数4:临时线程存活时间的单位
                new ArrayBlockingQueue<>(3),//参数5:设置工作队列,核心线程忙不过来就会存入工作对象
                Executors.defaultThreadFactory(),//参数6:设置线程工厂,创建核心线程或临时线程
                //new ThreadPoolExecutor.AbortPolicy()//参数7:设置拒绝策略,当临时线程也忙不过来,会拒绝任务抛出异常
                new ThreadPoolExecutor.CallerRunsPolicy()//参数7:设置拒绝策略,将任务交给调佣线程池的线程去处理(这里是main主线程)
        );

        //2.从线程池里面获取线程做任务
        pool.execute(new MyRunnalble()); //将任务给到线程池去处理,核心线程数来做
        pool.execute(new MyRunnalble()); //将任务给到线程池去处理,核心线程数来做
        pool.execute(new MyRunnalble()); //将任务给到线程池去处理,核心线程数来做
        pool.execute(new MyRunnalble()); //将任务给到线程池去处理,加入队列
        pool.execute(new MyRunnalble()); //将任务给到线程池去处理,加入队列
        pool.execute(new MyRunnalble()); //将任务给到线程池去处理,加入队列
        pool.execute(new MyRunnalble()); //将任务给到线程池去处理,核心线程没有空闲,队列也满了,会使用临时线程
        pool.execute(new MyRunnalble()); //将任务给到线程池去处理,核心线程没有空闲,队列也满了,会使用临时线程
        pool.execute(new MyRunnalble()); //会执行拒绝策略

    }
}

class MyRunnalble implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName()+"===>"+i);
            try {
                //目的让核心线程不能空闲
                Thread.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程池的注意事项

  • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务

任务拒绝策略(面试高频)

处理Callable任务

ExecutorService的常用方法

Java
public class Test {
    public static void main(String[] args) throws Exception {
        //目标:使用ExecutorService接口实现类ThreadPoolExecutor创建线程池。

        //1.创建线程池
        ExecutorService pool = new ThreadPoolExecutor(
                3,// 参数1:核心线程数,相当于正式工,稳定常用的线程。核心线程创建后不会被销毁
                5,// 参数2:最大线程数,最大线索数-核心线程数=临时线程数,临时线程看成临时工,当不忙的时候会销毁临时线程
                     //什么时候使用临时线程?答:核心线程没有空闲,工作队列也满了,才会使用临时线程
                10,// 参数3:临时线程存活时间,临时线程空闲了多少秒/毫秒就销毁
                TimeUnit.SECONDS,// 参数4:临时线程存活时间的单位
                new ArrayBlockingQueue<>(3),//参数5:设置工作队列,核心线程忙不过来就会存入工作对象
                Executors.defaultThreadFactory(),//参数6:设置线程工厂,创建核心线程或临时线程
                //new ThreadPoolExecutor.AbortPolicy()//参数7:设置拒绝策略,当临时线程也忙不过来,会拒绝任务抛出异常
                new ThreadPoolExecutor.CallerRunsPolicy()//参数7:设置拒绝策略,将任务交给调佣线程池的线程去处理(这里是main主线程)
        );

        //2.从线程池里面获取线程做任务
        long start = System.currentTimeMillis();
        Future<Integer> f1 = pool.submit(new MyCallable(0, 500));
        Future<Integer> f2 = pool.submit(new MyCallable(501, 1000));
        Future<Integer> f3 = pool.submit(new MyCallable(1001, 1500));
        Integer r1 = f1.get();
        Integer r2 = f2.get();
        Integer r3 = f3.get();
        long end = System.currentTimeMillis();
        System.out.println("计算结果:"+(r1+r2+r3)+",共耗时:"+(end-start)+"毫秒");

    }
}


//1.定义类实现Callable接口
class MyCallable implements Callable<Integer> {

    private Integer start;//累加开始数
    private Integer end;//累加结束数

    public MyCallable(Integer start, Integer end) {
        this.start = start;
        this.end = end;
    }

    //2.重写call方法执行任务代码
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (Integer i = start; i <= end; i++) {
            sum += i;
            System.out.println("第"+i+"次累加,结果:"+sum);
            Thread.sleep(2);

        }
        return sum;
    }
}

通过Executors创建线程池

是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。

 

注意 :这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。

只推荐使用上面这个↑↑↑。延迟执行任务

Java
public static void main(String[] args) throws Exception {
    //目标:使用ExecutorService接口实现类ThreadPoolExecutor创建线程池。

    //1.创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(3);

    //2.提交任务
    long start = System.currentTimeMillis();
    Future<Integer> f1 = pool.submit(new MyCallable(1, 500));
    Future<Integer> f2 = pool.submit(new MyCallable(501, 1000));
    Future<Integer> f3 = pool.submit(new MyCallable(1000, 1500));
    Integer r1 = f1.get();
    Integer r2 = f2.get();
    Integer r3 = f3.get();
    long end = System.currentTimeMillis();
    System.out.println("计算结果:"+(r1+r2+r3)+",共耗时"+(end-start)+"ms");
}

Executors使用可能存在的陷阱

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

OOM内存溢出(Out Of Memory),指程序在运行过程中申请的内存超过了系统或进程所能提供的最大内存限制,导致程序崩溃或被系统终止。

并发、并行

进程:

  • 正在运行的程序(软件)就是一个独立的进程。
  • 线程是属于进程的,一个进程中可以同时运行很多个线程。
  • 进程中的多个线程其实是并发和并行执行的。

并发的含义

  • 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
  • 并行的理解
    在同一个时刻上,同时有多个线程在被CPU调度执行

多线程的执行过程:

  • 并发和并行同时进行的
  • 并发:CPU分时轮询的执行线程。
  • 并行:同一个时刻同时在执行。
http://www.dtcms.com/a/113472.html

相关文章:

  • 虚拟Ashx页面,在WEB.CONFIG中不添加handlers如何运行
  • Linux系统之chkconfig命令详解
  • P1036 [NOIP 2002 普及组] 选数(DFS)
  • LeetCode算法题(Go语言实现)_32
  • 详解七大排序
  • 什么是RPC通信
  • 【spring cloud Netflix】Ribbon组件
  • 供应链业务-供应链全局观(二)
  • 蓝桥云客--回文数组
  • 迈向未来:数字化工厂管理如何重塑生产力
  • OpenGL学习笔记(简介、三角形、着色器、纹理、坐标系统、摄像机)
  • 数据库系统概述 | 第三章课后习题答案
  • 蓝桥杯_PCF8591
  • (二)输入输出处理——打造智能对话的灵魂
  • 如何使用 Nginx 代理 Easysearch 服务
  • 洛谷题单3-P5725 【深基4.习8】求三角形-python-流程图重构
  • C语言求3到100之间的素数
  • C++蓝桥杯实训篇(二)
  • Java 逐梦力扣之旅_[204. 计数质数]
  • 大模型持续学习方案解析:灾难性遗忘的工业级解决方案
  • 递归实现组合型枚举(DFS)
  • 蓝牙跳频扩频技术的作用:提升抗干扰能力与通信可靠性的核心机制
  • 道路裂缝数据集CrackForest-156-labelme
  • 设计模式简述(五)建造者模式
  • 小小模拟器 1.2.1 | 免登录无广告,畅玩经典游戏内置金手指
  • 【深度学习新浪潮】视觉与多模态大模型文字生成技术研究进展与产品实践
  • 字节二面:TCP 链接中,接收方不调用 recv,会出现什么情况?——拆解大厂面试题(校招)
  • css flex布局 让子元素在最右边技巧
  • 【移动计算】:AndroidStudio安装和项目搭建【2019:版本3.5.2】
  • 【书籍】DeepSeek谈《人月神话》