美文网首页
Java多线程

Java多线程

作者: ZoranLee | 来源:发表于2021-03-11 16:57 被阅读0次

    进程

    • 进程是程序运行资源分配的最小单位
    • 资源:包括CPU、内存空间、 磁盘 IO 等
    • 进程可以分为系统进程和用户进程

    线程

    • 线程是 CPU 调度的最小单位,必须依赖于进程而存在
    • 线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的、 能独立运行的基本单位。
    • 线程无处不在

    核心数、线程数

    核心数、线程数:目前主流 CPU 都是多核的。增加核心数目就是为了增加线 程数,因为操作系统是通过线程来执行任务的,一般情况下它们是 1:1 对应关系,也 就是说四核 CPU 一般拥有四个线程。但 Intel 引入超线程技术后,使核心数与线程 数形成 1:2 的关系

    CPU时间片轮转机制

    • https://baike.baidu.com/item/%E6%97%B6%E9%97%B4%E7%89%87%E8%BD%AE%E8%BD%AC%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95/7170554

    • 时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称 RR 调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。

    • 如果在时间片结束时进程还在运行,则 CPU 将被剥夺并分配给另一个进程。 如果进程在时间片结束前阻塞或结来,则 CPU 当即进行切换。

    • 时间片设得太短会导致过多的进程切换,降低了 CPU 效率: 而设得太长又可能引起对短的交互请求的响应变差。将时间片设为 100ms 通常 是一个比较合理的折衷。

    并行和并发

    • 并发: 一定要加个单位时间,也就是说单位时间内并发量是多少? 离开了单位时间其实是没有意义的。指应用能够交替执行不同的任务,比如单 CPU 核心下执行多线程并非是 同时执行多个任务

    • 并行: 指应用能够同时执行不同的任务。例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行

    两者区别:一个是交替执行,一个是同时执行.

    高并发编程

    • 充分利用 CPU 的资源
    • 加快响应用户的时间
    • 可以使你的代码模块化,异步化,简单化

    多线程程序注意事项

    • 线程之间的安全性
      在同一个进程里面的多线程是资源共享的,也就 是都可以访问同一个内存地址当中的一个变量。例如:若每个线程中对全局变量、 静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的:若有多 个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

    • 线程之间的死锁
      为了解决线程之间的安全性引入了 Java 的锁机制,而一不小心就会产生 Java 线程死锁的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成

    • 线程太多了会将服务器资源耗尽形成死机宕机

    某些系统资源是有限的,如文件描述符。多线程程序可能耗尽资源,因为每个 线程都可能希望有一个这样的资源。如果线程数相当大,或者某个资源的侯选线 程数远远超过了可用的资源数则最好使用资源池。一个最好的示例是数据库连接 池。只要线程需要使用一个数据库连接,它就从池中取出一个,使用以后再将它返 回池中。资源池也称为资源库。

    Java线程

    import java.lang.management.ManagementFactory;
    import java.lang.management.ThreadInfo;
    import java.lang.management.ThreadMXBean;
    public static void main(String[] args) {
     ThreadMXBean threadMXBean =   
     ManagementFactory.getThreadMXBean();
    ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
    for (ThreadInfo threadInfo : threadInfos) {
                System.out.println("args = " + threadInfo.getThreadId()+""+threadInfo.getThreadName());
            }
    }
    
    • [6] Monitor Ctrl-Break //监控 Ctrl-Break 中断信号的
    • [5] Attach Listener //内存 dump,线程 dump,类信息统计,获取系统属性等
    • [4] Signal Dispatcher // 分发处理发送给 JVM 信号的线程
    • [3] Finalizer // 调用对象 finalize 方法的线程
    • [2] Reference Handler//清除 Reference 的线程
    • [1] main //main 线程,用户程序入口

    线程生命周期

    image.png

    线程的启动

    • 启动线程的方式有两种
      1、X extends Thread;,然后 X.start
      2、X implements Runnable;然后交给 Thread 运行

    Thread 才是 Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑)
    的抽象。
    Thread 可以接受任意一个 Runnable 的实例并执行。

    线程中止

    • stop
      暂停、恢复和停止操作对应在线程 Thread 的 API 就是 suspend()、resume()和 stop()。但是这些 API 是过期的,也就是不建议使用的。不建议使用的原因主 要有:以 suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如 锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方 法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资 源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为 suspend()、 resume()和 stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方 法。

    • 中断

    安全的中止则是其他线程通过调用某个线程 A 的 interrupt()方法对其进行中 断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表 线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。 因为 java 里的线程是协作式的,不是抢占式的。

    线程通过检查自身的中断标志 位是否被置为 true 来进行响应,线程通过方法 isInterrupted()来进行判断是否被中断,也可以调用静态方法 Thread.interrupted()来进行判断当前线程是否被中断,不过 Thread.interrupted() 会同时将中断标识位改写为 false。

    如果一个线程处于了阻塞状态(如线程调用了 thread.sleep、thread.join、 thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在 这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即 将线程的中断标示位清除,即重新设置为 false。

    不建议自定义一个取消标志位来中止线程的运行。因为run方法里有阻塞调 用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取 消标志。

    这种情况下,使用中断会更好,因为,
    一、一般的阻塞方法,如 sleep 等本身就支持中断的检查 。
    二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。

    注意:处于死锁状态的线程无法被中断

    run() 和 start()

    Thread类是Java里对线程概念的抽象,我们通过new Thread() 其实只是 new 出一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。
    只有执行了 start()方法后,才实现了真正意义上的启动线程

    start()方法让一个线程进入就绪队列等待分配 cpu,分到 cpu 后才调用实现 的 run()方法,start()方法不能重复调用,如果重复调用会抛出异常。

    而run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方 法并没有任何区别,可以重复执行,也可以被单独调用。

    其他的线程相关方法

    • yield()方法:
      使当前线程让出 CPU 占有权,但让出的时间是不可设定的。也 不会释放锁资源。注意:并不是每个线程都需要这个锁的,而且执行 yield( )的线 程不一定就会持有锁,我们完全可以在释放锁后再调用 yield 方法。

    所有执行 yield()的线程有可能在进入到就绪状态后会被操作系统再次选中 马上又被执行。

    • wait()/notify()/notifyAll();

    • join 方法
      把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。
      比如在线程 B 中调用了线程 A 的 Join()方法,直到线程 A 执行完毕后,才会继续 执行线程 B。(此处为常见面试考点)

    线程的优先级

    在 Java 线程中,通过一个整型成员变量 priority 来控制优先级,优先级的范 围从 1~10,在线程构建的时候可以通过 setPriority(int)方法来修改优先级,默认 优先级是 5,优先级高的线程分配时间片的数量要多于优先级低的线程。

    设置线程优先级时,针对频繁阻塞(休眠或者 I/O 操作)的线程需要设置较 高优先级,而偏重计算(需要较多 CPU 时间或者偏运算)的线程则设置较低的 优先级,确保处理器不会被独占。在不同的 JVM 以及操作系统上,线程规划会 存在差异,有些操作系统甚至会忽略对线程优先级的设定。

    守护线程

    Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调 度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在非 Daemon 线程的 时候,Java 虚拟机将会退出。可以通过调用 Thread.setDaemon(true)将线程设置 为 Daemon 线程。我们一般用不上,比如垃圾回收线程就是 Daemon 线程。

    Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线 程中的 finally 块并不一定会执行。在构建 Daemon 线程时,不能依靠 finally 块中 的内容来确保执行关闭或清理资源的逻辑。

    线程间的共享

    • synchronized 内置锁
      线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码 一步一步地执行,直到终止。
      但是,每个运行中的线程,如果仅仅是孤立地运行, 那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作, 包括数据之间的共享,协同处理事情。这将会带来巨大的价值。

    Java 支持多个线程同时访问一个对象或者对象的成员变量,关键字 synchronized 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线 程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量 访问的可见性和排他性,又称为内置锁机制。

    • 对象锁和类锁
      对象锁是用于对象实例方法,或者一个对象实例上的。
      类锁是用于类的静态 方法或者一个类的 class 对象上的。

    类的对象实例可以有很多个,但 是每个类只有一个 class 对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。

    其实类锁只是一个概念上的东西,并不是真实存 在的,类锁其实锁的是每个类的对应的 class 对象。类锁和对象锁之间也是互不 干扰的。

    死锁发生的四个必要条件

    • 互斥条件
      1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

    • 请求保持
      2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

    • 不剥夺
      3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

    • 环路等待
      4)环路等待条件:指在发生死锁时,必然存在一个进程一一资源的环形链,即进程集合{P0,P1,P2,.・.,R}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,..,Pn正在等待已被PO占用的资源。

    只要打破四个必要条件之一就能有效预防死锁的发生。

    打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。

    打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。

    打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。

    打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。

    避免死锁常见的算法有有序资源分配法、银行家算法。

    1、多个操作者(M>=2)争夺多个资源(N>=2), N<=M
    2、争夺资源顺序不对
    3、拿到资源不放手

    解决

    • 关键是保证拿锁的顺序一致
      两种解决方式:
      1、内部通过顺序比较,确定拿锁的顺序;
      2、采用尝试拿锁的机制。

    其他线程安全问题

    • 活锁

    两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一个线程总是拿到同一把锁,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。

    解决办法:每个线程休眠随机数,错开拿锁的时间。

    • 线程饥饿
      低优先级的线程,总是拿不到执行时间。

    相关文章

      网友评论

          本文标题:Java多线程

          本文链接:https://www.haomeiwen.com/subject/hovnqltx.html