对JAVA多线程 并发编程的理解

对JAVA多线程并发编程的理解

Java多线程编程关注的焦点主要是对单一资源的并发访问,本文从Java如何实现支持并发访问的角度,浅析对并发编程的理解,也算是对前段时间所学的一个总结。

线程状态转换

Java语言定义了5中线程状态,在任何一个时间点,一个线程只能有且只有其中一种状态,这5中状态分别是:


新建(New):创建后尚未启动的线程处于这种状态


运行(Runable):Runable包括了操作系统线程状态中的Running和Ready,也就是处于此状态的线程可能正在执行,也有可能正在等待CPU为它分配执行时间


无限期等待(Waiting):处于这种状态的线程不会被分配CPU执行时间,他们要等待被其他线程显式的唤醒。以下方法会让线程陷入无限期的等待状态:


没有设置Timeout参数的Object.wait()方法。


没有设置Timeout参数的Thread.join()方法。


LockSupport.park()方法。


限期等待(TimedWaiting):处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显式地唤醒。在一定时间这后它们会由系统自动唤醒。以下方法会让线程进行限期等待状态:

Thread.sleep()方法。


设置了Timeout参数的Object.wait()方法。


设置了Timeout参数的Thread.join()方法。


LockSupport.parkNanos()方法


LockSupport.parkUntil()方法


阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是:“阻塞状态“在等待着获取到一个排它锁,这个事件将在另外一个线程放弃这个锁的时候发生:而”等待状态“则是在等待一段时间,或都唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。


结束(Terminated):已终止线程的线程状态,线程已经结束执行。

各种状态转换如下图:

资源监视器

在进行多线程并发操作时,关键的任务是如何保证在并发的多个线程中对竞争资源的安全访问。所谓安全访问是指一个线程正在在竞争性资源操作(会修改资源的状态)时,其它线程不能对该竞争性资源进行操作(如果所有线程都是对该资源进行读操作,则不会产生安全性问题)。

Java为解决此类问题,提出了监视器的概念。形象的讲,一个资源的监视器,好比是在要访问的资源上包了一层安全防护壳,如下图:

Java虚拟机保证,在同一时间点上,有且只会有一个线程获得该资源的监视器。换句话说,也是获得了该资源的锁。获得锁的线程可以进行后续操作,没有获得锁的线程则只能进行等待或阻塞。

一个已经获得该资源的监视器的线程,后续还可以多次获得该监视器,每获得一次,锁计数会加1,每退出一次资源监视器,锁计数也相应的减1。等到线程完全退出了该资源监视器,锁计数也清零了。此时,其他线程就可以获得该资源的监视器了。

Java监视器支持两种线程,互斥和协作。Java虚拟机通过对象锁来实现互斥,允许多个线程在一个共享数据上独立而互不干扰地工作。协作是通过Object类的wait方法和notify方法来实现,允许多个线程为了同一个目标而共同工作。

互斥可以在多线程中互斥地执行一段被称作监视区域的代码,在任务时候,特定监视器上只有一个线程执行监视区域。通常,互斥只在多个线程需要共享数据或其他资源时显得重要,如果两个线程并不使用任务公有数据或资源,它们通常会互不干扰,也就不需要进行互斥执行。

互斥帮助线程在访问共享数据时不被其他线程干扰,而协作帮助线程与其他线程共同工作。

当一个线程需要一些特别状态的数据,而由另一个线程负责改变这些数据的状态时,协作就显得非常重要。生产者和消费者就是这样一个例子。一个”读线程“会从数据缓冲区中读取数据,而另一个”写线程”会向数据缓冲区中写入数据。读线程缓冲区处理”非空“状态时才能读数据。如果读线程发现数据缓冲区是空的,它就必须等待。

Java提供”等待并唤醒“的方式来支持多线程的协作行为。对应到Java
API上就是Object对象的wait()/notify()/notifyAll()方法。

wait()方法使得当前线程必须要等待,等到另外一个线程调用notify()或者notifyAll()方法。

  当前的线程必须拥有当前对象的monitor,也即lock,就是锁。

  线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。

  要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。

  一个小比较:

  当线程调用了wait()方法时,它会释放掉对象的锁。

另一个会导致线程暂停的方法:Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。

notify()方法会唤醒一个等待当前对象的锁的线程。

  如果多个线程在等待,它们中的一个将会选择被唤醒。这种选择是随意的,和具体实现有关。(线程等待一个对象的锁是由于调用了wait方法中的一个)。

  被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁。

  被唤醒的线程将和其他线程以通常的方式进行竞争,来获得对象的锁。也就是说,被唤醒的线程并没有什么优先权,也没有什么劣势,对象的下一个线程还是需要通过一般性的竞争。

  notify()方法应该是被拥有对象的锁的线程所调用。

  (Thismethod should only be called by a thread that is the owner of this object‘smonitor.)

  换句话说,和wait()方法一样,notify方法调用必须放在synchronized方法或synchronized块中。

前面说wait、nofity、notifyAll方法被调用时,应确保在调用的对象上已经获得了该对你的监视器。即该线程操作该对象的锁。

一个线程变为一个对象的锁的拥有者是通过下列三种方法:

  1.执行这个对象的synchronized实例方法。

  2.执行这个对象的synchronized语句块。这个语句块锁的是这个对象。

  3.对于Class类的对象,执行那个类的synchronized、static方法。

时间: 2024-10-10 16:24:19

对JAVA多线程 并发编程的理解的相关文章

Java 多线程并发编程面试笔录一览

知识体系图: 1.线程是什么? 线程是进程中独立运行的子任务. 2.创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run 方法 方式二:声明实现 Runnable 接口的类.该类然后实现 run 方法 推荐方式二,因为接口方式比继承方式更灵活,也减少程序间的耦合. 3.获取当前线程信息? Thread.currentThread() 4.线程的分类 线程分为守护线程.用户线程.线程初始化默认为用户线程. setDaemon(true) 将该线程标记为

Java多线程并发编程/锁的理解

一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等方案后,问题得到解决. 限流方案见本人另一篇博客:Guava-RateLimiter实现令牌桶限流 二.乐观锁 & 悲观锁 1.乐观锁   顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号(versio

java多线程并发编程与CPU时钟分配小议

我们先来研究下JAVA的多线程的并发编程和CPU时钟振荡的关系吧 老规矩,先科普 我们的操作系统在DOS以前都是单任务的 什么是单任务呢?就是一次只能做一件事 你复制文件的时候,就不能重命名了 那么现在的操作系统,我一边在这边写BLOG,一边听歌,一边开着QQ,一边…………………… 显然,现在的操作系统都是多任务的操作系统 操作系统对多任务的支持是怎么样的呢? 每打开一个程序,就启动一个进程,为其分配相应空间(主要是运行程序的内存空间) 这其实就支持并发运行了 CPU有个时钟频率,表示每秒能执行

Java多线程-并发编程模型

以下内容转自http://ifeve.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%A8%A1%E5%9E%8B/: 并发系统可以采用多种并发编程模型来实现.并发模型指定了系统中的线程如何通过协作来完成分配给它们的作业.不同的并发模型采用不同的方式拆分作业,同时线程间的协作和交互方式也不相同.这篇并发模型教程将会较深入地介绍目前(2015年,本文撰写时间)比较流行的几种并发模型. 并发模型与分布式系统之间的相似性 本文所描述的并发模型类似于分布式系统中使

Java多线程并发编程

Thread和Runnable Runnable接口可以避免继承自Thread类的单继承的局限性. Runnable的代码可以被多个线程(Thread的实例)所共享,适合于多个线程共享资源(其实就是持有同一个runnable实例)的情况. 以火车站买票为例,分别以继承Thread类和实现Runnable接口这两种方式来模拟3个线程卖5张票: 使用Thread类模拟卖票 1 class MyThread extends Thread{ 2 3 private int ticketCount = 5

java多线程 并发 编程

转自:http://www.cnblogs.com/luxiaoxun/p/3870265.html 一.多线程的优缺点 多线程的优点: 1)资源利用率更好 2)程序设计在某些情况下更简单 3)程序响应更快 多线程的代价: 1)设计更复杂 虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂.在多线程访问共享数据的时候,这部分代码需要特别的注意.线程之间的交互往往非常复杂.不正确的线程同步产生的错误非常难以被发现,并且重现以修复. 2)上下文切换的开销 当CPU从执行一个线程切

Java 多线程 并发编程 (转)

一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源. 2.Java标准库提供了进程和线程相关的API,进程主要包括表示进程的jav

Java 多线程 并发编程

转自: http://blog.csdn.net/escaflone/article/details/10418651 一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空

java多线程 并发编程

一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源. 2.Java标准库提供了进程和线程相关的API,进程主要包括表示进程的jav