三篇搞定Java高并发笔记【第一篇】

本文Java高并发的内容将从三个阶段记录,参考资料【Java并发编程详解】:

多线程基础
Java内存模型(高并发设计模式)
Java并发包JUC
Java并发包源码AQS

什么是线程

相信学过操作系统的同学都知道线程和进程的关系,对于计算机来说一个任务就是一个进程,一个进程里面至少有一个线程。想必学习的时候会不会问,一个APP就对应一个进程,一个进程难道就是一个JVM吗?那经常写的函数是不是就是一个线程呢?

通常来说,一个APP是一个进程,但是也有可能多个进程。一个进程就是一个JVM(虚拟机),里面有很多个线程运行,接下来就是操作系统的知识了。
每个线程都有自己的局部变量表、程序计数器以及生命周期,线程的生存状态分为以下五个主要的阶段:

  • NEW状态
    当我们用关键字new创建一个Thread对象时,此时并不处于执行状态,因为没有调用start方法。此时只是一个对象的状态,就跟平常创建一个对象一样,当用start方法时就会进入到`RUNNABLE“状态。

  • RUNNABLE状态
    此时才是真正的在JVM中创建了一个线程,线程一经启动就立即执行吗?肯定不是的,因为还有个RUNNING状态,当调用run方法才真正开始执行。线程的执行与否都需要听从CPU的调度,只有CPU调度了才有执行资格。

  • RUNNING状态
    一旦CPU通过轮询从队列中选中了线程,此时才是真正的执行逻辑代码。

  • BLOCKED状态
    线程可能因为某种原因进入阻塞状态,就会进入BLOCKED状态。比如调用了sleep,或者wait方法而加入了waitSet中。比如为获得锁资源,而被进入阻塞队列中等待。

  • TERMINATED状态
    TERMINATED状态是线程的最终状态,在状态中线程不会切换到其他状态,线程进入TERMINATED状态,意味着该线程的整个生命周期都结束了。比如线程正常结束任务,比如JVM crash,所有线程结束。

    在面试过程中,我们都会遇到这样的面试题,怎样创建一个线程?
    答:
    1)继承 Thread类创建线程
    2)实现Runnable接口创建线程
    3)使用Callable和Future创建线程
    4)使用线程池例如用Executor框架

    其实在JDK中代表线程的就只有Thread这个类,线程执行单元就是run方法。所以创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元则有两种方式,第一种就是重写Thread的run方法,第二种实现runnable接口方法,并将Runnable实例用作构造Thread的参数。

    我们接入一个例子来引入上面的实例:假设一个银行的办事大厅,有三个柜台,每个柜台需要为顾客办理业务。顾客依次进门取号,等待办理,那么可以怎样模仿这个实例呢?

    public class demo implements Runnable{
    private int index = 1;//当前的顾客
    private int MAX = 50;
    @Override
    public void run() {
    while (index < MAX){
    System.out.println(Thread.currentThread() + "的号码是:"+(index++));
    try{
    Thread.sleep(100);
    }catch (Exception e){
    e.printStackTrace();
    }
    }
    }

    public static void main(String arg[]){//多线程测试hashmap 容易出现死循环
    final demo runnable = new demo();
    Thread thread_1 = new Thread(runnable,"线程一");
    Thread thread_2 = new Thread(runnable,"线程一");
    Thread thread_3 = new Thread(runnable,"线程一");
    thread_1.start();
    thread_2.start();
    thread_3.start();
    }
    }

    只贴部分输出情况,可以看到,某几个线程重复的index情况。

    Thread[线程四,5,main]的号码是:17
    Thread[线程二,5,main]的号码是:17

    为什么会这样呢?

    相信看过我的ConcurrentHashMap高并发讲解 的就知道,线程执行的时候会有缓存,还没等线程修改刷新至内存就被其他线程修改了,所以导致重复!那么怎么解决呢,一是定义static共享变量,二是使用volatile关键字强制刷新到内存。

    Thread与JVM虚拟机

    研究线程那么必然要懂得JVM的内存模型,以及各个内存区域之间的关系。JVM在执行Java程序的时候会把对应的物理内存划分为不同的区域,每一个区域都存放着不同的数据。

    JVM内存模型

    三篇搞定Java高并发笔记【第一篇】

      程序计数器
      相信这个大家都不陌生,操作系统都需要通过控制总线向CPU发送机器指令,而程序计数器就是当前执行指令地址。每个线程都需要一块独立的程序计数器,因此内存区域是线程私有的。

      Java虚拟栈
      线程创建时,都会为其创建一个虚拟机栈,虚拟机栈大小可以用-xss来配置。在线程中,方法执行会创建一个名为栈帧的东西,主要用于存放局部变量表操作栈动态链接等信息,其区域也是私有

      本地方法栈
      Java提供了调用本地方法的接口(JNI Java Native Interface),也就是操作系统程序方法。大家都知道,JVM是运行在操作系统上的,而操作系统是最终的管理。在JVM中经常会使用JNI方法,比如网络通信,文件操作系统的底层,甚至String的intern等都是JNI方法。

      堆内存
      堆内存是JVM中最大的一块内存区域,被所有的线程共享,Java创建的所有对象几乎都存在这里。该内存区域也会垃圾回收器经常照顾的对象,所以有时候被称为“GC堆”

      方法区
      方法区也是被多个线程共享的内存区域,主要用于存储被虚拟机加载的类信息、常量(运行常量池)、静态变量即时编译器(JIT)编译后的代码等数据。

    所以可以得出进程的内存大小为:堆内存+线程数量*栈内存

    那么JVM中可以创建多少个线程呢,我们是可以通过公式算出来的。
    线程数量=(最大内存空间-JVM堆内存-ReserverOsMemory)/ThreadStackSize(xss) 当然线程数量还跟操作系统的一些内核配置有很大的关系。

    守护线程

    你知道JVM在什么情况下会退出吗?
    我们说的是正常的退出,而不是调用System.exit()。正常退出就需要理解守护线程,守护线程是一类比较特殊的线程,一般用于处理后台的一些工作,比如JDK的垃圾回收。若线程中没有非守护线程就会,则JVM的进程就会退出。
    设置守护线程的方法就是thread.setDaemon(true),true就是代表守护线程,false就是正常线程。主要注意的就是,setDaemon方法只在线程启动之前才能失效,如果一个线程死亡,那么设置setDaemon则会抛出IllegalThreadStateException异常。

    线程sleep
    sleep方法会使线程进入指定的毫秒数的休眠,暂停执行。休眠有一个重要的特性就是不会放弃monitor锁的所有权。
    使用TimeUntil替代Thread.sleep
    在使用线程休眠方法Thread.sleep,可以完全用TimeUnit来代替,因为sleep能做的事,TimeUnti都能做。比如线程想休眠3小时24分17秒88毫秒

            TimeUnit.HOURS.sleep(3);
    TimeUnit.MINUTES.sleep(24);
    TimeUnit.SECONDS.sleep(17);
    TimeUnit.MILLISECONDS.sleep(88);

    线程yield
    yield方法是属于一种启发式的方法,调用yield方法会使当前线程从RUNNING状态切换到RUNNABLE状态

    总结:
    1.sleep会导致当前线程暂停指定的时间,没有CPU的时间消耗。
    2.yield只是对CPU调度器的一个提示,如果CPU没有忽略这个提示,他会导致线程上下文切换
    3.sleep会使线程短暂block,会在给定的时间内释放CPU的资源
    4.sleep会百分百的完成给定的时间消耗,而yield不一定担保
    5.一个线程sleep另一个线程调用interrupt会捕获中断信号,而yield不会。

    线程安全与数据同步

    多线程里最重要的内容之一,那就是数据同步线程安全等内容。在串行化任务执行时,由于不存在资源的共享,线程安全的问题几乎不用担心。但是现在都是追求高效率的执行,都需要满足多线程对共享资源的竞争。
    在前面的例子中,讲了多个线程对index变量的竞争引起的,解决竞争问题可以用synchronized关键字,synchronized提供了一种排他机制,也就是在同一时间只能有一个线程执行操作。

    什么是synchronized关键字?
    synchronized就是同步的意思,可以实现一种简单的策略来防止线程干扰和内存一致性错误,具体表现为如下:

    synchronized关键字提供了一种锁的机制,能够确保共享变量的互斥访问,从而防止数据不一致的出现。
    synchronized关键字包括monitor entermonitor exit两个JVM指令,能够保证随时都能执行到monitor enter之前都能必须从内存中获取数据。

    synchronized关键字的指令严格遵守java happens-before规则,一个monitor exit指令之前必定有一个monitor enter。

    Monitorenter
    每一个对象都与一个monitor关联,一个monitor的lock锁只能被一个线程在同一时间获得 。synchronized关键字获得锁,其实就是获取对象的monitor。

    点赞加关注!不能白嫖!没动力了!
    点赞加关注!不能白嫖!
    点赞加关注!不能白嫖!

    三篇搞定Java高并发笔记【第一篇】

    原创:https://www.panoramacn.com
    源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

    专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

    三篇搞定Java高并发笔记【第一篇】

    免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

    您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
    www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
    未经允许不得转载:书荒源码源码网每日更新网站源码模板! » 三篇搞定Java高并发笔记【第一篇】
    关注我们小说电影免费看
    关注我们,获取更多的全网素材资源,有趣有料!
    120000+人已关注
    分享到:
    赞(0) 打赏
  • 评论抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址

    您的打赏就是我分享的动力!

    支付宝扫一扫打赏

    微信扫一扫打赏