`
阅读更多

说明:本篇文章是在阅读《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并发编程基础PPT以及DEMO示例&操作系统概述PPT

    在本资源中,我们有两个主要的学习材料:一个关于“Java并发编程基础”的PPT和一个包含DEMO示例,另一个是“操作系统概述”的PPT。这些资料对于理解Java多线程编程以及操作系统的基础原理至关重要。 首先,让我们...

    Java 并发编程实战.pdf

    ### Java并发编程基础 - **并发与并行**:并发是指多个任务在同一时间段内被执行(但不一定同一时刻),而并行则是指多个任务同时执行。理解这两者的区别对于深入学习并发编程至关重要。 - **Java并发机制**:Java...

    JAVA并发编程艺术 高清pdf

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

    《Java并发编程的艺术》

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

    JAVA并发编程实践 中文 高清 带书签 完整版 Doug Lea .pdf

    ### 一、Java并发编程基础 #### 1.1 并发与并行的概念 - **并发**:指多个事件在同一时间段内发生。 - **并行**:指多个事件在同一时刻发生。 在Java并发编程中,通常会涉及多个线程共享资源并同时运行的情况,...

    JAVA并发编程实践

    ### 一、Java并发编程基础 #### 1.1 并发与并行 - **并发**:指在同一时间段内处理多个任务的能力。 - **并行**:指同一时刻处理多个任务的能力。并行依赖于多处理器或多核处理器的支持。 #### 1.2 Java并发工具类...

    JAVA并发编程实战.pdf

    ### Java并发编程基础 #### 1. 并发与并行 - **并发(Concurrency)**:指一个程序中存在多个执行序列(如线程)在逻辑上同时执行。 - **并行(Parallelism)**:指多个处理器同时执行不同的任务或指令,通常涉及硬件...

    《java 并发编程实战高清PDF版》

    总之,《Java并发编程实战》是一本全面介绍Java并发编程的书籍,适合有一定Java基础并希望提升并发编程能力的开发者阅读。通过学习,你可以掌握解决并发问题的策略和技巧,编写出更加健壮和高效的多线程应用。

    Java并发编程的艺术

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

    java并发编程实践

    #### 一、Java并发编程基础 ##### 1.1 并发与并行概念区分 在Java并发编程实践中,首先需要理解“并发”与“并行”的区别。“并发”指的是多个任务同时进行,但实际上可能是在多线程环境下通过交替执行的方式实现的...

    JAVA并发编程实践.pdf

    ### Java并发编程基础 #### 1. 并发与并行 - **并发**:指的是多个任务在同一时间段内被执行(可能不是同一时刻)。 - **并行**:指的是多个任务在同一时刻被执行。 在Java中,并发主要是通过多线程来实现的,而...

    java并发编程实战(英文版)

    本书不仅适合已经有一定Java编程经验的开发人员阅读,也适用于希望深入了解并发编程基础的新手。它从最基本的并发概念讲起,如进程与线程的区别、线程生命周期、线程调度策略等,并逐步深入到锁机制、死锁避免、原子...

    java并发编程内部分享PPT

    此外,Java并发编程还包括对并发容器的使用,如ArrayList、LinkedList、HashSet、HashMap等基础容器在并发环境下可能存在问题,Java提供了一些线程安全的容器,如Vector、HashTable以及java.util.concurrent包下的...

    Java并发编程实战华章专业开发者书库 (Tim Peierls 等 美Brian Goetz).pdf

    《Java并发编程实战》提供了深入浅出的指导,涵盖了从基础理论到高级技术的广泛内容。 第一部分介绍了并发编程的基础,包括线程安全性的概念,如何构建线程安全的类,以及Java平台提供的并发工具如线程、同步机制等...

    java 并发编程的艺术pdf清晰完整版 源码

    通过阅读《Java并发编程的艺术》这本书,开发者不仅可以掌握Java并发编程的基础知识,还能了解到一些高级特性和技巧,从而在实际开发中游刃有余。同时,附带的源码将有助于加深理解,提供实际操作的机会。

    java并发编程书籍

    Java并发编程是软件开发中的一个关键领域,尤其是在大型企业级应用和分布式系统中。通过学习相关的书籍,开发者可以深入理解如何有效地设计和实现高效的多线程应用程序,避免并发问题,如竞态条件、死锁、活锁等。...

    JAVA并发编程实践.pdf+高清版+目录 书籍源码

    《JAVA并发编程实践》这本书是Java开发者深入理解并发编程的重要参考资料。它涵盖了Java并发的核心概念、工具和最佳实践,旨在帮助读者在多线程环境下编写高效、安全的代码。 并发编程是现代软件开发中的关键技能,...

Global site tag (gtag.js) - Google Analytics