0%

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包下有不同的实现