多线程核心基础

1 进程和线程

​ 进程是OS分配资源的最进本的单位,线程是执行调度的最基本单位。分配资源最重要的是:独立的内存空间,线程调度执行(线程共享进程的内存空间,没有自己的独立空间)。
JVM线程与操作系统的线程是一一对应的,在JVM中启动一个线程,会交给操作系统启动一个线程。
纤程:用户太的线程,线程中的线程,切换和调度不需要经过OS。优势:占有资源很少,可以启动很多个(10W+)。目前支持向纤程的语言:GO、Python(lib)。Java目前不支持纤程。

2 实现多线程

//方法一:继承Thread
static class ThreadExtends extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ":T1");
        }
    }

//方法二:实现Runnable接口
static class ThreadImplRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":T2");
    }
}

// 方式3 实现Callble接口(线程执行带返回值的)
    class MyRun implements Callable{
        @Override
        public String call() throws Exception {
            return "success";
        }
    }
    @Test
    public void testCall(){
        MyRun t1 = new MyRun();
        try {
            String call = t1.call();
            System.out.println(call);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
//方式4 使用线程池创建(带返回值)
    public void threadPool(){
        ExecutorService pool = Executors.newCachedThreadPool();
        pool.execute(()->{
            System.out.println("hello ThreadPool");
        });
        Future future = pool.submit(() -> {
            return "success";
        });
        try {
            //阻塞
            String msg = future.get();
            System.out.println(msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
//方式5 使用FutureTask创建(带返回值)
    @Test
    public void testFutureTask(){
        FutureTask task = new FutureTask<>(()->{
            return  "success";
        });
        new Thread(task).start();
        try {
            //阻塞
            String msg = task.get();
            System.out.println(msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

3 启动线程

​ 线程启动使用start()方法。为什么不能用run()方法?start()会启动新线程执行(不能重复start),run()直接当前线程启动,不会使用新线程执行。

    //错误演示:使用run启动线程
    public static void main(String[] args) {
        ThreadExtends thread1 = new ThreadExtends();
        new Thread(thread1).run();
        System.out.println(Thread.currentThread().getName() + ":Main");
    }
//输出:
//  main:T1
//  main:Main
//结论:使用run()方法启动,执行线程为main线程,并非是thread1线程,并没有使用多线程执行

    //正确使用start()启动线程
    public static void main(String[] args) {
        ThreadExtends thread1 = new ThreadExtends();
        new Thread(thread1).start();
        System.out.println(Thread.currentThread().getName() + ":Main");
    }
//输出:
//  main:Main
//  Thread-1:T1
//结论:使用start方法启动线程,会开启多个线程执行,从而达到多线程执行的目的。

//jdk8使用Lambda表达式启动线程,实际也是通过实现Runnable接口
new Thread(()->{
    System.out.println(Thread.currentThread().getName() + ":T3");
}).start();

4 线程常用方法

4.1 Thread类中线程常用重要方法

  • sheep():sleep方法可以让线程进入waiting状态,并且不占用CPU资源,但是 不释放锁,直到规定时间后再执行,休眠期间如果被中断,会爆出异常并清除中断状态。
  • yield():短暂让出CPU进入排队队列。
  • join(): 因为其他线程加入了我们,所以我们要等他执行完了再出发。例:在A线程中执行B.join()即 让B线程执行完后再执行A线程。 原理:自己进入waiting状态,加入的线程执行后自动notifyAll()。
    @Test
    public void testJoin() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("T1执行");
            System.out.println("T1结束");
        });
        Thread t2 = new Thread(() -> {
            try {
                System.out.println("T2执行");
                t1.join();
                System.out.println("T2结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t3 = new Thread(() -> {
            try {
                System.out.println("T3执行");
                t2.join();
                System.out.println("T3结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t3.start();
        t1.start();
        t2.start();
        /**
         * 执行结果:
         * T3执行
         * T2执行
         * T1执行
         * T1结束
         * T2结束
         * T3结束
         * 结论:无论谁先抢到执行,都需要等T1执行结束后T2才能执行结束,最后T3执行结束。
         */
    }

4.2 Object类中线程常用重要方法

wait(),notify(), notifyAll()必须拥有monitor,属于Object类,和Condition类似。
notify唤醒

  • notify方法只应该被拥有该对象的monitor的线程调用;
  • 一旦线程被唤醒,线程便会从对象的”等待线程集合”中被移除,所以可以重新参与到线程调度当中
  • 要等刚才执行notify的线程退出被synchronized保护的代码并释放monitor
    wait等待以下情况之一才会被唤醒
  • 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程;
  • 另一个线程调用这个对象的notifyAll()方法;
  • 过了wait(long timeout)规定的超时时间,如果传入0就是永久等待;
  • 线程自身调用了interrupt()

4.3 Condition中常用方法

Condition是个接口,基本的方法就是await()和signal()方法;Condition 依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()。调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用。Condition中的await() 对应Object的wait();Condition中的signal() 对应Object的notify();Condition中的signalAll()对应Object的notifyAll()。

/**
 * 实现一个固定容量同步容器,拥有put和get方法,以及getCount方法.

 * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
 */
public class WaitAndNotilfy {
    List list = new ArrayList();
    //容器最大容量
    private final static int MAX = 10;
    private static int COUNT = 0;
    Lock lock = new ReentrantLock();
    //生产者
    Condition producer = lock.newCondition();
    //消费者
    Condition consumer = lock.newCondition();

    public Object get() {
        Object o = null;
        try {
            lock.lock();
            while (list.size() == 0) {
                System.err.println("消费者暂停");
                consumer.await();
            }
            o = list.get(0);
            list.remove(0);
            COUNT--;
            //通知生产者生产
            producer.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return o;
    }

    public void put(Object o) {
        try {
            lock.lock();
            while (list.size() == MAX) {
                System.err.println("生产者暂停");
                producer.await();
            }
            list.add(o);
            ++COUNT;
            //通知生产者生成
            consumer.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        return COUNT;
    }

    public static void main(String[] args) {
        WaitAndNotilfy c = new WaitAndNotilfy();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 1; j  {
                for (int j = 1; j

5 线程6个状态

  • new:刚创建线程还没有启动;
  • Runnable:可运行;
  • Blocked:被阻塞;
  • Waiting:等待;
  • Timed waiting:限期等待;
  • Terminated:终止
    一般习惯而言,把Blocked(被阻塞)、Waiting(等待)、Timed_waiting(计时等待)都称为阻塞状态。
    多线程核心基础

; 6 停止线程

通常情况下,我们不会手动停止一个线程,而是允许线程运行到结束,然后让它自然停止。但是依然会有许多特殊的情况需要我们提前停止线程,比如:用户突然关闭程序,或程序运行出错重启等。在这种情况下,即将停止的线程在很多业务场景下仍然很有价值。尤其是我们想写一个健壮性很好,能够安全应对各种场景的程序时,正确停止线程就显得格外重要。但是Java 并没有提供简单易用,能够直接安全停止线程的能力。

6.1 interrupt介绍

  • interrupt() 设置线程打断标志位
  • isInterrupted() 查询某线程是否被打断(查询表示位)
  • static interrupted() 查询当前线程是否被打断过,并重置打断标志
    @Test
    public void testInterrupted() throws InterruptedException {
        Thread t = new Thread(()->{
            while (true){
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("Thread is interrupted!");
                    System.out.println(Thread.currentThread().isInterrupted());
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(1000);
        t.interrupt();
    }
    /**
     * 输出:
     *  Thread is interrupted!

     *  true
     * 结论:isInterrupted()查询不重置标志位
     */

    @Test
    public void testInterrupted2() throws InterruptedException {
        Thread t = new Thread(()->{
            while (true){
                if(Thread.interrupted()){
                    System.out.println("Thread is interrupted!");
                    System.out.println(Thread.interrupted());
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(1000);
        t.interrupt();
        //当前线程
        System.out.println("main:"+t.isInterrupted());
    }
    /**
     * 输出:
     *  main:false
     *  Thread is interrupted!

     *  false
     * 结论:Thread.interrupted() 会重置标志位
     */

注意:interrupted不能中断正在竞争锁的线程。如果允许打断,在线程加锁时Lock的lockInterruptibly()加锁,当interrupted对该线程设置标志位的时候,该线程会保InterruptedException异常。

6.2 使用interrupt停止线程

对于 Java 而言, 最正确的停止线程的方式是使用 interrupt。但 interrupt 仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。

** 使用interrupt中断线程**
    @Test
    public void testInterruptNormal() throws InterruptedException {
        Thread t = new Thread(()->{
            while (!Thread.interrupted()){

            }
            System.out.println("t end");
        });
        t.start();
        Thread.sleep(500);
        t.interrupt();
    }

一旦调用某个线程的 interrupt() 之后,这个线程的中断标记位就会被设置成 true。每个线程都有这样的标记位,当线程执行时,应该定期检查这个标记位,如果标记位被设置成true,就说明有程序想终止该线程。

public class StopThread implements Runnable {
    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println(count++);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}
// 运行结果,输出没有到1000会被main线程中断
当线程处于sheep或await状态时,被其他线程interrupt,会报java.lang.InterruptedException
public class StopThread implements Runnable {

    @Override
    public void run() {
        int count = 0;
        try {
            while (!Thread.currentThread().isInterrupted() && count < 1000) {
                if(count==10){
                    Thread.sleep(10);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}
//运行结果:输出10个数后报异常

多线程核心基础

7获取子线程执行结果

查看Runnable接口可以发现,run()方法的返回是void,且未声明为抛出任何已检查的异常,而咱们实现并重写这个方法,自然也不能返回值,也不能抛出异常,因为在对应的Interface / Superclass中没有声明它。
Runnable为什么设计成这样?
如果run()方法可以返回值,或者可以抛出异常,也无济于事,因为我们并没办法在外层捕获并处理,这是因为调用run()方法的类(比如Thread类和线程池)也不是我们编写的。所以如果我们真的想达到这个目的,可以看看下面的补救措施:

  1. 使用Callable,声明了抛出异常并且有返回值;
@FunctionalInterface
public interface Callable {
    /**
     * Computes a result, or throws an exception if unable to do so.

     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}
  1. 用Future来获得子线程的运行结果;
    多线程核心基础

Original: https://www.cnblogs.com/dooor/p/thread.html
Author: Dvomu
Title: 多线程核心基础

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/610377/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 模型层

    准备阶段 django自带的sqlite3数据库,功能很少,并且针对日期类型不精确 准备步骤 数据库正向迁移命令(将类操作映射到表中) python3 manage.py make…

    Linux 2023年6月7日
    097
  • 使用docker 部署mysql,突然连接不上!

    WARNING: IPv4 forwarding is disabled. Networking will not work. 大概意思就是说,网络不能用,也就意味着不能连网络,所…

    Linux 2023年6月7日
    081
  • Tomcat 实现双向SSL认证

    大概思路: 使用openssl生产CA证书,使用keytool生产密钥库 1、生成CA密钥 genrsa [产生密钥命令] -des3 [加密算法] -out[密钥文件输出路径] …

    Linux 2023年6月14日
    085
  • zabbix监控配置

    zabbix监控配置 zabbix监控配置 zabbix通过web界面配置邮件告警 zabbix配置客户端监控 创建主机组 创建监控主机并将主机加入主机组 添加监控项 配置触发器 …

    Linux 2023年6月13日
    0103
  • 正则表达式

    正则表达式 字符 作用 . 点号 匹配任意一个且只有一个字符 [] 匹配[]集合内的任意一个字符 [^] 匹配不包含^后的任意字符 星号 重复前一个字符(连续出现)0次或N次 . …

    Linux 2023年6月6日
    0118
  • 外键,查询关键字

    目录 自增特性 约束条件之外键 *查询关键字 内容 自增特性 自增不会随着数据的删除而回退 删除数据但无法重置主键 truncate 删除数据并重置主键值 约束条件之外键 简介 给…

    Linux 2023年6月7日
    0104
  • ELK 脚本自动化删除索引

    kibana有自带接口,可通过自带的API接口 通过传参来达到删除索引的目的。 删除15天前的索引 curl -XDELETE "http://10.228.81.161…

    Linux 2023年6月8日
    082
  • KMP分析证明

    引用后缀的目的: “ABBABA” 如果说ABA里面组成的AB是答案组成部分的开头,那么AB后面的字符一定是和模式串开头的第三个字符一样,如果不一样一定不是…

    Linux 2023年6月7日
    067
  • 每周一个linux命令(tree)

    安装tree命令 yum install tree -y 显示当前目录下的一级目录结构 tree -L 1 目录信息说明 bin: 系统常用命令所在目录 boot: 系统启动相关的…

    Linux 2023年6月8日
    0105
  • bootstrap的基础使用

    Bootstrap Bootstrap是Twitter推出的一个用于前端开发的开源工具包。它由Twitter的设计师Mark Otto和Jacob Thornton合作开发,是一个…

    Linux 2023年6月7日
    0112
  • Java — 反射

    程序在运行中也可以获取类的变量和方法信息,并通过获取到的信息来创建对象。程序不必再编译期就完成确定,在运行期仍然可以扩展。 示例:学生类 public class Student …

    Linux 2023年6月8日
    0141
  • Redis的slot迁移

    1) 在目标节点B 上执行,从节点A 导入slot 到节点B : CLUSTER SETSLOT8 IMPORTING src– A-node-id 对于迁移的slot…

    Linux 2023年5月28日
    072
  • 零成本搭建个人博客搭建篇

    为什么要搭建个人博客 尽管已经有很多成型的在线博客平台供大家使用(csdn,博客园,掘金等),但是它们都有一些很明显的弊端,例如账号以及博客内容受到监管,所有权不属于作者本人,对于…

    Linux 2023年6月7日
    071
  • 软件危机复习

    没有银弹的含义 软件危机:由于软件规模越来越大,软件复杂性越来越高,可靠性问题也越来越突出,传统的个人设计,个人实现的方式不再满足要求,迫切需要改变软件生产方式,提高软件开发效率,…

    Linux 2023年6月8日
    093
  • Linux 用户密码不能设置问题

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月7日
    0130
  • 中土批量运维神器《ps1屠龙刀》 pk 西域批量运维圣器《ansible圣火令》

    据故老相传,运维界有句话:”脚林至尊,宝刀【ps1屠龙】,号令被控,莫敢不从”。 https://gitee.com/chuanjiao10/kasini3…

    Linux 2023年6月14日
    0119
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球