java基础多线程学习.docx

上传人:b****5 文档编号:8203063 上传时间:2023-01-29 格式:DOCX 页数:11 大小:251.87KB
下载 相关 举报
java基础多线程学习.docx_第1页
第1页 / 共11页
java基础多线程学习.docx_第2页
第2页 / 共11页
java基础多线程学习.docx_第3页
第3页 / 共11页
java基础多线程学习.docx_第4页
第4页 / 共11页
java基础多线程学习.docx_第5页
第5页 / 共11页
点击查看更多>>
下载资源
资源描述

java基础多线程学习.docx

《java基础多线程学习.docx》由会员分享,可在线阅读,更多相关《java基础多线程学习.docx(11页珍藏版)》请在冰豆网上搜索。

java基础多线程学习.docx

java基础多线程学习

如果对什么是线程、什么是进程仍存有疑惑,请先XX之,因为这两个概念不在本文的范围之内。

  使用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。

说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:

比如最常见的“生产者,消费者模型”。

 很多人(包括我之前)都对其中的一些概念不够明确,如同步、并发等等,让我们先建立一个数据字典,以免产生误会。

∙多线程:

指的是这个程序运行时产生了不止一个线程

∙并行:

与多线程其实是一个概念,当然多进程也属于并行。

∙并发:

与并行类似,唯一不同的是:

并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

∙线程安全:

在并发的情况之下,线程的调度顺序不影响任何结果。

这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。

viceversa

∙同步:

是碰到的多线程情况中相对复杂的,需要人为的对线程进行一些调度,来保证结果的准确。

在保证结果准确的同时,提高性能,才是优秀的程序。

 一些结论:

  并行肯定是线程安全的;

  同步场景肯定是线程不安全的(做了控制之后可能安全);

  并发不一定线程安全。

 

  好了,让我们开始吧。

我准备分成几部分来总结涉及到多线程的内容:

1.基础中的基础-状态切换

2.每个对象都有的方法(机制)

3.基本线程类

4.其他相关多线程控制类

  

 

   

1.基础中的基础-状态切换

  

  各种状态一目了然,值得一提的是"blocked"这个状态

   转自XX搜索结果:

线程在Running的过程中可能会遇到阻塞(Blocked)情况

  ①.调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。

  ②.调用wait(),使该线程处于等待池(waitblockedpool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lockblockedpool),释放同步锁使线程回到可运行状态(Runnable)

  ③.对Running状态的线程加同步锁(Synchronized)使其进入(lockblockedpool),同步锁被释放进入可运行状态(Runnable)。

  

  此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。

Thread类中的yield方法可以让一个running状态的线程转入runnable。

  

2. 每个对象都有的方法(机制)

  synchronized,wait,notify

  他们是应用于同步问题的人工线程调度工具。

最基本的使用方式搜索一下可以轻易得到,在这里就不在赘述。

至于其本质,首先就要明确monitor的概念,对java中的每个对象都有一个监视器,来监测并发代码的重入。

在非多线程编码时该监视器不发挥作用,反之如果在synchronized范围内,监视器发挥作用。

 

  wait/notify必须存在于synchronized块中。

并且,这三个关键字针对的是同一个监视器(某对象的监视器)。

这意味着wait之后,其他线程可以进入同步块执行。

   当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。

也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。

  

  

3. 基本线程类

  Thread类,Runnable接口,Callable接口

  

  Thread类实现了Runnable接口,启动一个线程的方法:

 

  第一种.继承Thread类,覆盖run(),然后调用

    MyThreadmy=newMyThread();

    my.start();

 

  Thread类相关方法:

    public staticThread.yield() //当前线程可转让cpu控制权,让别的就绪状态线程运行(切换)

    public staticThread.sleep()  //暂停一段时间

    publicjoin() //在一个线程中调用other.join(),将等待other执行完后才继续本线程。

    后两个函数皆可以被

    publicinterrupte()打断

    关于中断:

它并不像stop方法那样会中断一个正在运行的线程。

线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。

终端只会影响到wait状态、sleep状态和join状态。

被打断的线程会抛出InterruptedException

    Thread.interrupted()检查当前线程是否发生中断,返回boolean

    synchronized在获锁的过程中是不能被中断的

    中断是一个状态!

interrupt()方法只是将这个状态置为true而已。

所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。

如果在正常运行的程序中添加while(!

Thread.interrupted()),则同样可以在中断后离开代码体

 

    Thread类其他:

    ps.thread类写的时候最好要设置线程名称Thread.name,并设置线程组ThreadGroup,目的是方便管理。

在出现问题的时候,打印线程栈(jstack-pid)一眼就可以看出是哪个线程出的问题,这个线程是干什么的。

 

  

  Thread类之异常:

    

    如图,在其他线程中(Task类)抛出异常是无法通过常用的try-Catch机制来捕获异常的,若没有设置UncaughtExceptionHandler的话异常将不会被捕捉。

程序也能够正常的执行下去,这时候就有非常大的危险,因为你不知道代码存在问题--直到客户投诉。

 

  第二种.实现Runnable接口,实现run()然后调用

 

1 Threadmy=newThread(newMyRunnableImpl());

2 my.start();

    

  第三种.实现Callable接口,实现call()

 

1 ExecutorServicee=Executors.newFixedThreadPool(3);

2Futurefuture=e.submit(newmyCallable());//submit方法有多重参数版本,及支持callable也能够支持runnable接口类型.具体待研究

 

    future模式:

并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。

其中Future对象用来存放该线程的返回值以及状态

    future.isDone() //return true,false无阻塞

    future.get() //return返回值,阻塞直到该线程运行结束。

 

4. 其他相关多线程控制类  

 

∙volatile关键字

    volatile关键字用于将多线程中的变量置为同一副本。

用于修饰字段。

和syncronized之间差别是不加锁。

深入理解:

其实道理上讲同一实例的同一属性本身只有一个副本。

但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。

在线程安全的情况下加volatile会牺牲性能。

   

  

∙ThreadLocal类

  用处:

保存线程的独立变量。

对一个线程类(继承自Thread)

  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

 

  实现:

每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。

功能还是一个map。

)以本身为key,以目标为value。

  主要方法是get()和set(Ta),set之后在map里维护一个threadLocal->a,get时将a返回。

ThreadLocal是一个特殊的容器。

 

  

∙原子类(AtomicInteger、AtomicBoolean……)

  

  如果使用atomicwrapperclass如atomicInteger,或者使用自己保证原子的操作,则等同于syncronized

   

     AtomicIpareAndSet(intexpect,intupdate),返回值为boolean

   该方法可用于实现乐观锁,考虑如下场景:

a给b付款10元,a扣了10元,b要加10元。

此时c给b2元,但是b的加十元代码约为:

1intvalue=b.value;

2value+=10;

3b.value=value;

   在第一行之后b.value发生了变更。

那么2元就被吞掉了。

   有了这个方法之后,可以如此

   第三行替换为:

      

1      if(pareAndSet(old,value)){

2return;

3}else{

4//tryagain

5//ifthatfails,rollbackandlog

6}

 

  AtomicReference

   对于AtomicReference来讲,也许对象会出现,属性丢失的情况,即oldObject==current,但是oldObject.getPropertyA!

=current.getPropertyA。

   这时候,AtomicStampedReference就派上用场了。

这也是一个很常用的思路,即加上版本号

   

∙Lock类 

   lock:

在java.util.concurrent包内。

共有三个实现:

ReentrantLock,ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock。

   主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。

功能类似但有一些区别。

  区别如下:

     1.lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)

     2.提供多种加锁方案,lock阻塞式,trylock无阻塞式,lockInterruptily可打断式,还有trylock的带超时时间版本。

     3.本质上和监视器锁(即synchronized是一样的)

     4.能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。

     5.和Condition类的结合。

     6.性能更高,对比如下图:

    

    (图片来自互联网)

 

    

    ReentrantLock

    可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。

    使用方法是:

    1,先new一个实例

      static ReentrantLock r=newReentrantLock();

    2,加锁

      r.lock()

      或r.lockInterruptibly();

    此处也是个不同,后者可被打断。

当a线程lock后,b线程阻塞,此时如果是lockInterruptibly,那么在调用b.interrupt()之后,b线程退出阻塞,并放弃对资源的争抢,进入catch块。

(如果使用后者,必须throwinterruptableexception或catch)

    3,释放锁

    r.unlock()

    必须做!

何为必须做呢,要放在finally里面。

以防止异常跳出了正常流程,导致灾难。

这里补充一个小知识点,finally是可以信任的:

经过测试,哪怕是发生了OutofMemoryError,finally块中的语句执行也能够得到保证。

 

    ReentrantReadWriteLock

    可重入读写锁(读写锁的一个实现)

  ReentrantReadWriteLocklock=new ReentrantReadWriteLock()

  ReadLockr=lock.readLock();

  WriteLockw=lock.writeLock();

 

  两者都有lock,unlock方法。

写写,写读互斥;读读不互斥。

可以实现并发读的高效线程安全代码

 

 

∙容器类 

  这里就讨论比较常用的两个:

BlockingQueue和ConcurrentHashMap

    BlockingQueue

    阻塞队列。

 该类是java.util.concurrent包下的重要类,通过对Queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或取出元素。

类似于一个管  道,特别适用于先进先出策略的一些应用场景。

普通的queue接口主要实现有PriorityQueue(优先队列),有兴趣可以研究

    BlockingQueue在队列的基础上添加了多线程协作的功能:

    

    除了传统的queue功能(表格左边的两列)之外,还提供了阻塞接口put和take,带超时功能的阻塞接口offer和poll。

put会在队列满的时候阻塞,直到有空间时被唤醒;take在队 列空的时候阻塞,直到有东西拿的时候才被唤醒。

用于生产者-消费者模型尤其好用,堪称神器。

    常见的阻塞队列有:

ArrayListBlockingQueue,LinkedListBlockingQueue,DelayQueue,SynchronousQueue

  

    ConcurrentHashMap

     高效的线程安全哈希map。

请对比hashTable,concurrentHashMap,HashMap

 

∙管理类 

    管理类的概念比较泛,用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的工具做一些封装。

    了解到的值得一提的管理类:

ThreadPoolExecutor和JMX框架下的系统级管理类 ThreadMXBean

    ThreadPoolExecutor

    如果不了解这个类,应该了解前面提到的ExecutorService,开一个自己的线程池非常方便:

1  ExecutorServicee=Executors.newCachedThreadPool();

2ExecutorServicee=Executors.newSingleThreadExecutor();

3ExecutorServicee=Executors.newFixedThreadPool(3);

4//第一种是可变大小线程池,按照任务数来分配线程,

5//第二种是单线程池,相当于FixedThreadPool

(1)

6//第三种是固定大小线程池。

7//然后运行

8e.execute(newMyRunnableImpl());

 

    该类内部是通过ThreadPoolExecutor实现的,掌握该类有助于理解线程池的管理,本质上,他们都是ThreadPoolExecutor类的各种实现版本。

请参见javadoc:

 

     

    该类初始化时所需的各参数解释如下:

ocorePoolSize:

池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。

omaximumPoolSize:

线程最大值,线程的增长始终不会超过该值。

okeepAliveTime:

当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。

回收前处于wait状态

ounit:

时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS 

oworkQueue:

待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving)

othreadFactory:

线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。

    

    ThreadMXBean

    该类是JMX框架下的管理类,其他的MXbean也比较有价值,此处不讨论。

简单实现如下:

1ThreadMXBeanm=ManagementFactory.getThreadMXBean();

2ThreadInfo[]dumpAllThreads=m.dumpAllThreads(true,true);

3System.out.println(dumpAllThreads[0].toString());   

    javase6开始提供了大量性能监测工具,和线程相关的就是ThreadMXBean。

有很多想象空间,

     1.可以开发专门的监测页面,制成图标

     2.设定阀值监控报警

     3.设置开关,让系统可控

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 求职职场 > 自我管理与提升

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1