并发

并发

1. 实现并启动线程的两种方式:

1)继承Thread类,重写run方法,用start启动; 2) 实现Runnable接口,实现run方法,用new Thread(Runnable target).start()来启动; 3)实现Callable接口的call方法,有返回值。(前两种run方法无返回值)
run是由不同的线程的执行的,仍然可以执行main中的其他操作。

join:一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行。

并发编程中的三个特性:

1. 原子性(Atomicity):一个操作不能被打断,要么全部执行完毕,要么不执行。原子操作可以由线程保证其不可中断。当定义Long和double时可以用volatile修饰,就会获得原子性(因为Long和double是64位的,JVM会把64位的读写当作分离的32位操作来执行。)
2. 可见性: 一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量这种修改(变化)。
3. 有序性: 在单线程程序里,我们总是以为代码的执行是从前往后的,依次执行的。
但是在多线程并发时,程序的执行就有可能出现乱序。在本线程内观察,操作都是有序的(线程内表现为串行语义(WithIn Thread As-if-Serial Semantics));如果在一个线程中观察另外一个线程,所有的操作都是无序的(“指令重排”现象和“工作内存和主内存同步延迟”现象)。
Java提供了两个关键字volatile和synchronized来保证多线程之间操作的有序性,volatile关键字本身通过加入内存屏障来禁止指令的重排序,而synchronized关键字通过一个变量在同一时间只允许有一个线程对其进行加锁的规则来实现。
4. happens-before原则 Java内存模型中定义的两项操作之间的次序关系,如果说操作A先行发生于操作B,操作A产生的影响能被操作B观察到。

中断

1. Executor 的中断操作
Executor的shutdown方法可以防止新任务提交到Executor,当前线程将继续运行在shutdown()被调用前提交的所有任务。
2. interrupted()

3. 锁

1 synchronized关键字:隐式锁
1)可以把方法标记为synchronized来防止资源冲突。但在使用并发的时候需要将域设置为private,因为synchronized无法防止其他任务直接访问域。
2)synchronized底层是如何实现的:信号量,锁this。
2 Lock:显式锁
lock() unlock()方法。结构如下:

lock.lock()
try{
  return; //return必须在try中出现,以确保unlock不会过早发生,从而暴露给了第二个任务
}finally{
  lock.unlock()
}

3. volatile关键字:
volatile关键字保证了应用中的可视性。如果将一个域声明成volatile的,那么只要对这个域产生了写操作,那么所有的读操作都可以都可以看到这个操作。
如果一个域完全由synchronized防护,那就不必设置为volatile。
volatile可以防止指令重排,但注意!volatile并不保证原子性。(因为如果当前值与该变量以前的值相关,那么volatile关键字就不起作用:例如count++, count = count+1)

5. 多线程中的信息交互:

  1. Thread类的方法:sleep(), 让线程暂停执行指定的时间,但对象锁依然保持。休眠时间结束后线程回到就绪态。
  2. Object中的方法:wait(), notify(), notifyAll();wait()导致线程放弃对象锁进入等待池,只有调用对象的notidy()方法(或者notifyAll()方法)才能唤醒等待池中的线程进入等锁池。
  3. join:在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
  4. java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。
    相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

6. 多线程公用一个数据变量需要注意什么?

1)当在Runnable对象中定义了全局变量,且run方法会修改该变量时,如果有多个线程同时使用该对象,就会造成全局变量的值被同时修改,造成错误。
2)ThreadLocal可以用于解决线程间共享变量,使用ThreadLocal声明的变量即使在线程中属于全局变量,对于每个线程来说,这个变量也是独立的。
3)volatile变量,每次被线程访问时都强迫线程从主内存中重读该变量的最新值。当变量被修改时,也会强迫线程将最新的值刷新回主内存中。

7. 线程池

  1. 用途:事先创建若干个可执行的线程放入一个池(容器),需要的时候从池中获取线程,不用自行新建,使用完毕不需要销毁线程,而是放回线程池,从而减少创建和销毁线程对象的开销。
  2. 如何设计一个动态大小的资源池? 一个线程池有四个基本组成部分:
    1) 线程管理器(Threadpool):用于创建并管理线程池,添加新任务;
    2)工作线程(PoolWorker):线程池中线程,在没有任务是处于等待状态,可以循环执行任务;
    3)任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完成后的收尾工作,任务的执行状态等;
    4)任务队列(TaskQueue):用于存放没有处理的任务。提供一种缓存机制。

8. AQS (Abstract Queued Synchronizer)

  1. CountDownLatch:用来控制一个线程等待多个线程。
    维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。
    912a7886-fb1d-4a05-902d-ab17ea17007f.jpg

    (图片转载自 https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20并发.md#十java-内存模型)
  2. CyclicBarrier:用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
    和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。


    f944fac3-482b-4ca3-9447-17aec4a3cca0.png
  3. Semaphore:Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。

9. 内存模型

  1. 栈:Java栈总是与线程关联在一起的,每当创建一个线程,JVM就会为该线程创建对应的Java栈。Java栈数据不是线程共有的,所以不需要关心其数据一致性,也不会存在同步锁的问题。
  2. 堆:堆是存储Java对象的地方,Java堆是GC管理的主要区域,从内存回收的角度来看,由于现在GC基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;新生代再细致一点有Eden空间、From Survivor空间、To Survivor空间等。
  3. Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量这样的底层细节。
    1) JVM规定了所有的变量都存储在主内存(Main Memory)中,每个线程还有自己的工作内存(Working Memory)。线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量,不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。
    2) Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作。
    read:把一个变量的值从主内存传输到工作内存中
    load:在 read 之后执行,把 read 得到的值放入工作内存的变量副本中
    use:把工作内存中一个变量的值传递给执行引擎
    assign:把一个从执行引擎接收到的值赋给工作内存的变量
    store:把工作内存的一个变量的值传送到主内存中
    write:在 store 之后执行,把 store 得到的值放入主内存的变量中
    lock:作用于主内存的变量
    unlock:


    b6a7e8af-91bf-44b2-8874-ccc6d9d52afc.jpg

10. 多线程开发实践

  1. 给线程起个有意义的名字,这样可以方便找 Bug。
  2. 缩小同步范围,从而减少锁争用。例如对于 synchronized,应该尽量使用同步块而不是同步方法。
  3. 多用同步工具少用 wait() 和 notify()。首先,CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善。
  4. 使用 BlockingQueue 实现生产者消费者问题。
    5.多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。
  5. 使用本地变量和不可变类来保证线程安全。
  6. 使用线程池而不是直接创建线程,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容

  • layout: posttitle: 《Java并发编程的艺术》笔记categories: Javaexcerpt...
    xiaogmail阅读 5,770评论 1 19
  • 一、并发 进程:每个进程都拥有自己的一套变量 线程:线程之间共享数据 1.线程 Java中为多线程任务提供了很多的...
    SeanMa阅读 2,379评论 0 11
  • 第2章 java并发机制的底层实现原理 Java中所使用的并发机制依赖于JVM的实现和CPU的指令。 2.1 vo...
    kennethan阅读 1,377评论 0 2
  • 一.线程安全性 线程安全是建立在对于对象状态访问操作进行管理,特别是对共享的与可变的状态的访问 解释下上面的话: ...
    黄大大吃不胖阅读 811评论 0 3
  • 童年的梦走远了 承诺都去哪儿了 年少的狂都没了 成长都留下来了 拽着那轻快步伐的拍子 享受也排斥着 路过蘶蔷婀娜茱...
    阿布sara阅读 548评论 2 16