前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java并发学习1【面试+工作】

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

作者头像
Java帮帮
发布2018-06-11 14:16:21
7072
发布2018-06-11 14:16:21
举报

一.并发概述

为什么要学习并发

“今天和一哥们聊天,聊着聊着聊到钱的方面,当时我就说,全世界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来给线程设置优先级。


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-05-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java帮帮 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.并发概述
  • 为什么要学习并发
  • 并发的应用场景
  • jdk并发包具体结构
  • 学习java并发必知的几个概念
    • 同步和异步
      • 临界区
        • 阻塞和非阻塞
          • 死锁、饥饿、活锁
          • 二.并发基础
          • 进程和线程
          • 初始线程:线程的基本操作
            • 新建线程
              • 终止线程
                • 线程中断
                  • 等待和通知
                    • 挂起和继续执行线程
                      • 等待线程结束和谦让
                        • join
                        • yield
                    • 线程组
                    • 守护线程
                    • 线程优先级
                    相关产品与服务
                    容器服务
                    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档