Java 线程

1. 前言

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

2. 什么是 Java 多线程

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

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

并行与并发:

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

并行和并发

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

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

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

3. 线程的生命

3.1 生命周期及状态

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

线程的生命周期

  • 新建状态(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多线程总结

参考资料

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值