Java多线程编程核心技术读书笔记一
条评论题记
在B站上看到马士兵老师讲解美团研发死亡七连问的视频,讲课中听到马老师的一句鸡汤,“往深了学,往宽了学”,被马老师深厚的技术功底和探求精神折服。遂,翻出了买了很久但是还没有读的《java多线程编程核心技术》和《java并发编程的艺术》,选择了简单入门的《java多线程编程核心技术》读了起来。之前买了没有去看,因为觉得并发编程对我来说太深了,我还没到那个层次,所以书都没有打开过。这次在家待业,学了一些基础的jvm原理,不再恐惧了。翻出来,一天看了2章,并没有觉得看不懂。恩,我可能是低估了自己能力。所以计划4天把书看完,并整理出笔记。先看书,再行笔记。看书过程中,如果有看不懂或理解不上来的,直接就敲了代码了。所以本笔记,没有代码。
并发问题的来源
本节内容纯属自编自导,非教程内容,不做质保。
最根本的原因是cpu运算速度远超其他存储设备,每一次脉冲就是它的一次总线宽度的二进制信号变换,变换的规则都烙印在了硬件当中,为了便于控制,形成了很多叫指令的命令(多次脉冲组合),有些指令是原子的不可再分,有些则可以再分开(不知道描述是否准确)。
进程(线程)和CPU的关系是,被CPU执行,进程和线程是被动的,它要达到什么目的,只能写到自己身上,安静的等着CPU来调度的时候由CPU来解析后被执行。个人觉得理解这点很重要。
人们为了挖掘计算机最大的算力,而设计了一套多线程机制,多个线程一起干活。多线程之间存在“公共资源”的情况。多线程的运行机制大致是,以java为例,代码编译成字节码,加载到jvm成为指令,指令被jvm翻译给cpu执行。这些个线程中的代码指令集合(编码后最终的底层指令)就开始等待cpu的时间片来宠幸自个。但是对于这些个指令来说,它是没有权利控制CPU什么时候来找你的。这就导致了,虽然线程内部的指令执行是线性的,但是多线程和线程之间的指令可就不是有顺序的,它们之间彼此交叉,是随机的,这种随机推进一步就是线程告知CPU的信息也是随机的。既然出现了顺序的随机性,在“公共资源”的情形下,就需要同步机制来控制线程的执行顺序执行。至于为什么要同步……恩,哲学问题。线程之间同步信息方可行为达预期。
一句话,时间片分配的随机性、共同资源需要顺序执行是并发问题的主要来源。高并发下,只要高级语言中的代码被转换成的指令不是原子的指令,就可能出现线程的同步问题。
第1章 多线程技能
介绍了进程线程的关系,入门级的,就不记了。以后会深化理解。
多线程的使用姿势
怎么申明多线程。
- extend Thread 覆盖run方法
- implement Runnable 实现run方法
Thread的构造法中支持实现了Runnble接口的类,恩,面向抽象的体现。
一些常见方法:
- Thread.currentThread()可以获取当前线程。
- isAlive() 判断线程是否处于活动状态
- Thread.sleep() 休眠(暂停执行)当前线程,不会让出锁
- getId()获取线程的唯一标识
停止线程需要说的。
- 不能暴力停止线程
- 判定线程是否是中断的方法this.interrupted()和this.isInterrupted()的区别是,前者是静态方法,测试状态后,状态会被清除。
- 抛出throw new InterruptedException()可以停止线程(接受到interrupted后,线程持续一小会不会即可停止,而抛出异常可以达到即可停止的目的)
- 使用return来停止线程,不过最佳实践还是抛异常,使得异常得以传播
线程暂停和恢复
- suspend() 和resume()方法,暂停会独占锁,易导致不同步
- yield()告诉过来调度自己的cpu,我现在要放弃一次被调用的时间片,cpu就放弃这次调用。但是,很可能下一次调用又过来了。
线程优先级
- setPriority()是设置优先级的方法
- 优先级越高,代表被调度的概率高,但不代表调用的顺序就一定优先。因为cpu的时间片是随机分配的,只是优先级高的被分配的概率高。
守护线程
- 线程分二,用户和守护
- setDaemon(true)设置为守护线程,负责兜底,只要分出去的线程没有结束,就吧会结束,分出去的线程都结束了,才结束。
第2章 对象及变量的并发访问
本章节主要讲了synchronized关键字的魔性用法。没有提及“监视器”在jvm或内存中长什么样。
方法内的变量是线程安全的,因为是线程私有的,jvm中在方法栈中,方法执行完就回收了。
几句话说清楚synchronized
关键字synchronized取得锁是对象锁,而不是把一段代码或方法当作锁。用它来保证顺序。统一对象中,没有加synchronized的方法,多线程的执行顺序是不确定的。
synchronized可以用在方法、、代码块synchronized(this){…},那么所有带了synchronized的都需要竞争对象的锁。他们之间是竞争关系。
synchronized可以用在静态方法或静态成员变量上,但是锁的就不是对象,而是类了。尽管效果类似,但本质不同。类锁,锁住所有类的对象实例。
在方法内部如果使用synchronized(object ){…},object为其他的类,则相当于新开了一把锁,和代码所在的对象不是一把锁。
抛出异常,锁自动释放。
锁重入的概念,已经取得对象锁的线程,不必再去竞争锁,自动载入,防止了死锁。(我写了个demo,方法自己调用自己,方法上加了synchronized)。
死锁的一种情况。需要同一把锁的方法相互调用了对方。
synchronized用在方法上的弊端
锁方法,方法内部如果有耗时的语句,那么锁就长期得不到释放。说白了,锁得范围不够精准所致。替代办法是,方法内synchronized同步代码块来操作,没有synchronized的长时间代码异步,计算完再去争锁执行。
synchronized(“string”)和常量池
synchronized作用在String类型上时,是锁的常量池,常量池在jvm的方法取。等价于所有的String都共享常量池的锁,等价于锁不同的String,锁只有一把,大家也是互斥的。
被忽略的synchronized的作用
synchronized用在方法内部,有volatile同步线程私有变量和公众堆栈变量的作用。
volatile
关键字volatile的作用是强制从公共堆栈中取变量值,而不是从线程私有数据栈中取。这个关键字和线程同步没唯一关系是保证数据对多线程的可见性,但是不能保证其修饰的数据操作的原子性。
线程安全的核心
多线程并发,着重“外练互斥,内修可见”。
- 原子性
- 可见性
- 原子性基础上的业务顺序
小知识点
- i– 和++i,指令集上不是原子操作的。i=i+1;
- i++形式的原子操作在 java.util.concurrent.atomic包下有不同的实现