java executor多线程同时运行其中一个找到结果退出_Java并发知识点快速复习手册(上)...

本文回顾了Java并发知识,包括线程状态转换、Executor服务、线程中断机制、互斥同步及线程协作。讲解了线程池如ThreadPoolExecutor和ScheduledThreadPoolExecutor的使用,以及Lock接口、synchronized和ReentrantLock的比较。还探讨了线程间的协作方法,如join、wait/notify和Condition。
摘要由CSDN通过智能技术生成

94a25c9eb4887c5e4d2c8f3161631ef0.png

前言

本文快速回顾了常考的的知识点,用作面试复习,事半功倍。

知乎阅读体验不好,请移步Csdn:https://blog.csdn.net/qqxx6661/article/details/86726537

面试知识点复习手册

已发布知识点复习手册

  • Java基础知识点面试手册
  • 快速梳理23种常用的设计模式
  • Redis基础知识点面试手册
  • Java容器(List、Set、Map)知识点快速复习手册

参考

本文内容参考自CyC2018的Github仓库:CS-Notes

https://github.com/CyC2018/CS-Notes/

有删减,修改,补充额外增加内容

d57f5c275e0d6b59675ce5e0aaef5984.png


本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

线程状态转换

b988819f2b76a3dcac72e815659a3399.png

新建(New)

创建后尚未启动。

可运行(Runnable)

可能正在运行,也可能正在等待 CPU 时间片。

包含了操作系统线程状态中的 Running 和 Ready。

阻塞(Blocking)

等待获取一个排它锁,如果其线程释放了锁就会结束此状态。

无限期等待(Waiting)

等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。

| 进入方法 | 退出方法 | | --- | --- | | 没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() | | 没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 | | LockSupport.park() 方法 | - |

限期等待(Timed Waiting)

无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。

调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。

调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。

睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。

阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁

而等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。

| 进入方法 | 退出方法 | | --- | --- | | Thread.sleep() 方法 | 时间结束 | | 设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() | | 设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 | | LockSupport.parkNanos() 方法 | - | | LockSupport.parkUntil() 方法 | - |

死亡(Terminated)

可以是线程结束任务之后自己结束,或者产生了异常而结束。

使用线程

有三种使用线程的方法:

  • 实现 Runnable 接口
  • 实现 Callable 接口
  • 继承 Thread 类

实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。

实现 Runnable 接口

需要实现 run() 方法。

通过 Thread 调用 start() 方法来启动线程。

public class MyRunnable implements Runnable {
    
    public void run() {
    
        // ...
    }
}
public static void main(String[] args) {
    
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    thread.start();
}

实现 Callable 接口

Callable就是Runnable的扩展。

与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。

public class MyCallable implements Callable<Integer> {
    
    public Integer call() {
    
        return 123;
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

继承 Thread 类

同样也是需要实现 run() 方法,并且最后也是调用 start() 方法来启动线程。

public class MyThread extends Thread {
    
    public void run() {
    
        // ...
    }
}
public static void main(String[] args) {
    
    MyThread mt = new MyThread();
    mt.start();
}

其他方法

严格说不能算方法,只能算实现方式:

  • 匿名内部类
  • 线程池

实现接口 VS 继承 Thread

实现接口会更好一些,因为:

  • Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
  • 类可能只要求可执行就行,继承整个 Thread 类开销过大。
  • 代码可以被多线程共享,数据独立,很容易实现资源共享

start和run有什么区别?

详细解释:https://blog.csdn.net/lai_li/article/details/53070141?locationNum=13&fps=1

start方法:

  • 通过该方法启动线程的同时也创建了一个线程,真正实现了多线程。无需等待run()方法中的代码执行完毕,就可以接着执行下面的代码
  • 此时start()的这个线程处于就绪状态,当得到CPU的时间片后就会执行其中的run()方法。这个run()方法包含了要执行的这个线程的内容,run()方法运行结束,此线程也就终止了。

run方法: - 通过run方法启动线程其实就是调用一个类中的方法,当作普通的方法的方式调用。并没有创建一个线程,程序中依旧只有一个主线程,必须等到run()方法里面的代码执行完毕,才会继续执行下面的代码,这样就没有达到写线程的目的。

线程代码示例

package cn.thread.test;

/*
 * 设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。
 */
public class ThreadTest1 {

    private int j;

    public static void main(String[] args) {
        ThreadTest1 tt = new ThreadTest1();
        Inc inc = tt.new Inc();
        Dec dec = tt.new Dec();


        Thread t1 = new Thread(inc);
        Thread t2 = new Thread(dec);
        Thread t3 = new Thread(inc);
        Thread t4 = new Thread(dec);
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }

    private synchronized void inc() {
        j++;
        System.out.println(Thread.currentThread().getName()+"inc:"+j);
    }

    private synchronized void dec() {
        j--;
        System.out.println(Thread.currentThread().getName()+"dec:"+j);
    }

    class Inc implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                inc();
            }
        }
    }

    class Dec extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                dec();
            }
        }
    }
}

基础线程机制

Executor线程池

https://segmentfault.com/a/1190000014741369#articleHeader3

Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。异步是指多个任务的执行互不干扰,不需要进行同步操作。

  • 当前线程池大小 :表示线程池中实际工作者线程的数量;
  • 最大线程池大小 (maxinumPoolSize):表示线程池中允许存在的工作者线程的数量上限;
  • 核心线程大小 (corePoolSize ):表示一个不大于最大线程池大小的工作者线程数量上限。

如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队;

如果运行的线程等于或者多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不是添加新线程;

如果无法将请求加入队列,即队列已经满了,则创建新的线程,除非创建此线程超出 maxinumPoolSize, 在这种情况下,任务将被拒绝。

不用线程池的弊端

  • 线程生命周期的开销非常高
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值