Java并发学习1【面试+工作】

一.并发概述

为什么要学习并发

“今天和一哥们聊天,聊着聊着聊到钱的方面,当时我就说,全世界60亿人,要是每人给我一块钱那不就发财了啊.哥们立马用鄙视的眼神看我,全世界60亿人,平均一人给你钱需要2秒,也就是120亿秒,2亿分钟,330多万个小时,14万天,380年.恭喜你,过380年之后你就是亿万富翁,lz当时竟然无言以对。。”

  并发在海量任务处理时有非常的明显优势,如果是串行的执行海量任务,那时间就是累加的关系,但是如果采用并发设计,就相当于同时执行了多个任务,这样可以大幅提高任务的执行速度。如上的例子,如果采用并发的设计,完全不必等这么久。

并发的应用场景

1.比如构建大流量高并发的网站 2.构建一个支持大量接入的即时通讯服务端。 3.在android构建一个支持大量请求的网络请求框架。 4.双11,天猫的交易系统等等。涉及大量的请求,如果不使用并发,而是串行,效能会非常非常的低。 5.各种生产者消费者模型对应的业务场景等等

jdk并发包具体结构

一、描述线程的类:Runable和Thread都属于java.lang包 二、内置锁synchronized属于jvm关键字,内置条件队列操作接口Object.wait()/notify()/notifyAll()属于java.lang包 三、提供内存可见性和防止指令重排的volatile属于jvm关键字 四、而java.util.concurrent包(J.U.C)中包含的是java并发编程中有用的一些工具类,包括几个部分: 1、locks部分:包含在java.util.concurrent.locks包中,提供显式锁(互斥锁和速写锁)相关功能; 2、atomic部分:包含在java.util.concurrent.atomic包中,提供原子变量类相关的功能,是构建非阻塞算法的基础; 3、executor部分:散落在java.util.concurrent包中,提供线程池相关的功能; 4、collections部分:散落在java.util.concurrent包中,提供并发容器相关功能; 5、tools部分:散落在java.util.concurrent包中,提供同步工具类,如信号量、闭锁、栅栏等功能;

学习java并发必知的几个概念

同步和异步

同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。

异步方法调用更像是一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中执行。

临界区

临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。

阻塞和非阻塞

阻塞和非阻塞常用来形容多线程间的相互影响。

死锁、饥饿、活锁

易理解

  大学的时候,我们都学习过《操作系统》,里面也讲到了线程、信号量等等,有了这些知识,对本系列的内容理解起来会更容易一些,这个系列相当于只是采用了java语言来阐释大学的内容。

  针对具体业务建模,写出优秀的工业级并发代码,是一件非常困难的事情,这里也只是简单的介绍一些基础的知识。后续还要在工作中深入学习。



二.并发基础

进程和线程

  进程:这里不讲枯燥的概念,举一个例子:你在windows系统中,看到后缀为.exe的文件,都是一个程序。不过程序是死的,静态的。当你双击这个.exe执行的时候,这个.exe文件的指令就会被加载,那么你就能得到一个有关这个.exe程序的一个进程。进程是活的,或者说是正在被执行的。

  线程:进程中可以容纳若干个线程,他们并不是看不见、摸不着的。也可以通过工具看到他们。用稍微专业点的术语说,线程就是轻量级的进程,是程序执行的最小单位。使用多线程而不是用多进程去进行并发程序的设计,是因为线程间的切换和调用的成本远远小于进程。

一个线程的生命周期:

  new状态表示刚刚创建的线程,这种线程还没开始执行。等到线程的start方法调用时,才表示线程开始执行。当线程执行时,处于runnable状态,表示线程所需的一切资源都经准备好了。如果线程在执行过程中遇到了synchronized同步块,就会进入blockeduserid状态,这时线程就会暂停执行,知道获得请求的锁。waiting和timed-waiting都表示等待的状态,他们的区别是waiting会进入一个无时间限制的等待,time-waiting会进行一个有时限的等待,那等待的线程究竟在等待什么呢?一般来说,waiting的线程正是在等待一些特殊的事件。比如,通过wait方法等待的线程在等待notify方法,而通过join方法等待的线程则会等待目标线程的终止。一旦等到了期望的事件,线程就会再次执行,进入runnable状态。当线程执行完后,则进入terminated状态,表示结束。

初始线程:线程的基本操作

新建线程

java中如何新建一个线程。有两种方案:

1.

2.常用做法

线程start之后,会干什么呢?start方法会新建一个线程并让这个线程执行run方法。

特别注意:

这个方法是在主线程中执行的。

终止线程

  一般来说,线程在执行完毕后就会结束,无须手工关闭。但是,凡事都有例外,一些服务器的后台线程可能会常驻系统,他们通常不会正常终结。比如,他们的执行体本身就是一个大大的后端的无穷循环,用于提供某些服务。

那如何正确的关闭线程呢?

1.jdk提供的stop方法。(已被废弃,不推荐使用)

  已被废弃,不推荐使用,因为可能会引起数据的不一致。为什么呢?因为Thread.stop方法在结束线程时,会直接终止线程,并且会立即释放这个线程所持有的锁。而这些锁恰恰是用来维持对象一致性的。如果此时,写线程写入数据正写到一半,并强行终止,那么对象就会被写坏,同时,由于锁已经被释放,另外一个等待该锁的读线程就顺理成章的读到了这个不一致的对象,悲剧就发生了。如果在线上环境跑出不一致的结果,那么加班加点估计是免不了的了,因为这类问题一旦出现,就很难排查,因为它们甚至没有任何错误信息,也没有线程堆栈。这种情况一旦混杂在动则十几万行的程序代码中时,发现他们全凭经验,时间还有一点点的运气了。因此,除非你很清楚你在做什么,否则不要随便使用stop方法来停止一个线程。

2.在线程中增加一个stopMe方法即可。

线程中断

  在java中,线程中断是一种重要的线程协作机制。从表面上理解,中断就是让目标线程停止执行的意思,实际上并非完全如此。在上一节中,我们已经介绍了stop方法停止的害处,并且使用了一套自有的机制完善线程退出的功能。那在jdk中是否提供了更强大的支持呢?那就是线程中断。

严格来讲,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出了,至于目标线程接到通知后如何处理,则完全由目标线程自行决定。这点很重要,如果中断后,线程立即无条件退出,我们就又会遇到stop方法的老问题。

与线程中断相关有三个方法:

  Thread.interrupt来置中断状态,Thread.isInterrupted来书写中断处理逻辑。具体看起来好像跟前面的增加stopMe标记的手法非常相似,但是中断的功能更为强劲。Thread.sleep会抛出一个中断异常,如果在这种情况下依然想保持数据的一致性,Thread.interrupt就显得尤为重要了。

等待和通知

涉及方法:

  当在一个对象实例上调用wait方法后,当前线程就会在这个对象上等待。这是什么意思呢?比如,线程A中,调用了obj.wait()方法,那么线程A就会停止继续执行,而转为等待状态。等待何时结束呢?线程A会一直等到其他线程调用了obj.notify()方法为止。这时,obj对象就俨然成为多个线程之间的有效通信手段。

  那wait和notify究竟是如何工作的呢?如果一个线程调用了obj.wait(),那么它就会进入obj对象的等待队列。这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。当obj.notify()被调用时,它就会从这个等待队列中,随机选择一个线程,并将其唤醒。这里希望大家注意的是,这个选择是不公平的,并不是先等待的线程就优先被选择,这个选择完全是随机的。

  除了notify方法外,obj对象还有一个notifyall方法,它和notify的功能基本一致,但不同的是,它会唤醒在这个等待队列中所有等待的线程,而不是随机选择一个。

  这里要强调一点,obj.wait方法并不是可以随便调用的。它必须包含在对应的synchronized语句中,无论是wait还是notify都需要首先获得目标对象的一个监视器。

例如:

输出结果:

  这里T1执行wait之后,会释放这个监视器   T2执行notify之前也获得这个监视器,然后执行notify,尝试唤醒一个等待线程,唤醒了T1,T1被唤醒之后,要做的第一件事并不是执行后续代码,而是要尝试重新获得obj的监视器,而这个监视器此时T2还没释放掉,只有T2执行完Thread.sleep(2000);才会释放掉,所以T1必须等2s,才能得到这个监视器,当监视器顺利获得后,T1才可以真正意义上的继续执行。

挂起和继续执行线程

suspend和resume也是被废弃的方法。

原因是suspend在导致线程暂停的时候,并不会释放资源。

等待线程结束和谦让

join

  很多时候,一个线程的输入可能非常的依赖于另外一个或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。jdk提供了join方法来实现这个功能:

  第一个join方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。第二个方法给出了一个最大的等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为等不及了,而继续往下执行。

  应为join的翻译,通常是加入的意思。在这里感觉也非常贴切。因为一个线程要加入另一个线程,那么最好的方法就是等着它一起走。

举一个简单的例子:

结果: 10000000

  主函数中,如果不使用join等待AddThread,那么得到的i很可能是0或者一个非常小的数字。因为AddThread还没执行完,i的值就已经输出了。但在使用了join方法后,表示主线程愿意等待AddThread执行完毕,跟着AddThread一起往前走,故在join返回时,AddThread已经执行完毕,故i总是10000000。

yield

  这个方法是一个静态方法,一旦执行,它会让当前线程让出cpu后,还会进程cpu的正度,但是是否能够再次被分配到,就不一定了。具体调用:Thread.yield().

线程组

用得很少,不讲

守护线程

用得少,不讲

线程优先级

直接调用setPriority来给线程设置优先级。


原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2018-05-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

2 条评论
登录 后参与评论

相关文章

来自专栏Java学习网

Java线程使用技巧学习(一)

Java线程使用技巧学习(一) Java线程有哪些不太为人所知的技巧与用法?   萝卜白菜各有所爱。像我就喜欢Java。学无止境,这也是我喜欢它的一个原因...

1776
来自专栏码洞

如何解决Java线程池队列过饱问题

Java的Executors框架提供的定长线程池内部默认使用LinkedBlockingQueue作为任务的容器,这个队列是没有限定大小的,可以无限向里面sub...

501
来自专栏java达人

BlockingQueue

前言: 在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队...

1827
来自专栏LuckQI

学习Java基础知识,打通面试关~十三锁机制

1125
来自专栏移动开发面面观

Android线程池的详细说明(一)

1372
来自专栏CSDN技术头条

深入理解 Java 多线程核心知识:跳槽面试必备

多线程相对于其他 Java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲。在平时工作中如若使用不当会出现数据错乱、执行效率低(还不如单线程去运行)或者死锁...

913
来自专栏yukong的小专栏

【java并发编程实战4】偏向锁-轻量锁-重量锁的那点秘密(synchronize实现原理)synchronized自旋锁偏向锁轻量锁重量锁小结

在多线程并发编程中,synchronized一直都是元老级别的角色,人们都通常称呼它为重量锁,但是在jdk1.6版本之后,jdk就对synchronized做了...

1112
来自专栏熊二哥

Java并发编程快速学习

上周的面试中,被问及了几个关于Java并发编程的问题,自己回答的都不是很系统和全面,可以说是“头皮发麻”,哈哈。因此果断购入《Java并发编程的艺术》一书,学习...

2028
来自专栏xdecode

Java高并发之锁优化

923
来自专栏无题

JVM锁优化概述

* 锁优化 jdk1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 锁主要存在四中状态,依...

2705

扫码关注云+社区