Java基础18-一文搞懂Java多线程使用方式、实现原理以及常见面试题(一)

简介: Java基础18-一文搞懂Java多线程使用方式、实现原理以及常见面试题(一)

Java中的线程

Java之父对线程的定义是:

线程是一个独立执行的调用序列,同一个进程的线程在同一时刻共享一些系统资源(比如文件句柄等)也能访问同一个进程所创建的对象资源(内存资源)。java.lang.Thread对象负责统计和控制这种行为。

每个程序都至少拥有一个线程-即作为Java虚拟机(JVM)启动参数运行在主类main方法的线程。在Java虚拟机初始化过程中也可能启动其他的后台线程。这种线程的数目和种类因JVM的实现而异。然而所有用户级线程都是显式被构造并在主线程或者是其他用户线程中被启动。

      本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。在这之前,首先让我们来了解下在操作系统中进程和线程的区别:复制代码
      进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)复制代码
      线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)复制代码
      线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。复制代码
      多进程是指操作系统能同时运行多个任务(程序)。复制代码
      多线程是指在同一程序中有多个顺序流在执行。复制代码
    在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用复制代码

Java线程状态机

Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

一个线程的生命周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

下图显示了一个线程完整的生命周期。

  • 新建状态:
    使用 **new** 关键字和 **Thread** 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 **start()** 这个线程。复制代码
  • 就绪状态:
    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。复制代码
  • 运行状态:
    如果就绪状态的线程获取 CPU 资源,就可以执行 **run()**,此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。复制代码
  • 阻塞状态:
    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:复制代码
    *   等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。复制代码
    *   同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。复制代码
    *   其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。复制代码
  • 死亡状态:
    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
## Java多线程实战
## 多线程的实现复制代码
    public class 多线程实例 {复制代码
        //继承thread
        @Test
        public void test1() {
            class A extends Thread {
                @Override
                public void run() {
                    System.out.println("A run");
                }
            }
            A a = new A();
            a.start();
        }复制代码
        //实现Runnable
        @Test
        public void test2() {
            class B implements Runnable {复制代码
                @Override
                public void run() {
                    System.out.println("B run");
                }
            }
            B b = new B();
            //Runable实现类需要由Thread类包装后才能执行
            new Thread(b).start();
        }复制代码
        //有返回值的线程
        @Test
        public void test3() {
            Callable callable = new Callable() {
                int sum = 0;
                @Override
                public Object call() throws Exception {
                    for (int i = 0;i < 5;i ++) {
                        sum += i;
                    }
                    return sum;
                }
            };
            //这里要用FutureTask,否则不能加入Thread构造方法
            FutureTask futureTask = new FutureTask(callable);
            new Thread(futureTask).start();
            try {
                System.out.println(futureTask.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }复制代码
        //线程池实现
        @Test
        public void test4() {
            ExecutorService executorService = Executors.newFixedThreadPool(5);
            //execute直接执行线程
            executorService.execute(new Thread());
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("runnable");
                }
            });
            //submit提交有返回结果的任务,运行完后返回结果。
            Future future = executorService.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return "a";
                }
            });
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }复制代码
            ArrayList<String> list = new ArrayList<>();
            //有返回值的线程组将返回值存进集合
            for (int i = 0;i < 5;i ++ ) {
                int finalI = i;
                Future future1 = executorService.submit(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        return "res" + finalI;
                    }
                });
                try {
                    list.add((String) future1.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
            for (String s : list) {
                System.out.println(s);
            }
        }
    }复制代码

线程状态转换

    public class 线程的状态转换 {
    //一开始线程是init状态,结束时是terminated状态
    class t implements Runnable {
        private String name;
        public t(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            System.out.println(name + "run");
        }
    }复制代码
    //测试join,父线程在子线程运行时进入waiting状态
    @Test
    public void test1() throws InterruptedException {
        Thread dad = new Thread(new Runnable() {
            Thread son = new Thread(new t("son"));
            @Override
            public void run() {
                System.out.println("dad init");
                son.start();
                try {
                    //保证子线程运行完再运行父线程
                    son.join();
                    System.out.println("dad run");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //调用start,线程进入runnable状态,等待系统调度
        dad.start();
        //在父线程中对子线程实例使用join,保证子线程在父线程之前执行完复制代码
    }复制代码
    //测试sleep
    @Test
    public void test2(){
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t1 run");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });复制代码
        //主线程休眠。进入time waiting状态
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.start();复制代码
    }复制代码
    //线程2进入blocked状态。
    public static void main(String[] args) {
        test4();
        Thread.yield();//进入runnable状态
    }复制代码
    //测试blocked状态
    public static void test4() {
        class A {
            //线程1获得实例锁以后线程2无法获得实例锁,所以进入blocked状态
            synchronized void run() {
                while (true) {
                    System.out.println("run");
                }
            }
        }
        A a = new A();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t1 get lock");
                a.run();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t2 get lock");
                a.run();
            }
        }).start();复制代码
    }复制代码
    //volatile保证线程可见性
    volatile static int flag = 1;
    //object作为锁对象,用于线程使用wait和notify方法
    volatile static Object o = new Object();
    //测试wait和notify
    //wait后进入waiting状态,被notify进入blocked(阻塞等待锁释放)或者runnable状态(获取到锁)
    public void test5() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //wait和notify只能在同步代码块内使用
                synchronized (o) {
                    while (true) {
                        if (flag == 0) {
                            try {
                                Thread.sleep(2000);
                                System.out.println("thread1 wait");
                                //释放锁,线程挂起进入object的等待队列,后续代码运行
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("thread1 run");
                        System.out.println("notify t2");
                        flag = 0;
                        //通知等待队列的一个线程获取锁
                        o.notify();
                    }
                }
            }
        }).start();
        //解释同上
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (o) {
                        if (flag == 1) {
                            try {
                                Thread.sleep(2000);
                                System.out.println("thread2 wait");
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("thread2 run");
                        System.out.println("notify t1");
                        flag = 1;
                        o.notify();
                    }
                }
            }
        }).start();
    }复制代码
    //输出结果是
    //    thread1 run
    //    notify t2
    //    thread1 wait
    //    thread2 run
    //    notify t1
    //    thread2 wait
    //    thread1 run
    //    notify t2
    //不断循环复制代码
    }
## Java Thread常用方法复制代码

yield():

执行此方法会向系统线程调度器(Schelduler)发出一个暗示,告诉其当前JAVA线程打算放弃对CPU的使用,但该暗示,有可能被调度器忽略。使用该方法,可以防止线程对CPU的过度使用,提高系统性能。

Thread#sleep(time)或Thread.sleep(time, nanos):

使当前线程进入休眠阶段,状态变为:TIME_WAITING

Thread.interrupt():

中断当前线程的执行,允许当前线程对自身进行中断,否则将会校验调用方线程是否有对该线程的权限。

如果当前线程因被调用Object#wait(),Object#wait(long, int), 或者线程本身的join(), join(long),sleep()处于阻塞状态中,此时调用interrupt方法会使抛出InterruptedException,而且线程的阻塞状态将会被清除。

interrupted(),返回true或者false:

查看当前线程是否处于中断状态,这个方法比较特殊之处在于,如果调用成功,会将当前线程的interrupt status清除。所以如果连续2次调用该方法,第二次将返回false。

Thread.isInterrupted(),返回true或者false:

与上面方法相同的地方在于,该方法返回当前线程的中断状态。不同的地方在于,它不会清除当前线程的interrupt status状态。

join(time):

A线程调用B线程的join()方法,将会使A等待B执行,直到B线程终止。如果传入time参数,将会使A等待B执行time的时间,如果time时间到达,将会切换进A线程,继续执行A线程。


Java基础18-一文搞懂Java多线程使用方式、实现原理以及常见面试题(二):https://developer.aliyun.com/article/1535712

目录
相关文章
|
1月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
162 60
【Java并发】【线程池】带你从0-1入门线程池
|
17天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
67 23
|
1月前
|
数据采集 Java Linux
面试大神教你:如何巧妙回答线程优先级这个经典考题?
大家好,我是小米。本文通过故事讲解Java面试中常见的线程优先级问题。小明和小华的故事帮助理解线程优先级:高优先级线程更可能被调度执行,但并非越高越好。实际开发需权衡业务需求,合理设置优先级。掌握线程优先级不仅能写出高效代码,还能在面试中脱颖而出。最后,小张因深入分析成功拿下Offer。希望这篇文章能助你在面试中游刃有余!
46 4
面试大神教你:如何巧妙回答线程优先级这个经典考题?
|
24天前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
94 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
1月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
136 14
|
1月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
61 13
|
1月前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
4月前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
189 4
|
6月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
574 37
|
5月前
|
存储 安全 Java
java基础面试题
java基础面试题
50 2