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

目录
相关文章
|
4天前
|
安全 Java 程序员
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
41 11
|
3天前
|
Java Linux 调度
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
21 6
|
12天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
63 17
|
23天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
8天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
23天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
23天前
|
Java 调度
|
25天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
56 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
71 1
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
48 3

热门文章

最新文章