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

目录
相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
15 2
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
23 9
|
4天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
7天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
17 1
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
41 1
C++ 多线程之初识多线程
|
22天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
16 3
|
22天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
15 2
|
22天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
22天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1