本文学习资料和灵感来自网络,感谢享学课堂13号技师、开课吧小师妹、汪文君老师、《Java并发编程实战》[Brian Goetz]

一:概述

随着java技术的成熟和工作年限的增长,现在出去面试言必谈“多线程、高并发”,诸如:多线程和高并发是什么关系?什么是线程安全?锁是什么?举个例子,你在工作中是怎么使用多线程的?你们系统的秒并发数秒TPS数达到了多少?每天不知道有多少人跪倒在了面试官的休闲裤下。

坊间流传一句话,面试造航母,工作拧螺丝。然而现实中,拿着造航母的简历却干不好拧螺丝的工作的人比比皆是,其中也包括我自己。学技术,还是希望自己能够多一点脚踏实地,不仅能拧好螺丝,也能在心中勾画出航母,勾画出明天的自己,CODE A BETTER LIFE。

1.1:名词解释

线程:计算机操作(cpu)的基本调度执行单位

进程:类比现实中的一个任务,操作系统管理的基本单位

异步:同步就是串行,异步就是“同时”执行多个任务,线程可以说是一种异步的实现

性能:某一组操作所需要的消耗:执行时间越短性能越好,消耗资源越少性能越好...

原子性:同一时刻某一组操作,只能由同一个线程完成

可见性:线程对变量的修改会马上同步到主内存,其他线程get()到的都是该线程修改后的值

安全:指某个接口、某个类能正确的实现功能

线程安全:多线程下访问某个类,这个类始终能正确的实现功能。

锁:线程安全问题本质上就是对共享变量读写操作使用同步,锁就是实现这一同步方式的统称

内置锁:synchronize,通过对class头的monitor的持有来锁定对象

死锁:由于加锁时序的原因,多个线程相互持有等待对方释放的锁。死循环、死锁、递归栈溢出,都是程序不可自动修复的严重问题。

重入:将获取锁的粒度细化到“线程”而不是“调用”,所有线程都可以抢锁,类及子类可以获取到被持有的锁(递归调用不会死锁,实现:内置一个锁持有者+计数器,获取锁前先比对是不是类及子类,成功获取一次锁+1,释放一次锁-1,计数器为0则释放该锁)

竞态条件:正确的执行结果取决于运气(多线程交替执行时序),多线程安全说到底就是->操作(读+改+写),改和写依赖于读,在改和写的过程中读的结果失效了(被另外线程写入了其他结果)。并发编程书本中的说明是:基于一种可能失效的观察结果来做出判断或者执行某个计算

状态:状态就是指实例变量,无状态就是说:线程间对象不包含其他类中域的引用,计算过程使用的局部变量且只能由正在执行的线程访问,也就是所谓的两个线程没有共享状态,所以线程安全。大部分servlet都是无状态的作为容器存在的,所以servlet本身是线程安全的。

CAS:compare and swap,比较和交换,绝大数锁的实现机制。读+改+写,在写的时候通过内存的方式(原子)获取当前读的值并和之前读的值比较,如果符合期望(没被修改)则成功写入,如果值被改变,则放弃写入。

AQS:AbstractQueuedSynchronizer,用于构建锁和同步器的框架,常用方法有acquire(),tryAcquire(),compareAndSet()

ReentrantLock:jdk提供的一种cas机制的可重入锁

ThreadLocal:线程封闭方式之一,通过内置的get和set方法为每个线程创建一份只属于自己的独立副本,计算操作不受其他线程影响

FutureTask:获取线程的计算结果或者抛出异常,常用作Callable接口的返回类型

CountDownLatch:阻塞线程,当计数器为零时解除所有等待线程的阻塞。常被用于模拟高并发,和FutureTask结合使用有奇效

1.2:现代计算机模型

说到多线程就不得不讲讲JMM(Java Memory Model)以及现代计算机模型。

1946年的情人节(2月14日),世界上第一台计算机(ENIAC)诞生,美国人为了发展弹道导弹技术制造它用来计算导弹轨迹。原始的计算机是通过纸条打洞来实现计算的,从头到尾只执行一个程序。后来产生了能存储程序和程序控制原理的电脑,有了操作系统,我们可以使用操作系统帮助我们每次运行多个程序(任务/进程),通过异步的方式提高了计算机资源的利用效率。软件的发展往往是附属于硬件的,随着cpu的频率提高到达天花板,多处理器系统的出现和普及,进一步提高了异步处理方式需求,的也促进了相较于进程,更细粒度的线程的发展。

撇开进程线程这些绕口的话题,谈回硬件,社会和科学技术发展的基石就是材料学,我们不知道外星人造出来的电脑是什么样子的,但现在的电脑就是目前人类便于利用的地球上材料量产电脑的最优方案。以前课本上计算机内存和磁盘分别叫RAM/ROM,RAM比ROM快但比较小且断电数据就会消失,ROM比较慢但能相对永久的保存信息(磁化),之所以要把电脑分这么多部件也是人们根据地球上已有材料的特性按照现实的需求摸索来的。

我们都知道,cpu是用于计算,磁盘用于存储我们编写的程序,但cpu远远快于磁盘的,如果他们直接交互相,恐怕cpu不得急死。内存存在的意义就是作为cpu和磁盘之间的桥梁,但cpu仍然远远快于内存,因此现代计算机产生了高速缓存(一级缓存,二级缓存,三级缓存),如下图

有两个线程执行某一段代码,从主内存中读取值+1然后更新主内存,理论上两个线程执行两次就应该是2,可是因为cpu执行时序的问题,导致第一个线程读+改+写的完整操作被打断了,最终呈现了错误的结果1

我们可以理解为,我们读操作是在主内存执行的,cpu运行计算的值是存放在高速缓存中的(可以理解为ThreadLocal),volatile所谓的可见性就是强制每次set到高速缓存的值也通过缓存一致性协议通知给其他高速缓存(同步set到主内存中),最终的写操作是在主内存进行的。而所谓的JMM,虽然是在JVM中执行的,但本质上还是cpu和内存配合运行了我们的指令,虽然有JIT指令重排等特殊情况,但基本原理是一样的。

二:Synchronized简单使用和实现原理,原子类简单使用,volatile使用原则

2.1.实现原理

原子类:如AtomicInteger,也是使用的CAS机制,主要使用了Unsafe类里面的native方法,AQS的实现也主要使用Unsafe类

volatile:volatile的特点是保证可见性和禁止指令重排序,不保证原子性。当出现多线程写入时,会有线程安全问题,因此适合一写多读的业务场景,比如全局的flag字段。

synchronized是jdk的内置锁,也是一种可重入锁,使用非常优雅,直接在对应的方法上使用关键字或者synchronized(this){}代码块就可以。他的主要实现原理是通过jvm监视器的方式,获取对象的头部文件,通过monitorenter获取对象的访问权限,通过monitorexit释放对象的访问权限。

从本质上讲,synchronized是一种悲观锁的实现,早期的synchronized性能较低,在jdk1.6以后,内置锁得到了大量的优化,比如轻量级锁、偏向锁、自旋锁、锁消除等,总体上来说就是减少线程的挂起 和恢复开销

轻量级锁:通过对象头的“Mark Word”的分析,在代码进入同步块的时候,虚拟机建一个Lock Record(一开始是空的,加锁成功则存储被加锁的对象头信息Mark Word)的空间来存储Mark Word的拷贝。然后使用CAS操作让要加锁对象的Mark Work指向Lock Record,如果成功则加锁。失败则判断是不是自己,不是自己则锁被其他线程抢占了。实现思想是,在整个同步期间,绝大部分锁是不存在竞争的,用CAS操作去消除同步的互斥量

偏向锁:在锁无竞争的情况下把整个同步消除掉,锁会偏向于第一个获得到他的线程,在接下来的执行过程中,锁如果没有被其他线程获取,则持有偏向锁的线程不会进行同步。当有另外线程尝试获取这个锁时,偏向模式结束,进入轻量级锁模式。

自旋锁:让后面请求锁的线程进入忙循环(默认自旋10次,“等一下”,而不是挂起),这个时间段内很大可能共享数据的锁定状态已经解除。

锁消除:在编译运行时,通过逃逸分析技术,判断代码堆上的数据不会被其他线程访问到,则去除锁

2.2.synchronize简单使用代码示例如下:

package generator;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;public class ThreadDemo {//公共变量int no = 0;static AtomicInteger num = new AtomicInteger(0);//公共方法public synchronized void add(){
//        synchronized (this){
//            no++;
//        }no++;}public static void increment(){num.getAndIncrement();}//线程类,继承Threadprivate static class CountThread extends Thread{private ThreadDemo threadDemo;public CountThread(ThreadDemo threadDemo) {this.threadDemo = threadDemo;}@Overridepublic void run() {for (int i = 0; i <10000 ; i++) {threadDemo.add();increment();}}}//线程类,实现runnableprivate static class CountRunnable implements Runnable{private ThreadDemo threadDemo;public CountRunnable(ThreadDemo threadDemo) {this.threadDemo = threadDemo;}@Overridepublic void run() {for (int i = 0; i <10000 ; i++) {threadDemo.add();increment();}}}//测试结果/***Thread将线程执行体和线程绑定在了一起,执行多次需要手工创建多个线程对象。*Runnable将执行体与线程分开,用线程池负责线程的创建与回收* @throws InterruptedException*/public static void main(String[] args) throws InterruptedException {//创建简单线程池ExecutorService threads = Executors.newFixedThreadPool(10);//实例对象,传输公共属性和方法ThreadDemo td1 = new ThreadDemo();ThreadDemo td2 = new ThreadDemo();//线程类CountThread ct1 = new CountThread(td1);CountThread ct2 = new CountThread(td1);CountRunnable cr1 = new CountRunnable(td2);CountRunnable cr2 = new CountRunnable(td2);//启动线程ct1.start();ct2.start();threads.submit(cr1);threads.submit(cr2);//睡眠当前线程main,等待任务线程执行结束Thread.sleep(4000);System.out.println(td1.no+","+td2.no);System.out.println(ThreadDemo.num);}}

执行结果如下:

20000,20000
40000

num输出40000是因为添加了static。简单说,却别是:num的内存地址是class单独开辟出的,生命周期同class;而no属于对象,生命周期同创建的对象。

三:ReentrantLock简单使用和手写实现

3.1.ReentrantLock、Condition使用示例

package generator;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;public class ReetrantLockDemo {private boolean flag = false;private final int MAX_NO = 5;ReentrantLock lock = new ReentrantLock();Condition condition = lock.newCondition();public void getA(){try {lock.lock();while(flag == true){condition.await();}System.out.print("a");flag = true;condition.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void getB(){try {lock.lock();while(flag == false){condition.await();}System.out.print("b");flag = false;condition.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}private static class Arunnable implements Runnable{private ReetrantLockDemo reetrantLockDemo;public Arunnable(ReetrantLockDemo reetrantLockDemo) {this.reetrantLockDemo = reetrantLockDemo;}@Overridepublic void run() {for (int i = 0; i <reetrantLockDemo.MAX_NO ; i++) {reetrantLockDemo.getA();}}}private static class Brunnable implements Runnable{private ReetrantLockDemo reetrantLockDemo;public Brunnable(ReetrantLockDemo reetrantLockDemo) {this.reetrantLockDemo = reetrantLockDemo;}@Overridepublic void run() {for (int i = 0; i <reetrantLockDemo.MAX_NO ; i++) {reetrantLockDemo.getB();}}}public static void main(String[] args) {ReetrantLockDemo reetrantLockDemo = new ReetrantLockDemo();ExecutorService threads = Executors.newFixedThreadPool(10);Arunnable arunnable = new Arunnable(reetrantLockDemo);Brunnable brunnable = new Brunnable(reetrantLockDemo);threads.submit(arunnable);threads.submit(brunnable);}}

执行结果为:ababababab

解释:lock()锁定线程、unlock()释放锁,两个必须成对出现。使用condition需要先通过lock.lock()获得线程(对象监视器),condition.await和signal的是当前线程

3.2.手写实现简单ReentrantLock

我们知道ReentrantLock是一个可重入锁,可重入锁的特点是所有线程都可以竞争锁,只有第一个线程类及其子类能够获取到锁。需要我们内置一个计数器,需要我们内置一个列表来存放等待线程

百度实现1,待测试优化

public class MyLock implements Lock{Thread lockBy = null;int lockCount = 0;//锁标志 private boolean isLocked = false;/** 加锁* (non-Javadoc)* @see java.util.concurrent.locks.Lock#lock()*/@Overridepublic synchronized void lock() {Thread currentThread = Thread.currentThread();//如果不是第一个进来,则在这儿等待while (isLocked && currentThread != lockBy) {// ...阻塞到这儿,都等待,但是第一个进来不让让等待try {wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}//如果是第一个进来,将其变为falseisLocked = true;//将lockBy指向当前线程lockBy = currentThread;//计数器自增lockCount ++;}/** 释放锁* (non-Javadoc)* @see java.util.concurrent.locks.Lock#lockInterruptibly()*/@Overridepublic synchronized void unlock() {//如果当前线程等于lockByif (lockBy == Thread.currentThread()) {lockCount --;if (lockCount == 0) {notify();//释放锁isLocked =false;}}}@Overridepublic void lockInterruptibly() throws InterruptedException {}@Overridepublic boolean tryLock() {return false;}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return false;}@Overridepublic Condition newCondition() {return null;}}

ReentrantLock手写实现2

package generator;import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;public class CassellReentrantLock implements Lock {//原子特殊线程AtomicReference<Thread> owner = new AtomicReference<>();//等待列表,存放没有抢到锁的线程LinkedBlockingDeque<Thread> waiter = new LinkedBlockingDeque<>();@Overridepublic void lock() {//比较旧值null,如一致则替换为新值Thread.currentThread()while(!owner.compareAndSet(null,Thread.currentThread())){//没有抢到,进入等待列表waiter.add(Thread.currentThread());//等待,类似waitLockSupport.park();//线程被唤醒,从等待列表中移除该线程waiter.remove(Thread.currentThread());}}@Overridepublic void unlock() {//比较旧值Thread.currentThread(),如一致则替换为新值nullif(owner.compareAndSet(Thread.currentThread(),null)){//遍历等待线程列表,唤醒Object[] objects = waiter.toArray();for (Object object : objects) {Thread next = (Thread)object;LockSupport.unpark(next);}}}@Overridepublic void lockInterruptibly() {}@Overridepublic boolean tryLock() {return false;}@Overridepublic boolean tryLock(long time, TimeUnit unit) {return false;}@Overridepublic Condition newCondition() {return null;}
}

并发编程(入门) 多线程学习 手写ReentrantLock相关推荐

  1. 并发编程入门(五):Java并发包和Java8并发

    目录 前言 JUC(Java.util.concurrent) 1.Java并发包之原子类 1.1.AtomicInteger 1.2.AtomicReference 1.3.AtomicStampe ...

  2. 安琪拉教百里守约学并发编程之多线程基础

    <安琪拉与面试官二三事>系列文章 一个HashMap能跟面试官扯上半个小时 一个synchronized跟面试官扯了半个小时 <安琪拉教鲁班学算法>系列文章 安琪拉教鲁班学算法 ...

  3. 《Java并发编程实践》学习笔记之一:基础知识

    <Java并发编程实践>学习笔记之一:基础知识 1.程序与进程 1.1 程序与进程的概念 (1)程序:一组有序的静态指令,是一种静态概念:  (2)进程:是一种活动,它是由一个动作序列组成 ...

  4. Java零基础并发编程入门

    Java零基础并发编程入门 并发编程主要包括: 线程,同步,future,锁,fork/join, volatile,信号量,cas(原子性,可见性,顺序一致性),临界性,分布式 了解基础: JMM: ...

  5. 学习嵌入式的书籍推荐,嵌入式编程入门教程学习大纲

    嵌入式系统是当前热门.具发展前景的IT应用领域之一,很多数字包括手机.电子字典.可视电话.数字相机.数字摄像机.机顶盒.智能玩具医疗仪器和航空航天设备等都是典型的嵌入式系统.越来越多的人想要了解学习嵌 ...

  6. week6 day4 并发编程之多线程 理论

    week6 day4 并发编程之多线程 理论 一.什么是线程 二.线程的创建开销小 三.线程和进程的区别 四.为何要用多线程 五.多线程的应用举例 六.经典的线程模型(了解) 七.POSIX线程(了解 ...

  7. 深度学习——手写数字识别

    深度学习--手写数字问题 前不久入门学习了Tensorflow深度学习框架,了解一下什么是神经网络和Tensorflow的简单使用.下面通过Tensorflow框架来建造神经网络模型来对手写数字进行训 ...

  8. 【PytorchLearning】NLP入门笔记之手写Transformer Encoder内部机制

    NLP入门笔记之手写Transformer Encoder内部机制 本文主要从Transformer Encoder中Word embedding生成.Position embedding机制和sel ...

  9. 《Java并发编程入门与高并发面试》or 《Java并发编程与高并发解决方案》笔记

    <Java并发编程入门与高并发面试>or <Java并发编程与高并发解决方案>笔记 参考文章: (1)<Java并发编程入门与高并发面试>or <Java并发 ...

最新文章

  1. Spring Boot配置文件学习记录【1】
  2. 相机位姿估计2:[应用]实时位姿估计与三维重建相机姿态
  3. Adobe Bridge 2021中文版
  4. html背景自动换,html页面换皮肤颜色、背景图片(更换页面背景,常驻缓存)刷新保存...
  5. oracle的parameters怎么用,oracle普通用户使用show parameter方法
  6. 利用索引数组排序 不改变原数组值的位置
  7. Python实现RabbitMQ中6种消息模型(转)
  8. 云计算之路-阿里云上:4000IOPS的RDS+16核CPU的负载均衡
  9. cooleditpro批量加速文件_Python玩转阿里云OSS对象存储,批量转存markdown和html图片
  10. Caffe的各个版本简介
  11. 徐小凤将现身东方卫视跨年晚会 为63岁庆生
  12. python拼接sql语句字符串 无效字符,Python拼接SQL字符串的方法
  13. jdk和cglib动态代理
  14. IEEE 会议论文的参考文献
  15. 耦合是什么 耦合有哪些种类
  16. python外汇兑换代码_Python爬取中国银行外汇牌价
  17. Linux、UNIX设置开机自动运行命令、脚本配置
  18. 一些冷门的JS技巧 顶
  19. ninja源码下载及编译(Win10+VS2019)
  20. 类在c++中的初步运用

热门文章

  1. 安卓手机微信发不出去怎么办 微信不能发信息怎么办
  2. python进程管理工具 ci_推荐 10 个好用的 CI/CD 工具
  3. 计算机课反思的作文600字,反思作文600字
  4. 下载微信公众号全部文章的方法
  5. 电脑开不了机怎么办?排查这3种情况
  6. 用一根网线颠覆传统教育,这名计算机极客如何变成了数学教父,拒绝10个亿?...
  7. 电脑如何通过HTC手机上互联网的配置方法(联通号)
  8. 额温枪的误差分析和测量方法
  9. SICP--Scheme语言编辑器Racket安装和配置
  10. 年金用计算机怎么算,cfa计算器计算年金