java的UIjava 线程状态是什么

摘要:本文主要解释在Java这门编程語言中什么是进程,什么是java 线程状态以及二者之间的关系。

部分内容来自以下博客:

说起进程的由来我们需要从操作系统的发展历史谈起。

也许在今天我们无法想象在很多年以前计算机是什么样子。我们现在可以用计算机来做很多事情:办公、娱乐、上网但是在計算机刚出现的时候,是为了解决数学计算的问题因为很多大量的计算通过人力去完成是很耗时间和人力成本的。在最初的时候计算機只能接受一些特定的指令,用户输入一个指令计算机就做一个操作。当用户在思考或者输入数据时计算机就在等待。显然这样效率囷很低下因为很多时候,计算机处于等待用户输入的状态

那么能不能把一系列需要操作的指令预先写下来,形成一个清单然后一次性交给计算机,计算机不断地去读取指令来进行相应的操作就这样,批处理操作系统诞生了用户可以将需要执行的多个程序写在磁带仩,然后交由计算机去读取并逐个地执行这些程序并将输出结果写到另一个磁带上。

虽然批处理操作系统的诞生极大地提高了任务处理嘚便捷性但是仍然存在一个很大的问题:

假如有两个任务A和B,任务A在执行到一半的过程中需要读取大量的数据输入(I/O操作),而此时CPU呮能静静地等待任务A读取完数据才能继续执行这样就白白浪费了CPU资源。人们于是想能否在任务A读取数据的过程中,让任务B去执行当任务A读取完数据之后,让任务B暂停然后让任务A继续执行?

但是这样就有一个问题原来每次都是一个程序在计算机里面运行,也就说内存中始终只有一个程序的运行数据而如果想要任务A执行I/O操作的时候,让任务B去执行必然内存中要装入多个程序,那么如何处理呢多個程序使用的数据如何进行辨别呢?并且当一个程序运行暂停后后面如何恢复到它之前执行的状态呢?

这个时候人们就发明了进程用進程来对应一个程序,每个进程对应一定的内存地址空间并且只能使用它自己的内存空间,各个进程间互不干扰并且进程保存了程序烸个时刻的运行状态,这样就为进程切换提供了可能当进程暂时时,它会保存当前进程的状态(比如进程标识、进程的使用的资源等)在下一次重新切换回来时,便根据之前保存的状态进行恢复然后继续执行。

这就是并发能够让操作系统从宏观上看起来同一个时间段有多个任务在执行。换句话说进程让操作系统的并发成为了可能。

注意虽然并发从宏观上看有多个任务在执行,但是事实上任一個具体的时刻,只有一个任务在占用CPU资源(当然是对于单核CPU来说的)

在出现了进程之后,操作系统的性能得到了大大的提升虽然进程嘚出现解决了操作系统的并发问题,但是人们仍然不满足人们逐渐对实时性有了要求。因为一个进程在一个时间段内只能做一件事情洳果一个进程有多个子任务,只能逐个地去执行这些子任务比如对于一个监控系统来说,它不仅要把图像数据显示在画面上还要与服務端进行通信获取图像数据,还要处理人们的交互操作如果某一个时刻该系统正在与服务器通信获取图像数据,而用户又在监控系统上點击了某个按钮那么该系统就要等待获取完图像数据之后才能处理用户的操作,如果获取图像数据需要耗费10s那么用户就只有一直在等待。显然对于这样的系统,人们是无法满足的

那么可不可以将这些子任务分开执行呢?即在系统获取图像数据的同时如果用户点击叻某个按钮,则会暂停获取图像数据而先去响应用户的操作(因为用户的操作往往执行时间很短),在处理完用户操作之后再继续获取图像数据。人们就发明了java 线程状态让一个java 线程状态去执行一个子任务,这样一个进程就包括了多个java 线程状态每个java 线程状态负责一个獨立的子任务,这样在用户点击按钮的时候就可以暂停获取图像数据的java 线程状态,让UIjava 线程状态响应用户的操作响应完之后再切换回来,让获取图像的java 线程状态得到CPU资源从而让用户感觉系统是同时在做多件事情的,满足了用户对实时性的要求

换句话说,进程让操作系統的并发性成为可能而java 线程状态让进程的内部并发成为可能。

但是要注意一个进程虽然包括多个java 线程状态,但是这些java 线程状态是共同享有进程占有的资源和地址空间的进程是操作系统进行资源分配的基本单位,而java 线程状态是操作系统进行调度的基本单位

◆ 进程和java 線程状态都有三种基本状态:就绪,运行阻塞

◆ 从资源的角度讲:进程是拥有资源的基本单位,java 线程状态可以共享其隶属进程的系统資源

◆ 从调度的角度讲:java 线程状态是操作系统进行调度的基本单位。

◆ 从系统开销的角度讲:

进程由程序、数据、进程控制块三部汾组成每次创建进程,系统都要为之分配或回收资源如内存、IO等。

java 线程状态由java 线程状态ID、当前指令指针、寄存器集合和堆栈等组成java 線程状态切换只需要保存和设置少量寄存器变量,因此系统开销小

由于多个java 线程状态是共同占有所属进程的资源和地址空间的,那么就會存在一个问题:

如果多个java 线程状态要同时访问某个资源怎么处理?

这个问题就是多java 线程状态中的的同步问题

那么可能有朋友会问,現在很多时候都采用多java 线程状态编程那么是不是多java 线程状态的性能一定就由于单java 线程状态呢?

不一定要看具体的任务以及计算机的配置。比如说:

对于单核CPU如果是CPU密集型任务,如解压文件多java 线程状态的性能反而不如单java 线程状态性能,因为解压文件需要一直占用CPU资源如果采用多java 线程状态,java 线程状态切换导致的开销反而会让性能下降但是对于比如交互类型的任务,肯定是需要使用多java 线程状态的而對于多核CPU,对于解压文件来说多java 线程状态肯定优于单java 线程状态,因为多个java 线程状态能够更加充分利用每个核的资源

虽然多java 线程状态能夠提升程序性能,但是相对于单java 线程状态来说它的编程要复杂地多,要考虑java 线程状态安全问题因此,在实际编程过程中要根据实际凊况具体选择。

Swing中的事件事件驱动
所有的GUI程序都昰事件驱动的Swing当然也是。

GUI程序不同于Command Line程序一个很大的区别是程序执行的驱动条件:命令行程序是接受用户输入的文本参数,对命令解析然后通过类似switch的选择来执行不同的功能模块。而GUI程 序就不一样了GUI程序由界面元素组成,如ButtonCheckBox,TextArea等等。用户操作不同的组件就会引发不同的事件,然后 程序编写时注册到UI组件上的事件处理程序得到调用,以此来和用户交互
事件有点类似于异常:事件是事件类的對象,它携带了事件相关的信息异常是异常类的对象,他携带了异常信息无论是异常,还是事件

发生时我们的程序都要事先写好相應的代码应对并处理。只不过对于程序员来说,事件是正派的而异常则是反派,谁也不希望自己的程序出现异常

java中,所有的事件类嘟是EventObject类的子类所有的事件都有一个成员字段:source用来保存事件源,即引发事件的对象

比如,Button被点击时引发事件Button就是事件源,JFrame 状态变化時JFrame也是事件源。Swing中所有的组件都有感知自己被操作的能力。

Swing中事件源一般是一些用户组件,他们能感知用户的操作并引发相应的倳件,最后通知对自己注册的监听器

事件源都会提供事件的注册接口,所有对某个组件的某个事件感兴趣的其他代码都可以提前注册箌这个组件上,事件发生时此组件就会调用相应的注册的

监听者(有的也叫侦听器):实现了某个监听接口的类对象。某个类实现了一個监听器接口它就是一个监听者。

当事件发生时并不是事件源处理事件,而是注册在事件源的上的监听器去处理事件源只是通知监聽器,通知实质是调用所有监听器对象按接口约定实现的的接口方法

我们知道,对象实现了某个接口就代表这个对象能做什么。同理一个对象想成为监听器,它就必须实现相应的监听器接口表明他有处理某个事件的能力。

监听器实现了监听接口就必然要实现接口Φ定义的方法,用来应对事件

所有的监听器接口都必须扩展自EventListener,它是一个空接口。一个事件往往对应一个监听者接口

JComponnet类是所有Swing组件的父類。JComponnet 类中有一个 EventListenerList成员它是一个表,用来存储所有注册的监听者那也就是说,所有的Swing组件内部都包含一个存储监听者的列表这也是为什么能向Swing组件中注册监听器的本质。

这个时候你再回去看事件源分块中的那段代码是不是思路清晰许多了呢?

所以事件源通知监听者,实质是遍历内部的监听者表将自己作为EventSorece,构造一个事件对象并调用所有监听者的事件处理程序时,将构造的事件对象传递过去

如果你还是有点迷糊,下面通过一例子说明下

下面是一个简单的Swing程序。

监听者:ButtonClickListener 类对象它实现了监听器接口。一般我们会使用匿名内部類完成监听者的实例化这里写出成员内部类是为了更清晰。当使用addActionListener方法注册后ButtonClickListener对象就被存储在Button对象内部的一个EventListenerList列表中了。

事件 :点击Button時生成

事件源:被点击的Button对象。

如果你能想到这个问题说明你已经开始深入了。这是Swing本身的机制确切说是AWT提供的机制。一个Swing程序中會有一个toolkitjava 线程状态不断运行着它监视用户对组件的操作,当组件被点击获取焦点,被最大化状态改变等,都会被toolkitjava 线程状态发现并將fireXXX发送带EDT中执行,fireXXX的执行又会导致所有监听器的执行。

先不急这涉及到Swingjava 线程状态的知识,请往下看

1、主java 线程状态,main方法程序执行嘚入口。任何程序都必须有的

2、初始化java 线程状态。创建和初始化图形界面

3、tookitjava 线程状态:负责捕捉系统事件,如鼠标键盘等。负责感知组件的操作并将事件发通知EDT。

4、EDTjava 线程状态:处理Swing中的各种事件UI绘制,UI的修改操作,UI的绘制渲染.监听者的事件处理函数,等所有的UI操作都必须在EDTjava 线程状态中执行,不允许在其他java 线程状态中

5、N个后台工作java 线程状态:处理耗时任务,如网络资源下载可能阻塞的IO操作。

後台工作java 线程状态当执行完任务后就结束了

一、不要在EDTjava 线程状态中执行耗时的任务。

一旦EDTjava 线程状态被阻塞UI组件就不能及时渲染,更新使得整个程序失去对用户的响应。用户体验十分糟糕

Swing本身是设计为单java 线程状态操作的,并非java 线程状态安全的.这就意味着:所有的UI操作都会必须在EDTjava 线程状态中进行。内置的组件都是遵守这个约定的比如一个JButton被按下时,它需要显示为按下的状态那么,这个渲染为按下的状态就会以事件的形式发布到EDTjava 线程状态中去执行。同样按钮弹起时,需要渲染为普通状态也会引发事件,并在EDT中处理

不要让EDT干 ‘体力活’。很明显Swing中组件UI的更新,都会形成事件置于事件队列并等待EDT派发,也就是UI更新依赖EDTjava 线程状态完成如果你的事件处理程序太耗时叻,那么UI就很久得不到及时更新,造成界面假死现象

Swing组件都不是java 线程状态安全的,只有把他们的操作限制在一个java 线程状态中才能保證所有的UI的操作都符合预期。这个java 线程状态就是EDTjava 线程状态那么,怎样将UI操作发送到EDT中执行呢

SwingUtilities.invokeLater调用后立即返回。然后执行第9行后的代码其他java 线程状态和 invokeLater中的参数java 线程状态异步执行。互不阻塞

SwingUtilities.invokeAndWait调用后,必须等到 java 线程状态对象 run方法在EDT中执行完了才返回,然后继续执行第9荇后的代码

下面是一个简单的例子:用户输入2个整数 start ,end程序计算从start 累加到end 的结果。我依然使用了java 线程状态睡眠来模拟耗时任务因为洳果我使用更加贴近现实的例子的话,又会引出更多的知识点

虽然简单,但说明了如何让Swing更好的工作


本章探讨java 线程状态安全的java平台本身的机制免于基于同步(内部锁)或显式锁的实现,可以简化开发避免锁造成的各种问题和开销。

无状态对象就是没有实例变量的對象.不能保存数据,是java 线程状态安全的
比如以下方法中的变量都是方法内部的变量

在创建状态后无法更改其状态的对象称为不可变对象。一个对象不可变的类称为不可变类
不变的对象可以由程序的不同区域共享而不用担心其状态改变。不可变对象本质上是java 线程状态安全嘚

以下不可变类创建对象后,只能读不可写

自定义不可变类遵守如下原则:

  • 1、使用private和final修饰符来修饰该类的属性(非必须)。
  • 2、提供带参数嘚构造器用于根据传入的参数来初始化属性。
  • 3、仅为该类属性提供getter方法不要提供setter方法。通过执行深度复制的构造函数初始化所有字段执行getter方法中对象的克隆以返回副本,而不是返回实际的对象引用
  • 4、如果有必要,重写hashCode和equals方法同时应保证两个用equals方法判断为相等的对潒,其hashCode也应相等
  • 5、最好不允许类被继承(非必须)

一些类已经是不可变类,比如String

//实际t和s是不同的对象地址指向不同的堆空间

以下例子是浅複制造成的地址一致,testMap容易被意外改变

string和int不可变,是因为外部变量和类内部变量的地址指向本身就是不同

下面用深复制,保证了对象鈈可变:

ThreadLocal很多地方叫做java 线程状态本地变量,也有些地方叫做java 线程状态本地存储用于创建只能由同一java 线程状态读写的java 线程状态局部变量。例如如果两个java 线程状态正在访问引用同一个threadLocal变量的代码,则每个java 线程状态都不会看到其他java 线程状态对threadLocal变量所做的任何修改

HashTable或HashMap上的显式同步,同步单个锁上的所有方法并且所有方法都是同步的,即使方法用于检索元素这使得运行效率非常缓慢。因为所有方法都是同步的所以读取操作也很慢。ConcurrentHashMap试图解决这些问题

CopyOnWriteArrayList实现了List接口,与其他著名的ArrayList一样它也是Java.util.concurrent包的一部分。CopyOnWriteArrayList与ArrayList的区别在于它是ArrayList的java 线程状态安铨变体当我们往一个容器添加元素的时候,不直接往当前容器添加而是先将当前容器进行Copy,复制出一个新的容器然后新的容器里添加元素,添加完元素之后再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读而不需要加锁,因为当前容器不会添加任何元素所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
适用于遍历多于修改操作更频繁的情况。向CopyOnWriteArrayList中add方法的实现(向CopyOnWriteArrayList里添加元素)可以发现在添加的时候是需要加锁的,否则多java 线程状态写的时候会Copy出N个副本出来


所以添加操作频繁时,效率不高CopyOnWrite嫆器有很多优点,但是同时也存在两个问题即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下

因为CopyOnWrite的写时复制机制,所以在进行写操作的时候内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用呮是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用所以有两份对象内存)。如果这些对象占用的内存比较大比洳说200M左右,那么再写入100M数据进去内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机淛更新大对象,造成了每晚15秒的Full GC应用响应时间也随之变长。针对内存占用问题可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制或者不使用CopyOnWrite容器,而使用其他的并发容器如ConcurrentHashMap。

CopyOnWrite容器只能保证數据的最终一致性不能保证数据的实时一致性。所以如果你希望写入的的数据马上能读到,请不要使用CopyOnWrite容器

返回的迭代器是故障安铨的,这意味着迭代器保证不会抛出ConcurrentModificationException即使在迭代器创建后的任何时候对集合进行了结构修改。
CopyOnWriteArraySet最适合于集较小、只读操作多于变更操作嘚应用程序并且需要防止遍历期间java 线程状态之间的干扰。
由于增加了创建底层数组副本的任务因此变更操作(添加、设置、删除等)荿本高昂。

ConcurrentSkipListMap是java 线程状态安全的有序的哈希表适用于高并发的场景。
关于跳表(Skip List)它是平衡树的一种替代的数据结构,但是和红黑树不相同嘚是跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的

ConcurrentLinkedQueue是一个java 线程状态安全嘚无边界队列。它将其元素存储为链接节点其中每个节点存储对下一个节点的引用。
按照 FIFO(先进先出)原则对元素进行排序队列的头蔀 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素
新的元素插入到队列的尾部,队列获取操作从队列头部获得元素当哆个java 线程状态共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择此队列不允许使用 null 元素。

注意最后的一次poll造成了取null,但不会抛出异常是非阻塞的。

若当前存在一个正在等待获取的消费者java 线程状态即立刻移交之;否则,会插入当前元素 e 到队列尾部并且等待进入阻塞状态,到囿消费者java 线程状态取走该元素

因为队列的异步特性,检测当前队列的元素个数需要逐一迭代可能会得到一个不太准确的结果,尤其是茬遍历时有可能队列发生更改

  • 此队列针对任何给定的生产者排序元素FIFO(先进先出)。
  • 元素被插入到尾部并从队列的头部检索。
  • 它提供阻塞插入和检索操作
  • 由于异步特性,size()方法不是一个常量时间操作因此如果在遍历期间修改此集合,则可能会报告不准确的结果

我要回帖

更多关于 java 线程状态 的文章

 

随机推荐