欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 线程

程序员文章站 2022-05-04 17:24:14
...

1. 前言

此文为学习笔记,不是很详细,还望理解,有错也希望各位及时指出。详细可以参考《Java 并发编程的艺术》

2. 什么是 Java 多线程

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

多线程:指的是这个程序(一个进程)运行时产生了不止一个线程

并行与并发:

  • 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
  • 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

Java 线程

这里定义和线程相关的另一个术语 - 进程:

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

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

3. 线程的生命

3.1 生命周期及状态

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

Java 线程

  • 新建状态(NEW):

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 运行状态(RUNNABLE):

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。

    操作系统隐藏 Java虚拟机(JVM)中的 READYRUNNING 状态,它只能看到 RUNNABLE 状态(图源:HowToDoInJava:Java Thread Life Cycle and Thread States),所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。

  • 阻塞状态(BLOCKED):

    表示线程阻塞于锁。

  • 等待状态(WAITING):

    表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程趋一些特定动作(通知或中断)。

  • 超时等待状态(TIME_WAITING):

    该状态不同于WATING,它是可以在指定的时间自行返回的。

  • 终止状态(TERMINATED):

    表示当前线程已经执行完毕。

3.2 创建线程

Java 提供了三种创建线程的方法:

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 Callable 和 Future 创建线程。

这里先只写一下前两种,第三种涉及与线程池使用,暂时不写

public class MyThread {
    public static void main(String[] args) {
        new Thread(new RunnableImplThread(), "RunnableImplThread").start();

        new Thread(new ThreadExtend(),"ThreadExtend").start();
    }


    static class RunnableImplThread implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }

    static class ThreadExtend extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

/*
运行结果:
RunnableImplThread
ThreadExtend
*/

三种创建方法想深入了解的话可以参考:

Java 多线程编程

3.3 加入线程Thread.join()

Thread API 包含了等待另一个线程完成的方法:join() 方法。当调用 Thread.join() 时,调用线程将阻塞,直到目标线程完成为止。

Thread.join() 通常由使用线程的程序使用,以将大问题划分成许多小问题,每个小问题分配一个线程。等待线程终止,Happens-Before 规则中也有规定。

Happens-Before规则

3.4 线程中断

中断一个进程是通过调用该线程的 interrupt() 方法来完成的,真正的实现也是一个 native 方法 interrupt0(),该方法设置线程的中断标志位为有效,然后线程的某些方法检测到中断就会抛出 InterruptException 并清除中断标志位。Thread 的 isInterrupt 方法只有在设置中断但未响应时才会返回 true

随着 run() 方法的执行完毕,线程就会正常终止。如果想要提前终止线程可以使用比较优雅的方式:

中断或者 boolean 变量检测:

class Runner implements Runnable {
    private static volatile boolean on = true;

    @Override
    public void run() {
        while (on && !Thread.currentThread().isInterrupted()) {
            // TODO
        }
    }

		public void cancel() {
        on = false;
    }
}

3.5 结束线程

线程会以以下三种方式之一结束:

  • 线程到达其 run() 方法的末尾。
  • 线程抛出一个未捕获到的 ExceptionError
  • 另一个线程调用一个弃用的 stop() 方法。弃用是指这些方法仍然存在,但是您不应该在新代码中使用它们,并且应该尽量从现有代码中除去它们。

当 Java 程序中的所有线程都完成时,程序就退出了。

3.6 休眠和等待

3.6.1 休眠

Thread API 包含了一个 sleep() 方法,它将使当前线程进入等待状态,直到过了一段指定时间,或者直到另一个线程对当前线程的 Thread 对象调用了 Thread.interrupt(),从而中断了线程。当过了指定时间后,线程又将变成可运行的,并且回到调度程序的可运行线程队列中。

如果线程是由对 Thread.interrupt() 的调用而中断的,那么休眠的线程会抛出 InterruptedException,这样线程就知道它是由中断唤醒的,就不必查看计时器是否过期。

Thread.yield() 方法就象 Thread.sleep() 一样,但它并不引起休眠,而只是暂停当前线程片刻,这样其它线程就可以运行了。在大多数实现中,当较高优先级的线程调用 Thread.yield() 时,较低优先级的线程就不会运行。

3.6.2 等待/通知机制

等待/通知机制的相关方法是任意 Java 对象都具备的,这些方法都定义在 java.lang.Object 中.

方法名称 描述
notify() 随机唤醒等待队列中等待同一共享资源的 “一个线程”,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个线程”
notifyAll() 使所有正在等待队列中等待同一共享资源的 “全部线程” 退出等待队列,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现
wait() 使调用该方法的线程释放共享资源锁,然后从运行状态退出,进入等待队列,直到被再次唤醒
wait(long) 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回
wait(long,int) 对于超时时间更细力度的控制,可以达到纳秒
public class WaitNotify {
    static class MyList {
        private static List<String> list = new ArrayList<>();

        public static void add() {
            list.add("something");
        }

        public static int size() {
            return list.size();
        }
    }

    static class ThreadA implements Runnable {
        private final Object lock;

        public ThreadA(Object lock) {
            super();
            this.lock = lock;
        }

        @Override
        public void run() {
            try {
                synchronized (lock) {
                    if (MyList.size() != 5) {
                        System.out.println("wait() " + System.currentTimeMillis());
                        lock.wait();
                        System.out.println("wait() end " + System.currentTimeMillis());
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class ThreadB implements Runnable {
        private final Object lock;

        public ThreadB(Object lock) {
            super();
            this.lock = lock;
        }

        @Override
        public void run() {
            try {
                synchronized (lock) {
                    for (int i = 0; i < 10; i++) {
                        MyList.add();
                        if (MyList.size() == 5) {
                            lock.notify();
                            System.out.println("已发出通知!");
                        }
                        System.out.println("添加了" + (i + 1) + "个元素!");
                        Thread.sleep(1000);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {

        try {
            Object lock = new Object();

            new Thread(new ThreadA(lock)).start();

            Thread.sleep(50);

            new Thread(new ThreadB(lock)).start();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

运行结果

wait() 1575620403794
添加了1个元素!
添加了2个元素!
添加了3个元素!
添加了4个元素!
已发出通知!
添加了5个元素!
添加了6个元素!
添加了7个元素!
添加了8个元素!
添加了9个元素!
添加了10个元素!
wait() end 1575620413846

注意:从运行结果,我们可以看出,notify() 或 notifyAll() 执行后并不会立即释放锁,需要调用的 notify() 或 nitify() 的线程线程释放锁后,等待线程才会从 wait() 返回。线程状态从 WATING 变成 BLOCKED。

3.6.3 sleep()和wait()的区别

  1. wait()是Object的方法,而sleep()是Thread的方法。
  2. sleep()方法不会释放锁,可以定义时间,时间过后会自动唤醒。wait()方法会释放锁。
  3. sleep()不会释放资源,wait()进入线程等待池等待,出让系统资源,其它线程可以占用CPU。一般 wait()不会加时间限制,这是因为如果 wait()线程运行的资源不够,要等待其它线程调用 notify()或 notifyAll()唤醒等待池中所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒,如果时间不到,只能用 interrupt()强行打断。
  4. wait()、notify()、notifyAll()只能在同步控制方法或同步控制块中使用,而 sleep()可以任何地方使用。
  5. sleep()必须捕获异常,wait()、notify()、notifyAll()则不用。

3.7 ThreadLocal

ThreadLocal 提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程

JDK 8 之前是每个 ThreadLocal 类都创建一个 Map,然后用 threadID 作为 Map 的 key,要存储的局部变量作为 Map 的 value,这样就能达到各个线程的值隔离的效果

JDK8 的设计是:每个 Thread 维护一个 ThreadLocalMap 哈希表,这个哈希表的 key 是 ThreadLocal实例本身,value 才是真正要存储的值 Object

这样有两个好处:

  1. 这样设计之后每个 Map 存储的 Entry 数量就会变小,因为之前的存储数量由 Thread 的数量决定,现在是由 ThreadLocal 的数量决定
  2. Thread 销毁之后,对应的 ThreadLocalMap 也会随之销毁,能减少内存的使用

4. 小结&参考资料

小结

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

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

Java 多线程对于 Java 开发程序猿来说真的非常非常重要。得深刻地理解其中的知识点,并熟练的使用。

Java 线程

参考资料

相关标签: Java多线程