博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java多线程系列
阅读量:6701 次
发布时间:2019-06-25

本文共 5117 字,大约阅读时间需要 17 分钟。

1、参考资料(多线程系列)

1、:

1.1、基础篇

01. 

02. 

03. 

04. 

05. 

06. 

07.  

08. 

09. 

10. 

11. 

1.2、JUC原子类 

12.  

13. 

14. 

15. 

16. 

 1.3、JUC锁

17. 

18. 

19.  

20.  

21.  

22. 

23. 

24.  

25. 

26. 

27.  

1.4、JUC集合

28. 

29. 

30. 

31. 

32. 

33. 

34. 

35.  

36. 

37. 

1.5、

38. 

39. 

40. 

41. 

42. 

43. 

2、简要总结

  多线程是Java中不可避免的一个重要主体。从本章开始,我们将展开对多线程的学习。接下来的内容,是对“JDK中新增JUC包”之前的Java多线程内容的讲解,涉及到的内容包括,Object类中的wait(), notify()等接口;Thread类中的接口;synchronized关键字。

注:JUC包是指,Java.util.concurrent包,它是由Java大师Doug Lea完成并在JDK1.5版本添加到Java中的。

2.1、线程状态

说明

线程共包括以下5种状态。
1. 新建状态(New)         : 线程对象被创建但还没有调用start方法时,处于新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked)  : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
    (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
    (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead)    : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程的5种状态涉及到的内容包括Object类, Thread和synchronized关键字。这些内容我们会在后面的章节中逐个进行学习。

Object类,定义了wait(), notify(), notifyAll()等休眠/唤醒函数。
Thread类,定义了一些列的线程操作函数。例如,sleep()休眠函数, interrupt()中断函数, getName()获取线程名称等。

Java线程状态:

注:其实Java在Thread类中定义了线程的状态(enum State),共有六种:NEW、RUNNABLE、TERMINATED、BLOCKED、WAITING、TIMED_WAITING。

 

  • RUNNABLE包括上述的Runnable和Running。
  • WAITING表示无线等待,需要被唤醒,如执行wait()、join()方法
  • TIMED_WAITING表示有限等待,在一定时间后自动唤醒,如执行wait(long timems)、join(long timems)、sleep(long timems)方法
  • BLOCKED表示阻塞,线程等待进入同步区时进入此状态

 

2.2、线程状态转换方法

详见:

2.3、同步互斥

同步:在多个线程并发访问共享数据时,保证数据在同一时刻只被一个(或一些,使用信号量的时候)线程使用。可以分为阻塞同步(如互斥)和非阻塞同步(需要硬件指令集的发展以保障“操作和冲突检测”的原子性,CAS操作等)。

互斥:实现同步的一种手段,属于阻塞同步。临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)是主要的互斥实现方式

互斥是因,同步是果;互斥是方法,同步是目的。

2.4、线程协作

待...

2.5、线程竞争

多线程共享内存(竞争访问同一资源)带来的问题:(参考资料:)

1、竞态条件(race condition):当多个线程访问和操作同一个对象时,最终执行结果与执行时序有关,可能正确也可能不正确

解决:

  • 使用synchronized关键字(相关:)
  • 使用显式锁
  • 使用原子变量(计数、序号等场景)或原子更新(被更新的变量需被volatile修饰)
    1 // 2、原子变量 2 // 对于count++这种操作来说,使用synchronzied成本太高了,需要先获取锁,最后还要释放锁,获取不到锁的情况下还要等待,还会有线程的上下文切换,这些都需要成本。 3 // 4 // 对于这种情况,完全可以使用原子变量代替,Java并发包中的基本原子变量类型有: 5 // AtomicBoolean:原子Boolean类型 6 // AtomicInteger:原子Integer类型 7 // AtomicLong:原子Long类型 8 // AtomicReference:原子引用类型 9 class AtomicIntegerDemo {10     private static AtomicInteger counter = new AtomicInteger(0);11 12     static class Visitor extends Thread {13         @Override14         public void run() {15             for (int i = 0; i < 100; i++) {16                 counter.incrementAndGet();17                 Thread.yield();18             }19         }20     }21 22     public static void main(String[] args) throws InterruptedException {23         int num = 100;24         Thread[] threads = new Thread[num];25         for (int i = 0; i < num; i++) {26             threads[i] = new Visitor();27             threads[i].start();28         }29         for (int i = 0; i < num; i++) {30             threads[i].join();31         }32         System.out.println(counter.get());33     }34 }35 36 // 原子更新37 class FieldUpdaterDemo {38     static class DemoObject {
    // 类DemoObject中有两个成员num和ref,声明为volatile,但不是原子变量,不过DemoObject对外提供了原子更新方法compareAndSet,它是使用字段对应的FieldUpdater实现的,FieldUpdater是一个静态成员,通过newUpdater工厂方法得到,newUpdater需要的参数有类型、字段名、对于引用类型,还需要引用的具体类型。39 private volatile int num;40 private volatile Object ref;41 42 private static final AtomicIntegerFieldUpdater
    numUpdater = AtomicIntegerFieldUpdater43 .newUpdater(DemoObject.class, "num");44 private static final AtomicReferenceFieldUpdater
    refUpdater = AtomicReferenceFieldUpdater45 .newUpdater(DemoObject.class, Object.class, "ref");46 47 public boolean compareAndSetNum(int expect, int update) {48 return numUpdater.compareAndSet(this, expect, update);49 }50 51 public int getNum() {52 return num;53 }54 55 public Object compareAndSetRef(Object expect, Object update) {56 return refUpdater.compareAndSet(this, expect, update);57 }58 59 public Object getRef() {60 return ref;61 }62 }63 64 public static void main(String[] args) {65 DemoObject obj = new DemoObject();66 obj.compareAndSetNum(0, 100);67 obj.compareAndSetRef(null, new String("hello"));68 System.out.println(obj.getNum());69 System.out.println(obj.getRef());70 }71 }
    View Code

2、内存可见性:多个线程可以共享访问和操作相同的变量,但一个线程对一个共享变量的修改,另一个线程不一定马上就能看到,甚至永远也看不到

解决:

  • 使用volatile关键字(相关:)
  • 使用synchronized关键字同步
  • 使用显式锁同步

2.6、 对象的线程安全

1、线程安全:(Brian Goetz《Java Concurrency In Practice》)当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步或在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那此对象是线程安全的。

此定义要求线程安全的代码都必须具备一个特征:代码本身封装了所有必要的正确性保障手段(如互斥同步等),令调用者无须关心多线程的问题,更无须采取任何措施保证多线程的正确调用。此并不易做到,在大多数场景中,会将此定义弱化些,若把“调用这个对象的行为”限定为“单次调用”,此定义的其他描述也能成立,则称之是线程安全的了。

2、保证线程安全的思路

  1. 锁,使用synchronized或ReentrantLock(悲观策略),参看
  2. 原子操作,循环CAS(乐观策略),参看
  3. 写时拷贝,如CopyOnWriteArrayList,适用于元素个数不多,绝大部分访问都是读,且有大量并发线程要求读,只有个别线程进行写,且只是偶尔写的场合。(参看)

 

转载地址:http://iywlo.baihongyu.com/

你可能感兴趣的文章
系统数据库
查看>>
JAVA: java产生随机数的几种方式
查看>>
调试发现的小错误
查看>>
c#中使用NetCDF存储二维数据的读写操作简单应用
查看>>
移动终端处理器构成和基带芯片概述
查看>>
Android 动态加载 (一) 态加载机制 案例一
查看>>
Oracle存储过程中异步调用的实际操作步骤
查看>>
Atitti.java android反编译解决方案-----虚拟机方案
查看>>
Java 装饰模式 (Decorator)
查看>>
JAVA虚拟机垃圾回收算法原理
查看>>
PHP开启curl_init
查看>>
动态规划法求背包问题
查看>>
【maven + hibernate(注解) +spring +springMVC】 使用maven搭建项目
查看>>
Mybatis-mapper-xml-基础
查看>>
如何在Visual Studio VS中定义多项目模板
查看>>
tcpip学习
查看>>
yii2权限控制rbac之菜单menu最详细教程
查看>>
国内四大炒股软件APP 全面技术解析
查看>>
C++ STL--queue 的使用方法
查看>>
[svc]visio绘制模具
查看>>