25-多线程

news2024/11/25 4:31:26

多线程

线程(Thread)是一个程序内部的一条执行流程。
程序中如果有一条执行流程,那这个程序就是单线程的程序
多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。
再例如:消息通信、淘宝、京东系统都离不开多线程技术

如何在程序中创建出多条线程?

Java是通过java.lang.Thread 类的对象来代表线程的。
有两种方法可以创建新的执行线程。 一种是将类声明为Thread的子类。 此子类应覆盖类Threadrun方法。 然后可以分配和启动子类的实例。
run方法是我们这个线程做什么事情

     class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }
     PrimeThread p = new PrimeThread(143);
     p.start();

多线程的创建方式一:继承Thread类

定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
创建MyThread类的对象
调用线程对象的start()方法启动线程(启动后还是执行run方法的)
方式一优缺点:
优点:编码简单
缺点:线程类已经继承 Thread,无法继承其他类,不利于功能的扩展。(java 可以嵌套基层,但不能同时继承多个类,一个类只能有一个父类)

// 子类的线程
threadClass tc = new threadClass();
tc.start();
// 主函数的线程
for (int i = 0; i < 5; i++) {
	System.out.println("main thread is " + i);
}

public class Mythead extends Thread {
        @Override
        public void run(){
            for (int i = 0; i <5 ; i++) {
                System.out.println("my thread is "+ i);
            }
        }
}

main本身也是一个线程
线程是同时执行的,所以这个执行语句输出可能是随机的, 并不是俺早顺序输出的。

my thread is 0
my thread is 1
main thread is 0
main thread is 1
main thread is 2
main thread is 3
main thread is 4
my thread is 2
my thread is 3
my thread is 4
注意事项

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

多线程的创建方式二:实现Runnable接口

  1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
  2. 创建MyRunnable任务对象
  3. 把MyRunnable任务对象交给Thread处理。
  4. 调用线程对象的start()方法启动线程
Thread类提供的构造器说明
public Thread(Runnable target)封装Runnable对象成为线程对象

方式二的优缺点

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

举例代码

public static void main(String[] args) {
    RunnableObject ro = new RunnableObject(123);
    new Thread(ro).start();
    for (int i = 0; i <7 ; i++) {
        System.out.println("主 thread is "+ i);
    }
}

//这个类和别的类几乎没什么区别,我们也可以正常的定义成员变量什么的
public class RunnableObject implements Runnable{
    long minRunObj;
    public RunnableObject() {
    }
    public RunnableObject(long minRunObj) {
        this.minRunObj = minRunObj;
    }
    @Override
    public void run() {
        for (int i = 0; i <7 ; i++) {
            System.out.println("我的 thread is "+ i);
        }
    }
}

创建方式二的匿名内部类的方式写法

如果这个类我们只使用一次的话, 我们就可以使用这个匿名内部类, 匿名内部类是实现这个接口和实现这个抽象类的一种方法, 我们可以直接在这个函数传递的参数中用匿名内部类来代替实际的参数, 比如下面的这种, 这个 new Runnable 本质上就是一个接口. 我们实现这个接口就是实现了这个匿名内部类.

new Thread(new Runnable() {
	@Override
	public void run() {
		for (int i = 0; i <7 ; i++) {
			System.out.println("我的 thread is "+ i);
		}
	}
}).start();

创建方式三实现Callable接口

前面两种创建方式都存在的一个问题
假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。
JDK 5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)。
这种方式最大的优点:可以返回线程执行完毕后的结果。

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

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

举例
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建对象
        callableObject co = new callableObject(100);
        //是一个任务对象,实现了这个runnable接口,可以直接使用
        FutureTask<String> sft = new FutureTask<>(co);
        new Thread(sft).start();
        System.out.println(sft.get());  //这个获取结果的顺序必须要在启动线程之后,否则没有结果

    }

实现callable接口的对象

public class callableObject implements Callable<String> {

    private int n;

    public callableObject(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        //求1-n的和
        int sum=0;
        for (int i = 0; i < n; i++) {
            sum+=i;
        }
        return "线程求出了1-"+ n+"的和是"+sum;
    }
}

需要注意的是这个 callable 是一个泛型的变量, 我们在使用的时候要根据返回值对其确定类型如果我们的 call 函数的返回值是一个 String 类型的变量, 那么我们就要在这个 callable 后面加上这个<String>否则的话我们这个 call 函数的返回值就要设置为object

三种线程的创建方式的不同点

方式优点缺点
继承Thread类编程比较简单,可以直接使用Thread类中的方法扩展性较差,不能再继承其他的类,不能返回线程执行的结果
实现Runnable接口扩展性强,实现该接口的同时还可以继承其他的类。编程相对复杂,不能返回线程执行的结果
实现Callable接口扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果编程相对复杂

Thread的常用方法

Thread提供的常用方法说明
public void run()线程的任务方法
public void start()启动线程
public String getName()获取当前线程的名称,线程名称默认是Thread-索引
public void setName(String name)为线程设置名称
public static Thread currentThread()获取当前执行的线程对象
public static void sleep(long time)让当前执行的线程休眠多少毫秒后,再继续执行
public final void join()…让调用当前这个方法的线程先执行完!

其中比较重点的是getName,和setName,以及这个currentThread,sleep,join等方法
尤其是这个currentThread很重要
setName
我们可以通过这个代码来设置名字,我们也可以通过这个构造器中的代码来进行构造名字

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

我们可以通过这个代码来获取当前执行的线程对象,使我们能够在随机执行的线程中找到当前线程对象

        Thread t3 = Thread.currentThread();
        System.out.println(t3.getName());
        //System.out.println(Thread.currentThread().getName());

sleep 这个可以让这个程序变快,变慢(可以通过这个设置会员和非会员的速度, 会员的话就让其不休眠, 非会员的话就让它每次运行的话休眠 0.3 秒, 这样不太影响体验, 但确实有区别, 哈哈)
Thread.sleep(5000)
让这个当前线程休眠5秒
join 方法
注意:
main函数中使用Thread.currentThread().join()会导致主线程(即main线程)等待自己,这会导致程序陷入死锁状态,因为主线程无法等待自己完成。

        Thread t1= new Mythead();
        t1.start();
		t1.join();
        System.out.println(t1.getName());

常见构造器

Thread提供的常见构造器说明
public Thread(String name)可以为当前线程指定名称
public Thread(Runnable target)封装Runnable对象成为线程对象
public Thread(Runnable target, String name)封装Runnable对象成为线程对象,并指定线程名称

线程安全问题

多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
线程安全问题发生的原因是什么?
多个线程,同时访问同一个共享资源,且同时修改该资源。就会出现问题.

取钱的线程安全问题

场景:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?
取钱的三步
1、判断余额是否足够
2、吐出100000元
3、更新账户余额
第一步两个人执行的时候因为多线程不是按照顺序来的,因为我们可能会两个人通过对余额判断成功

image-20230810112226615

线程安全问题出现的原因?

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

用程序模拟线程安全问题

主函数

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        account ac = new account(100000);
        myThread mTh = new myThread(ac,90000,"小明");
        FutureTask ft = new FutureTask(mTh);
        myThread mTh2 = new myThread(ac,50000,"小红");
        FutureTask ft2 = new FutureTask(mTh2);
        new Thread(ft).start();
        new Thread(ft2).start();
    }

    @Override
    public Double call() throws Exception {
        // 判断这个钱是不是还够,如果不够提示,如果够的话,修改余额
        if (ac.getBalance() < balance) {
            // 钱不够了
            System.out.println(name + "取钱的时候,账户余额不足");
        } else {
            // 钱还够
            System.out.println(name + "取钱的时候,账户余额充足");
            ac.setBalance(ac.getBalance() - balance);
            System.out.println(name + "取钱成功,账户余额为" + ac.getBalance());
        }
        return 0.0;
    }


出现了这个线程冲突

小明在取钱,余额充足,还有100000.0
小红在取钱,余额充足,还有100000.0
小红取完钱后,剩余-40000.0
小明取完钱后,剩余10000.0

线程同步 解决线程安全的问题

线程同步
解决线程安全问题的方案。
线程同步的思想
让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
线程同步的原理
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
加锁:让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

加锁的几种方法

同步代码块

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

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

原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。
同步锁的注意事项
对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出 bug。 下面的这种

    public static void main(String[] args) {
        account ac = new account(100000);
        myThread mTh = new myThread(ac,100000,"小明");
        FutureTask ft = new FutureTask(mTh);
        myThread mTh2 = new myThread(ac,100000,"小红");
        FutureTask ft2 = new FutureTask(mTh2);

        new Thread(ft).start();
        new Thread(ft2).start();
    }
    @Override
    public Double call() throws Exception {
        synchronized ("黑马") {
            if (Double.compare(ac.getMoney(),getMoney)<0){
                System.out.println(this.name+"在取钱,余额不够,只剩"+ac.getMoney());
            }else {
                System.out.println(name+"在取钱,余额充足,还有"+ac.getMoney());
                ac.setMoney(ac.getMoney()-getMoney);
                System.out.println(name+"取完钱后,剩余"+ac.getMoney());
            }
            //返回余额
            return 0.0;
        }

    }

因为这个锁"黑马"是固定的,我们不能够用这个唯一的字符串作为锁,因为黑马不仅能够锁着小明和小红,还会锁着别的人
最好的是用小明和小红有,别人也有但是每一个张卡都不一样
synchronized (this.getclass())
静态方法由于是以类为单位来进行访问的,因此我们在使用的时候我们可以通过synchronized (Account.class);来上锁
锁对象随便选择一个唯一的对象好不好呢?
不好,会影响其他无关线程的执行。
锁对象的使用规范
建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。

同步方法

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

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

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

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  • 如果方法是实例方法:同步方法默认用this作为的锁对象。
  • 如果方法是静态方法:同步方法默认用类名. Class 作为的锁对象。
    @Override
    public synchronized Double call() throws Exception {
        if (Double.compare(ac.getMoney(),getMoney)<0){
            System.out.println(this.name+"在取钱,余额不够,只剩"+ac.getMoney());
        }else {
            System.out.println(name+"在取钱,余额充足,还有"+ac.getMoney());
            ac.setMoney(ac.getMoney()-getMoney);
            System.out.println(name+"取完钱后,剩余"+ac.getMoney());
        }
        //返回余额
        return 0.0;
    }

是同步代码块好还是同步方法好一点?
范围上:同步代码块锁的范围更小,同步方法锁的范围更大。
可读性:同步方法更好。

方式三 lock锁

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

构造器说明
public ReentrantLock()获得Lock锁的实现类对象

Lock的常用方法

方法名称说明
void lock()获得锁
void unlock()释放锁
  • Lock实现提供了比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构,可能具有完全不同的属性,并且支持多个关联的[Condition]对象。

注意点
为了防止这个锁解不开,我们一定要进行try finally进行解锁
手动进行加锁和解锁,lock,unlock
final我们最好用这个final进行修饰这个lock,防止它被替换

image-20230810122441126

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

public class Account {
    private String cardId; // 卡号
    private double money; // 余额。
    // 创建了一个锁对象
    private final Lock lk = new ReentrantLock();
    public Account() {
    }
    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    // 小明 小红线程同时过来的
    public void drawMoney(double money) {
        // 先搞清楚是谁来取钱?
        String name = Thread.currentThread().getName();
        try {
            lk.lock(); // 加锁
            // 1、判断余额是否足够
            if(this.money >= money){
                System.out.println(name + "来取钱" + money + "成功!");
                this.money -= money;
                System.out.println(name + "来取钱后,余额剩余:" + this.money);
            }else {
                System.out.println(name + "来取钱:余额不足~");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lk.unlock(); // 解锁
        }
    }

}

线程通信,了解一下,理解思想就好了

什么是线程通信?
当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。
线程通信的常见模型(生产者与消费者模型)
生产者线程负责生产数据
消费者线程负责消费生产者生产的数据。
注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!

image-20230810123021243

方法名称说明
void wait()让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法
void notify()唤醒正在等待的单个线程
void notifyAll()唤醒正在等待的所有线程

注意
上述方法应该使用当前同步锁对象进行调用。
唤醒放在前面,等待放在后面
对下面的代码进行分析发现主函数中有五个线程,这五个线程是在不断的进行竞争的,不一定会谁先进行执行
所以我们必须要考虑到所有的情况,如果吃货执行的时候没有包子,那么就让其等待,唤醒别的线程,然后又有可能会是吃货执行,在来一次循环, 如果是厨师执行, 那么先判断这个有没有包子, 如果有的话就不做了, 继续等待, 唤醒别的线程, 如果没有的话就做, 做完之后唤醒别的, 把自己等待下去.
总会有一次是厨师线程执行的,因为这个执行是随机的,也不可能每次都不执行啊


    public static void main(String[] args) {
        //   需求:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上
        //      2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃。
        Desk desk  = new Desk();

        // 创建3个生产者线程(3个厨师)匿名内部类
        new Thread(() -> {
            while (true) {
                desk.put();
            }
        }, "厨师1").start();

        new Thread(() -> {
            while (true) {
                desk.put();
            }
        }, "厨师2").start();

        new Thread(() -> {
            while (true) {
                desk.put();
            }
        }, "厨师3").start();

        // 创建2个消费者线程(2个吃货)
        new Thread(() -> {
            while (true) {
                desk.get();
            }
        }, "吃货1").start();

        new Thread(() -> {
            while (true) {
                desk.get();
            }
        }, "吃货2").start();
    }
public class Desk {
    private List<String> list = new ArrayList<>();

    // 放1个包子的方法
    // 厨师1 厨师2 厨师3
    public synchronized void put() {
        try {
            String name = Thread.currentThread().getName();
            // 判断是否有包子。
            if(list.size() == 0){
                list.add(name + "做的肉包子");
                System.out.println(name + "做了一个肉包子~~");
                Thread.sleep(2000);

                // 唤醒别人, 等待自己
                this.notifyAll();
                this.wait();
            }else {
                // 有包子了,不做了。
                // 唤醒别人, 等待自己
                this.notifyAll();
                this.wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 吃货1 吃货2
    public synchronized void get() {
        try {
            String name = Thread.currentThread().getName();
            if(list.size() == 1){
                // 有包子,吃了
                System.out.println(name  + "吃了:" + list.get(0));
                list.clear();
                Thread.sleep(1000);
                this.notifyAll();
                this.wait();
            }else {
                // 没有包子
                this.notifyAll();
                this.wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

线程池

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

image-20230810132351186

如何创建线程池

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

image-20230810154009891
例如

//new LinkedBlockingDeque<>() 无限大小
        //new ArrayBlockingQueue<>(4) 限制这个集合的大小
        new ThreadPoolExecutor(3,5,8, 
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

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

常用方法

方法名称说明
void execute(Runnable command)执行 Runnable 任务
Future<T> submit(Callable<T> task)执行 Callable 任务,返回未来任务对象,用于获取线程返回的结果
void shutdown()等全部任务执行完毕后,再关闭线程池!
List<Runnable> shutdownNow()立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务

举例说明

main 函数

ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        Runnable target = new MyRunnable();
        pool.execute(target);  //线程池会自动创建一个新线程,自动处理这个任务,自动执行的
        pool.execute(target);  //线程池会自动创建一个新线程,自动处理这个任务,自动执行的
        pool.execute(target);  //线程池会自动创建一个新线程,自动处理这个任务,自动执行的
        pool.execute(target);  //复用前面的核心线程,因为前面三个线程已经执行过了,又空出来了
        pool.execute(target);  //复用前面的核心线程
		
		pool.shutdown();

线程代码

实现的runable接口
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name + " is running");
    }

运行结果

pool-1-thread-2 is running
pool-1-thread-1 is running
pool-1-thread-3 is running
pool-1-thread-1 is running
pool-1-thread-2 is running
pool-1-thread-3 is runnin
进程已结束,退出代码0

注意shutdownNow()
shutdownNow()是不管这个线程池怎么样,只要执行到这里就关闭线程池
shutdown是等到这个线程任务执行完毕之后才关闭
实际上我们也不会关闭这个线程池

临时线程和拒绝任务

        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        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);  //拒绝任务

核心线程三个,排队的四个,然后临时线程两个,之后在添加就会拒绝任务了
此时就会抛出异常,提示你这个线程池满了

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task thread.pool.MyRunnable@4f3f5b24 rejected from java.util.concurrent.ThreadPoolExecutor@15aeb7ab[Running, pool size = 5, active threads = 5, queued tasks = 4, 

拒绝异常

策略详解
ThreadPoolExecutor.AbortPolicy丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常 这是不推荐的做法,你都不知道你错了没有,不推荐
ThreadPoolExecutor.DiscardOldestPolicy;抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy由主线程负责调用任务的run()方法从而绕过线程池直接执行,由老板亲自处理新任务

线程池处理callable的处理方法

方法名称说明
void execute(Runnable command)执行任务/命令,没有返回值,一般用来执行 Runnable 任务
Future<T> submit(Callable<T> task)执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown()等任务执行完毕后关闭线程池
List<Runnable> shutdownNow()立刻关闭,停止正在执行的任务,并返回队列中未执行的任务
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());


        Future<String> submit1 = pool.submit(new MyCallable(100));
        Future<String> submit2 = pool.submit(new MyCallable(200));
        Future<String> submit3 = pool.submit(new MyCallable(300));
        System.out.println(submit1.get());
        System.out.println(submit2.get());
        System.out.println(submit3.get());
        Future<String> submit4 = pool.submit(new MyCallable(400));
        Future<String> submit5 = pool.submit(new MyCallable(500));
        System.out.println(submit4.get());
        System.out.println(submit5.get());
pool-1-thread-1执行从1-100的运算结果是10100
pool-1-thread-2执行从1-200的运算结果是40200
pool-1-thread-3执行从1-300的运算结果是90300
pool-1-thread-1执行从1-400的运算结果是160400
pool-1-thread-3执行从1-500的运算结果是250500

Executors 工具实现线程池

方法名称说明
public static ExecutorService newFixedThreadPool(int nThreads)创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static ExecutorService newSingleThreadExecutor()创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
public static ExecutorService newCachedThreadPool()线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

Executors使用可能存在的陷阱
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
这种Executors创建的会导致这个等待的任务可以无限长

image-20230810162939695

核心线程数量到底配置成多少呢
计算密集型的任务,核心线程数量 = CPU的核数 + 1
IO密集型的任务: 核心线程数量 = CPU 核数 + 2

进程

  1. 正在运行的程序就是一个独立的进程
  2. 线程是属于进程的,一个进程中可以同时运行很多个线程。
  3. 进程中的多个线程其实是并发和并行执行的。

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

线程的生命周期

线程的生命周期
也就是线程从生到死的过程中,经历的各种状态及状态转换。
理解线程这些状态有利于提升并发编程的理解能力。
Java线程的状态
Java总共定义了6种状态
6种状态都定义在Thread类的内部枚举类中。

public class Thread{
     ...
         public enum State {
         NEW,
         RUNNABLE,
         BLOCKED,
         WAITING,
         TIMED_WAITING,
         TERMINATED;
     }
     ...
}

线程状态说明
NEW(新建)线程刚被创建,但是并未启动。
Runnable(可运行)线程已经调用了start(),等待CPU调度
Blocked(锁阻塞)线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;。
Waiting(无限等待)一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒
Timed Waiting(计时等待)同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

image-20230810165200048

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1056851.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1.4 计算机网络在我国的发展

思维导图&#xff1a; 笔记&#xff1a; #### 一、初期发展 1. **1980年代初**&#xff1a;铁道部开始进行计算机联网实验&#xff0c;为我国计算机网络的起点。 2. **1989年11月**&#xff1a;我国第一个公用分组交换网CNPAC建成运行。 #### 二、专用计算机广域网 1. **20世…

27-动态代理和反射

参考视频链接 反射黑马教程 反射 反射初识 反射允许对封装类的字段&#xff0c;方法和构造函数的信息进行编程访问 它可以将一个类里面的方法和成员变量获取出来 反射是通过 class 文件中获取这字段&#xff0c;构造方法和成员方法&#xff0c;然后在从这三个当中进行解剖&…

【LeetCode热题100】--148.排序链表

148.排序链表 对链表进行排序最适合的算法就是归并排序&#xff1a; 对链表自顶向下归并排序的过程&#xff1a; 找到链表的中点&#xff0c;以中点为分界&#xff0c;将链表拆分成两个子链表&#xff0c;寻找链表的中点可以使用快慢指针的做法&#xff0c;快指针每次移动 2步…

c++三大概念要分清--重载,隐藏(重定义),覆盖(重写)

目 录 一、重载 **&#xff08;1&#xff09;概念&#xff1a;**在同一个作用域内&#xff1b;函数名相同&#xff0c;参数列表不同&#xff08;参数个数不同&#xff0c;或者参数类型不同&#xff0c;或者参数个数和参数类型都不同&#xff09;&#xff0c;返回值类型可相同也…

leetCode 122.买卖股票的最佳时机 II 贪心算法

122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&…

【mysql】—— 复合查询

前言&#xff1a; 在前面我已经讲解的mysql表的查询都是对一张表进行查询&#xff0c;在实际开发中这远远不够。因此&#xff0c;本期我将带大家学习的是关于 “ 复合查询” 的相关知识&#xff01;&#xff01;&#xff01; 目录 &#xff08;一&#xff09;基本查询回顾 &a…

解决mac pro 连接4k显示器严重发烫、卡顿问题

介绍个不用花钱的方法。其实mac自带的风扇散热能力还可以的&#xff0c;但是默认比较懒散&#xff0c;可以用一个软件来控制下&#xff0c;激发下它的潜能。 可以下个stats软件 打开传感器开关&#xff0c;以及同步控制风扇开关 以及cpu显示温度 点击控制台上的温度图标&…

MySQL 面试题——数据库理论基础

目录 1.什么是数据库、数据库管理系统、数据库系统、数据库管理员&#xff1f;2.什么是元组、码、候选码、主码、外码、主属性、非主属性&#xff1f;3.什么是 ER 图&#xff1f;它有哪几个要素组成&#xff1f;4.主键与外键有什么区别&#xff1f;5.✨为什么不推荐使用外键与级…

简单走近ChatGPT

目录 一、ChatGPT整体背景认知 &#xff08;一&#xff09;ChatGPT引起关注的原因 &#xff08;二&#xff09;与其他公司的竞争情况 二、NLP学习范式的发展 &#xff08;一&#xff09;规则和机器学习时期 &#xff08;二&#xff09;基于神经网络的监督学习时期 &…

深入浅出剖析 LoRA 源码及实践

在上一篇中&#xff0c;我们详细阐述了LoRA的原理。在本篇中&#xff0c;我们将一起学习LoRA源码&#xff08;微软原版&#xff09;。 许多朋友在使用LoRA的过程中&#xff0c;都会用到HuggingFace Peft库封装好的LoRA接口&#xff0c;这个接口是对微软版LoRA代码的改写和封装…

全国核辐射检测数据月度表

特别说明 吸收剂量是单位质量受照物质所吸收的平均电离辐射能量&#xff0c;单位是J/kg。 专门名词是戈瑞&#xff08;Gray&#xff09;&#xff0c;符号“Gy”&#xff0c;1Gy 1J/kg。 这是个很大的单位。 因此在实际应用时&#xff0c;往往用其千分之一或百万分之一作单位&a…

【Java 进阶篇】使用 JDBC 更新数据详解

在关系型数据库中&#xff0c;更新数据是一项常见的任务。通过Java JDBC&#xff08;Java Database Connectivity&#xff09;&#xff0c;我们可以使用Java编程语言来执行更新操作&#xff0c;例如修改、删除或插入数据。本文将详细介绍如何使用JDBC来进行数据更新操作&#x…

《XSS-Labs》01. Level 1~10

XSS-Labs 索引Level-1题解 Level-2题解 Level-3题解总结 Level-4题解 Level-5题解总结 Level-6题解 Level-7题解 Level-8题解 Level-9题解 Level-10题解 靶场部署在 VMware - Win7。 靶场地址&#xff1a;https://github.com/do0dl3/xss-labs 只要手动注入恶意 JavaScript 脚本…

多目标平衡优化器黏菌算法(MOEOSMA)求解CEC2020多模式多目标优化

多目标平衡优化器黏菌算法&#xff08;MOEOSMA&#xff09;比现有的多目标黏菌算法具有更好的优化性能。在MOEOSMA中&#xff0c;动态系数用于调整勘探和开采趋势。采用精英存档机制来促进算法的收敛性。使用拥挤距离法来保持Pareto前沿的分布。采用平衡池策略模拟黏菌的协同觅…

开放式耳机怎么选择、300之内最好的耳机推荐

开放式耳机凭借不入耳、不伤耳、安全更舒适的佩戴体验&#xff0c;得到了越来越多音乐爱好者和专业人士的青睐。开放式耳机不需要插入耳道&#xff0c;在佩戴时可以更加自然和轻松&#xff0c;减少了长时间佩戴引起的不适感&#xff0c;而且不会完全隔绝外界声音&#xff0c;用…

手机号码格式校验:@PhoneQuery(作为查询参数)(自定义参数校验注解)

目标 自定义一个用于校验&#xff08;作为查询参数的&#xff09;手机号码格式的注解PhoneQuery&#xff0c;能够和现有的 Validation 兼容&#xff0c;使用方式和其他校验注解保持一致。 校验逻辑 可以为 null 或 空字符串&#xff1b;不能包含空格&#xff1b;必须为数字序…

Mysql主从复制数据架构全面解读

大家好&#xff0c;我是山子&#xff0c;今天给大家分析Mysql 实现主从复制的方方面面&#xff0c;主从复制当然也是我们做读写分离的前提&#xff0c;以下内容是从各网络平台摘录整理总结归纳在一起的&#xff1b;内容已经从主从复制的各方面的维度进行了阐述&#xff1b;非常…

百元价位开放式耳机哪款好、百元耳机性价比最高的开放式耳机推荐

现如今越来越多的人选择开放式耳机&#xff0c;因为开放式耳机具有多重优点&#xff0c;首先是舒适性。由于它们不需要插入耳道&#xff0c;长时间佩戴也不会产生不适感。其次&#xff0c;开放式耳机在保持与外界的连接上表现出色&#xff0c;使得户外活动更加安全。另外&#…

【Spring Cloud】基于 Feign 实现远程调用,深入探索 Feign 的自定义配置、性能优化以及最佳实践方案

前言 在微服务架构中&#xff0c;服务之间的通信是至关重要的&#xff0c;而远程调用则成为实现这种通信的一种常见方式。在 Java 中&#xff0c;使用 RestTemplate 是一种传统的远程调用方式&#xff0c;但它存在一些问题&#xff0c;如代码可读性差、编程体验不一致以及参数…

笔记二:odoo搜索、筛选和分组

一、搜索 1、xml代码 <!--搜索和筛选--><record id"view_search_book_message" model"ir.ui.view"><field name"name">book_message</field><field name"model">book_message</field><field…