`
阅读更多

说明:本篇文章是在阅读《Java 并发编程艺术》过程中的一些笔记和分析,由于本人能力有限,如果有书写错误的地方,欢迎各位大佬批评指正!我们互相交流,学习,共同进步!

该项目的地址:https://github.com/xiaoheng1/concurrent-programming

1.现代操作系统调度的最小单位是线程(轻量级进程)

2.为什么要使用多线程了?肯定是为了快. 目前电脑的 CPU 核心数越来越多,如果将没有数据依赖的程序拆分成多个线程,然后放到多个核心上跑,是不是
比程序在单核上跑耗时更短了?
但是值得注意的是:**一个线程在一个时刻,只能运行在一个处理器核心上**.

3.现代操作系统一般采用时分的形式调度,操作系统会分出一个一个的时间片,线程会分配到若干个时间片,当线程的时间片用完了就会发生线程调度,
并且等着下次分配.
换句话说,一个线程能分配到的时间片越多,那么就能占用更长的 CPU 资源,就能干跟多的活. 那什么决定一个线程能够分配多少时间片了?

答案是线程的优先级. 但是我在网上看到有人说,线程的优先级在更大的可能上让优先级高的线程先执行,低优先级的线程在大概率上后执行. 也有人
说线程的优先级高的获得更多的 CPU 资源(获得更多的时间片). 其实这两种说法是不矛盾的. 获得很多的时间片,也就意味着在大概率上先执行.

在 java 线程中,通过一个 priority 变量来控制优先级,优先级的范围是从 1~10. 可以在线程构造时设置,也可以通过 setPriority() 方法
设置线程优先级. 默认的线程优先级是 5.

在设置线程优先级时,针对平凡阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的
优先级,确保处理器不会被独占. 如果理解了?可以这么理解,I/O操作一般执行时间较短,而偏重计算型的任务一般需要大量的时间. 如果将本来就
耗时的操作还分配跟多的时间片,那么 CPU 将会被偏重计算的线程占有,其他很快能够执行完的线程一直等待(不能及时响应). 现在反过来,让耗时
短的任务先执行,那么偏重计算型的任务则不需要等待那么长的时间(能够更快的响应).

但是需要注意的是:**在不同的 JVM 以及操作系统上,有些系统会忽略对线程优先级的设定**.


4.线程的状态

关于这点可能有的小伙伴有疑问了?线程状态不是只有 5 种状态吗?为啥会有 6 种状态了?其实 5 种状态是早期进程的状态.

1.创建状态:创建进程过程非常复杂,首先需要申请一个空白的 PCB,并向线程中写入用于控制和管理进程的信息. 然后为其分配必要的资源,
  最后把线程加入到就绪队列中
2.就绪状态:就绪状态就是说该线程已经准备好运行,只要给我时间片,我就能运行.
3.运行状态:指的是已经获取 CPU 的进程,目前处于执行状态.
4.阻塞状态:指的是正在执行的进程由于发生某件事,例如 I/O 操作等,暂时无法执行,这个状态称为阻塞状态.
5.终止状态:终止状态指的是线程的最终状态,要么线程执行完,要么由于无法解决的错误,被操作系统终止,或者被有权限终止的线程终止.

转换规则:

1.创建状态 -> 就绪状态
     获得时间片
2.就绪状态 -> 运行状态
     时间片用完
3.运行状态 <- 就绪状态
       I/O操作
4.运行状态 -> 阻塞状态
     I/O操作完成
5.阻塞状态 -> 就绪状态

线程的 6 大状态

1.初始状态 —— NEW(线程刚被创建,但是还没有调用 start 方法)
2.运行状态 —— RUNNABLE(Java 线程将就绪和运行两种状态统称为运行中)
3.阻塞状态 —— BLOCKED(阻塞状态,表示线程阻塞于锁)
4.等待状态 —— WAITING(表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定的动作,通知或者中断)
5.超时等待状态 —— TIME_WAITING(该状态不同于 WAITING,它可以在指定的时间自行返回)
6.终止状态 —— TERMINATED(终止状态,表示当前线程已经执行完毕)


转换规则:

       调用 start 方法
1.创建状态 -> 运行状态(就绪)
            获得时间片
2.运行状态(就绪) -> 运行状态(运行中)
   等待进入 sync
3.运行状态 -> 阻塞
    获取到锁
4.阻塞 -> 运行状态
    sleep(100)/Object.wait(100)等
5.运行状态 -> 超时等待状态
       notify/unpark
6.超时等待状态 -> 运行状态
   wait/join/park
7.运行状态 -> 等待状态
     notify/unpark
8.等待状态 -> 运行状态

参考:https://www.zhihu.com/question/56494969

注意的是:只有进入 synchronized 关键字修饰的方法或代码块时,才会是阻塞状态,其他状态都是等待状态. 这样很好理解,Java 中 Lock 的
实现在没有获取到锁的情况下,调用的是 park 方法进行阻塞,这个就是等待状态.

5.守护线程

(1)当不存在非守护线程的时候,java 虚拟机将退出.
(2)可以使用 setDaemon(true) 来设置守护线程,但是要在线程启动前设置,如果线程启动后设置会报错.
(3)Daemon 线程中的 finally 块在 Java 虚拟机退出的时候并不一定会执行. 换句话说,当不存在守护线程的时候,所有的守护线程
**立即终止**,所以,守护线程中的 finally 块可能来不及执行.

6.新线程的创建
(1)新线程是有其 parent 线程来构造的,子线程继承父线程的 daemon, 优先级,contextClassLoader 以及可继承的 ThreadLocal,同时
还会分配一个唯一的 ID 来标识此线程.

7.中断
(1)中断可以理解为线程的一个标识位属性,它标识一个运行中的线程是否被其他线程进行了中断操作. 中断线程好比其他线程对这个线程打了个招呼,
其他线程调用该线程的 interrupt() 方法对其进行中断操作.
(2)被中断线程通过检查自己是否被中断来进行响应. 可以通过 isInterrupted() 方法来进行检查.
(3)可以通过调用 Thread.isInterrupted() 来对当前线程的中断标志进行复位.

注意:**如果该线程已经处于终结状态,即使该线程被中断过,调用 isInterrupted() 方法依旧会返回 false**.

但是我们经常会看到如下写法:就是一个方法在抛出异常前,会将该线程的中断标志位清除,然后抛出异常. 例如 Thread.sleep() 方法. 为什么
要这么设计了?可以这么想,如果说只要中断了,Thread.sleep() 方法就直接抛出异常(不进行复位操作), 那么下一次其他线程在进行中断,该
方法是不是就不能响应中断了?所以,你会发现,JDK 在实现的时候,会有好多的小细节值得我们思考.

8.suspend & resume & stop
(1)suspend 挂起
(2)resume 恢复
(3)stop 终止

这三个 API 已经过期,不建议使用. 为什么了?suspend 调用后,线程不会释放已经占有的资源. 如果先执行了 resume,然后再调用 suspend,
那么将会造成线程无限挂起. 为什么弃用 stop 了?因为 stop() 方法在终结一个线程时不会保证线程的资源正常释放,通常没有给线程释放资源
的聚会,因此导致程序可能工作在不确定的状态下.

上面的方法可以通过 wait/notify 或 lock/conditon 来代替.


9.安全的终止线程

中断只是一个标志,如果程序不响应中断的话,那么你调用 interrupt() 方法是不起作用的. 中断一般用来取消任务,同时也可以使用一个标志位
来停止或者终止任务.

例如:

class A {
    private static class Count implements Runnable {
        private volatile boolean on = true;
        private int i;
        public void run(){
            while(on && !Thread.currentThread().isInterrupted()){
                i++;
            }
            System.out.println("i= " + i);
        }
        public void cancle(){
            if(on){
                on = false;
            }
        }
    }
}

注意:**前提是程序要响应中断,如果程序中没有使用 Thread.isInterrupted 来判断,则中断将会不起作用,一定要理解中断只是一个标志**.

同时这种方式将会更加的优雅(相比较 stop 而言)


10.线程间的通信

(1) 使用 volatile
(2) 使用 synchronized
(3) wait/notify
(4) lock/condition

在使用 wait/notify 时有一些注意事项:
(1) wait/notify/notifyAll 必须位于 synchronized 中
(2) 调用 wait 方法后,线程状态由 RUNNING -> WAITING
(3) notify/notifyAll 方法调用后,等待线程依旧不会从 wait() 方法返回,需要等待调用 notify/notifyAll 的线程释放锁后,等待线程
才有机会从 wait 返回.
(4) notify 方法将一个等待线程从等待队列中移到同步队列中,被移动的线程状态由 WAITING -> BLOCKED.
(5) 从 wait 返回的条件是获得锁.

具体步骤:

A: synchronized(obj){
    obj.wait();
}

B: synchronized(obj){
    obj.notify();
}

(1)首先线程 A 获得锁,在同步代码块中调用 obj.wait 方法,释放锁,进入到等待队列,线程状态由 RUNNING -> WAITING.
(2)线程B获得锁,在同步代码块中调用 obj.notify 方法,唤醒 A. A 从等待队列移动到同步队列,且线程状态由 WAITING -> BLOCKED.
(3)线程A获得锁,并从 wait 方法返回.

管道输入/输出流

PipedOutputStream/PipedInputStream/PipedReader/PipedWriter 用于线程间的数据传递. 方式:内存为媒介.

如何实现的了?
(1) PipedWriter 和 PipedReader 连接.
(2) 一个线程在 PipedWriter 写入,一个线程从 PipedReader 读取,这样就可以实现通讯了.

注意:**Piped 类型的流,必须先进行绑定(调用 connect) 方法,如果没有进行绑定,则会在使用的过程中抛出异常**.

这样很好理解,比如说我们在读写文件的时候,input 和 output 是如何进行关联了?答案是不是文件?所以此处进行绑定的道理是一样的.

11.Thread.join

Thread.join 的含义是:当前线程等待 thread 线程终止后,才从 join 方法返回. 同时 thread 中的共享变量对当前线程可见.

12.ThreadLocal

每个线程内部持有一个 ThreadLocalMap 的东西,而我发现 ThreadLocal 内中 nextHashCode 为静态变量,这就意味着该变量为所有 ThreadLocal 锁共有.

现在考虑一种极端情况,有两个 ThreadLocal 实例:ThreadLocalA 和 ThreadLocalB. 两个线程:ThreadA 和 ThreadB.

ThreadLocalA 存有线程A和线程B的数据. 反应到底层的数据结构是:

ThreadA.ThreadLocalMap<ThreadLocalA, Value>
ThreadB.ThreadLocalMap<ThreadLocalA, Value>

现在 ThreadLocalB 也存有线程A和线程B的数据. 反应到底层的数据结构是:

ThreadA.ThreadLocalMap<ThreadLocalB, Value>
ThreadB.ThreadLocalMap<ThreadLocalB, Value>

这时候,nextHashCode 就起作用了,每个 threadLocal 的 threadLocalHashCode 不同(存在一个神奇的 hash 值:0x61c88647),具体情况自行百度.

所以ThreadA 和 ThreadB 中存放这两个值的时候很大概率不会出现冲突. 这也是为啥 threadLocalHashCode 是 final 修饰,而 nextHashCode 是 static 修饰的原因.


13.线程应用实例

ps:

public void synchronized get(long timeout) {
    long future = Thread.currentTimeMills() + timeout;
    long remaining = timeout;
    while(remaining > 0){
        wait(timeout);
        remaining = future - Thread.currentTimeMills();
    }
}


**这进行系统设计的时候,特别是针对稀缺资源的获取,例如数据库连接,应该使用超时获取这样的设计,这样在获取不到的时候,线程不会一直挂在
连接获取的操作上,而是按时返回,并告知客户端连接获取出现问题,这是一种系统的自我保护机制**.


线程不是创建的越多越好,线程越多,那么系统在进行上下文切换的时候,已经线程的创建和销毁,都会耗费大量的时间,所以需要使用到线程池
技术.

那么如何设计线程池了?

1.创建的线程数量应当有限
2.应当有一个任务队列,用于存放提交的任务.
3.应当有一个工人队列,用于执行提交的任务.

所以线程池的设计很清晰了,1创建工人(Worker & Thread),启动,并从任务队列中读取任务,执行. 2用户将任务提交到线程池.当任务队列中
没有任务的时候,该如何处理了?答案是 Worker 休眠
0
0
分享到:
评论

相关推荐

    Java并发编程基础.pdf

    Java并发编程基础主要包括以下几个核心方面: 线程与线程状态:理解Java中线程的基本概念,包括线程的创建、启动、暂停、恢复和终止。熟悉线程的生命周期及其不同状态,如新建、就绪、运行、阻塞和死亡。 线程同步...

    JAVA并发编程艺术 高清pdf

    JAVA并发编程艺术 高清pdf : 1.并发变成的挑战 2. java并发机制的底层实现原理 3. java 内存模型 4. java并发编程基础 5.java中的锁。。。。。。。

    《Java并发编程的艺术》

    《Java并发编程的艺术》内容涵盖Java并发编程机制的底层实现原理、Java内存模型、Java并发编程基础、Java中的锁、并发容器和框架、原子类、并发工具类、线程池、Executor框架等主题,每个主题都做了深入的讲解,同时...

    Java并发编程的艺术

    , 《Java并发编程的艺术》内容涵盖Java并发编程机制的底层实现原理、Java内存模型、Java并发编程基础、Java中的锁、并发容器和框架、原子类、并发工具类、线程池、Executor框架等主题,每个主题都做了深入的讲解,...

    java并发编程基础PPT以及DEMO示例&操作系统概述PPT

    NULL 博文链接:https://lingqi1818.iteye.com/blog/644940

    Java并发编程实战

    1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 1.2.2 建模的简单性 1.2.3 异步事件的简化处理 1.2.4 响应更灵敏的用户界面 1.3 线程带来的风险 1.3.1 安全性问题 1.3.2 活跃性问题 1.3.3 ...

    java并发编程从入门到精通

    《Java并发编程从入门到精通》作者结合自己10多年Java并发编程经验,详细介绍了Java并发编程的基础概念、工作原理、编程技巧和注意事项,对Java高性能高并发编程有极大的参考价值。 《Java并发编程从入门到精通》...

    Java并发编程Xmind思维导图

    Java并发编程Xmind思维导图,思路更清晰。内容来自《Java并发编程的艺术》,包括并发机制底层原理、Java内存模型、Java并发编程基础、锁机制、线程池、并发工具类、原子操作类、并发容器和框架。纯手打,非诚勿扰。

    java并发编程面试题

    java并发编程 基础知识,守护线程与线程, 并行和并发有什么区别? 什么是上下文切换? 线程和进程区别 什么是线程和进程? 创建线程有哪几种方式?,如何避免线程死锁 线程的 run()和 start()有什么区别? 什么是 ...

    Java 并发核心编程

    自从java创建以来就已经支持并发的理念,如线程和锁。这篇指南主要是为帮助java多线程开发人员理解并发的核心概念以及如何应用这些理念。...开发者通过这些基础的接口可以构建高并发、线程安全的java应用程序。

    Android面试整理之java基础并发编程思维脑图

    囊括了Android面试中的java多线程知识,包括线程的基础、threadLoca|、并发编程中的锁 JMM synchronized关键字 以及部分垃圾回收机制

    Java并发编程常识-梁飞.rar

    Java并发编程常识.ppt,阿里大牛梁飞编写,非常实用,欢迎大家想学习

    JAVA并发编程实践

    JAVA并发编程实践,对基础好的,这秘籍便可打通任督二脉,多线程开发小case的。

    JAVA 并发编程实战

    Java并发编程实战,本书从线程的基础知识,包括线程安全性,对象共享以及对象组合灯,到结构化并发编程,都提供了很详细的讲解。

    读书笔记-Java并发编程实战-基础篇

    读书笔记-Java并发编程实战-基础篇

    Java并发编程实践 PDF 高清版

    本书的读者是那些具有一定Java编程经验的程序员、希望了解Java SE 5,6在线程技术上的改进和新特性的程序员,以及Java和并发编程的爱好者。 目录 代码清单 序 第1章 介绍 1.1 并发的(非常)简短历史 1.2 线程的...

Global site tag (gtag.js) - Google Analytics