今天看着看着源码,突然发现Thread的一些概念 觉得那么的陌生。。。囧rz....所以特意整理一下 以备不时之需。
其实线程也就是我们计算机中正在执行程序(进程)的某个单一顺序的控制流。线程不仅可以共享进程的内存空间,而且也拥有自己的工作内存(即系统分配的线程栈空间)。
对于我们的java程序,总是会有一个默认的主线程,当JVM加载java程序时发现main方法,就会启动一个线程即主线程。如果main方法里没有其他线程,那么main返回时jvm就回结束java程序。但如果main方法中启动了其他新的线程,jvm就会在主线程和这些线程之间进行轮流切换,保证每个线程都有机会使用CPU资源。这时,我们需要注意 此时的主线程返回也不会导致java程序结束,jvm会等到该程序的所有线程结束,才会终结我们的程序。(也有例外情况,如果强制调用了Runtime的exit方法,而且安全管理器允许程序退出,则JVM也会终止程序)也即:java中的多线程其实是一种抢占机制(多个线程处于运行状态,但只允许一个线程运行,那么他们之间就会采用谁抢到算谁的的竞争方式抢占CPU)而不是分时机制。
线程的定义:
众所周知,java中定义一个线程既可以继承Thread类 亦可实现Runnable接口,具体如何玩,想必也不必我啰嗦。他们之间的区别在于,因为java是单根继承,所以我们一般使用Runnable来定义线程,使用Runnable方式 线程间可以共享相同的内存单元(包括代码和数据),并利用这些共享单元来实现数据交换、实时通信与必要的同步操作。对于Thread(Runnable target)创建的使用同一目标对象的线程,可以共享该目标对象的成员变量和方法。
另外,创建目标对象类在必要时还可以是某个特定类的子类,因此,使用Runnable接口比使用Thread的子类更具有灵活性。
(注意:具有相同目标对象的线程,run()方法中的局部变量相互独立,互不干扰)
至于Thread的启动时利用了JNI调用操作系统底层的方法。代码为证:
private native void start0();
线程的生命周期:
在上图中,我们需要注意的是:使用Object.wait()/notify()方法时,这些方法其实都是针对已经获取了对象锁进行的操作,所以从语法的角度,我们必须将Object.wait()/notify()放在synchronized(obj){...}语法块中执行。从功能上来说,wait()方法是对象在获得对象锁后,主动释放对象锁同时阻塞(休眠)当前线程,直到其他线程调用该对象的notify()方法来唤醒该线程,才能继续获取对象锁继续执行。但是有一点需要我们注意的是,notify()方法唤醒线程并不是立即生效,而是在相应的synchronized(){}语法块执行结束,自动释放锁后,JVM在wait()对象锁的线程中随机选取一个赋予对象锁,唤醒线程 继续执行。Thread.sleep()和Object.wait()都可以暂停当前线程,释放CPU资源,主要区别在Object.wait()释放CPU资源的同时,释放了对象锁的控制。Sleep()不释放的锁指的是“单线程锁” 而并非是对象锁。
以下代码依然不是同步的:
/** * @author Sonicery_D * @date 2014年11月13日 * @version 1.0.0 * @description **/ public class Share { public void print(String str){ System.out.print("["+str); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); System.out.print("thread interrupted ..."); } System.out.print("]"); } public static void main(String[] args) { Share share = new Share(); Call call1 = new Call("A",share); Call call2 = new Call("B",share); Call call3 = new Call("C",share); Call call4 = new Call("D",share); } } class Call implements Runnable{ private Share share; private String str; public Call(String str,Share share){ this.str = str; this.share = share; Thread thread = new Thread(this); thread.start(); } @Override public void run() { share.print(str); } }
我们需要将线程资源加锁:
public synchronized void print(String str){ System.out.print("["+str); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); System.out.print("thread interrupted ..."); } System.out.print("]"); }
新建状态(new):用new语句创建的线程对象处于新建状态,此时它和其它的java对象一样,仅仅在堆中被分配了内存
就绪状态(runnable):当一个线程创建了以后,其他的线程调用了它的start()方法,该线程就进入了就绪状态。处于这个状态的线程位于可运行池中,等待获得CPU的使用权.一旦JVM将CPU使用权切换给该线程时,此线程就可以脱离创建它的主线程独立开始自己的生命周期了。
线程创建后仅仅是占有了内存资源,在JVM管理的线程中还没有这个线程,此线程必须调用start()方法(从父类继承)通知JVM,这样JVM就知道又有一个新的线程排队等候切换。
当JVM将CPU使用权切换给线程时,如果线程是Thread的子类创建的,该类中的run()方法立刻执行(run()方法中规定了该线程的具体使命)。Thread类中的run()方法没有具体内容,程序要在Thread类的子类中重写run()方法覆盖父类该方法。(注意:在线程没有结束run()方法前,不要再调用start方法,否则将发生ILLegalThreadStateException异常)
运行状态(running): 一旦获取CPU(被JVM选中),线程就进入运行(running)状态,线程的run()方法才开始被执行;在运行状态的线程执行自己的run()方法中的操作,直到调用其他的方法而终止、或者等待某种资源而阻塞、或者完成任务而死亡;如果在给定的时间片内没有执行结束,就会被系统给换下来回到线程的等待状态;此时线程是活着的(alive);
阻塞状态(blocked):当线程处于阻塞状态时,java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。
引起阻塞状态的情况大致如下:
- (1)、JVM将CPU资源从当前线程切换给其他线程,使本线程让出CPU的使用权,并处于挂起状态。
- (2)、线程使用CPU资源期间,执行了sleep(int millsecond)方法,使当前线程进入休眠状态。sleep(int millsecond)方法是Thread类中的一个类方法,线程执行该方法就立刻让出CPU使用权,进入挂起状态。经过参数millsecond指定的毫秒数之后,该线程就重新进到线程队列中排队等待CPU资源,然后从中断处继续运行。
- 线程使用CPU资源期间,执行某个操作进入阻塞状态,如执行读/写操作引起阻塞。进入阻塞状态时线程不能进入线程队列,只有引起阻塞的原因消除时,线程才能进入到线程队列排队等待CPU资源,以便从中断处继续运行。
- (3)、线程使用CPU资源期间,执行了wait()方法,使得当前线程进入等待状态。JVM就会把线程放到这个对象的等待池中, 等待状态的线程不会主动进入线程队列等待CPU资源,必须由其他线程调用notify()方法通知它,才能让该线程从新进入到线程队列中排队等待CPU资源,以便从中断处继续运行。
- (4)、 位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程占用,JVM就会把这个线程放到这个对象的琐池中。
死亡状态(dead):线程释放了实体,即释放了分配给线程对象的内存。线程死亡的原因有两个:
(1)、正常运行的线程完成了它的全部工作,即执行完run()方法中的全部语句,结束了run()方法。
(2)、线程被提前强制性终止,即强制run()方法结束。
有三种方法可以使终止线程。
1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
3. 使用interrupt方法中断线程。
一般来说,当调用start方法后,线程开始执行run方法中的代码。线程进入运行状态。可以通过Thread类的isAlive方法来判断线程是否处于运行状态。当线程处于运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于等待状态,也可能处于停止状态。
在线程执行的过程中,可以通过两个方法使线程暂时停止执行。这两个方法是suspend和sleep。在使用suspend挂起线程后,可以通过resume方法唤醒线程。而使用sleep使线程休眠后,只能在设定的时间后使线程处于就绪状态(在线程休眠结束后,线程不一定会马上执行,只是进入了就绪状态,等待着系统进行调度)。
虽然suspend和resume可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成一些不可预料的事情发生,因此,这两个方法被标识为deprecated(抗议)标记,这表明在以后的jdk版本中这两个方法可能被删除,所以尽量不要使用这两个方法来操作线程。
线程中常用方法总结:
join():等待此线程死亡后再继续,可使异步线程变为同步线程
也即, 线程联合:一个线程A在占有CPU资源期间,可以让其他线程调用join()方法和本线程联合,如:
B.join();
此时称A在运行期间联合了B。这时A线程立刻中断执行,一直等到它联合的线程B执行完毕,A线程 再重新排队等待CPU资源。但如果A准备联合的线程B已经结束,则B.join()不会产生任何效果。
守护线程:
线程默认是非守护线程,非守护线程也称用户线程,一个线程调用setDaemon(boolean on)方法可以将自 己设置成一个守护(Daemon)线程,如:
thread.setDaemon(true);
一个线程必须在自己运行之前设置自己是否是守护线程。守护线程是当程序中所有用户线程都已结束运行 时即使守护线程的run()方法还有需要执行的语句,守护线程也会立刻结束运行。因此守护线程用于做一些 不是很严格的工作,当线程随时结束时不会产生什么不良后果。
interrupt():Thread.interrupt()对运行的线程是不起作用的,只对阻塞的线程起作用。Thread.interrupt()不会中断一个正在运行的线程,Thread.interrupt()实际完成的是在线程处于阻塞状态时,抛出一个中断信号,这样线程就可以退出阻塞状态,重新排队获取CPU资源。更确切的说,当线程被Object.wait(),Thread.join()和Thread.sleep()三个方法之一阻塞,那么再调用该线程的interrupt()方法时得到的一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
那么当线程处于上述方法阻塞,正确中断线程的方式是设置共享变量,并调用interrupt()方法(注意:变量应该先设置),如果线程没有被阻塞,调用interrupt将不起作用,否则线程将接受到异常,逃出阻塞状态。
//main 方法中在调用 interrupt()之前 将stop 设置为true public void run(){ while(!stop){ try{ //执行业务逻辑 }catch(InterruptedException e){ stop = true; logger.info("线程被Interrupt,退出"); } } }
isAlive():当线程调用start()方法并占有CPU资源后该线程的run()方法开始运行,在run()方法没有结束之前调用isAlive()返回true,当线程处于新建态或死亡态时调用isAlive()返回false。
sleep():让当前正在执行的线程休眠,有一个用法可以代替yield函数——sleep(0)
yield():暂停当前正在执行的线程对象,并执行其他线程。也就是交出CPU一段时间
sleep和yield区别:
1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。
2、当线程执行了sleep(long millis)方法后,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法后,将转到就绪状态。
3、sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常
4、sleep()方法比yield()方法具有更好的移植性
如果希望明确地让一个线程给另外一个线程运行的机会,可以采取以下的办法之一:
1、 调整各个线程的优先级
2、 让处于运行状态的线程调用Thread.sleep()方法
3、 让处于运行状态的线程调用Thread.yield()方法
4、 让处于运行状态的线程调用另一个线程的join()方法
首先,wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法。
(1).常用的wait方法有wait()和wait(long timeout):
void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。
wait()和notify()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运 行时会发生IllegalMonitorStateException的异常。
使用 wait()需要注意:
wait()方法必须放在一个循环中,因为在多线程环境中,共享对象的状态随时可能改变。当一个在对象等待池中的线程被唤醒后,并不一定立即恢复运行,等到这个线程获得了锁及CPU才能继续运行,又可能此时对象的状态已经发生了变化。
(2).Thread.sleep(long millis),必须带有一个时间参数。
sleep(long)使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;
sleep(long)可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;
sleep(long)是不会释放锁标志的。
(3).yield()没有参数。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出CPU占有权,但让出的时间是不可设定的。
yield()也不会释放锁标志。
实际上,yield()方法对应了如下操作: 先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程。
sleep方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程些时获得CPU占有权。 在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。所以yield()只能使同优先级的线程有执行的机会。
volitile 语义:
volatile相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其他的线程。
volatile包含以下语义:
(1)Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。
(2)volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取volatile变量的结果。也就是说对于volatile变量的修改,其它线程总是可见的,并且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个volatile变量的写操作后,其后的任何读操作理解可见此写操作的结果。
尽管volatile变量的特性不错,但是volatile并不能保证线程安全的,也就是说volatile字段的操作不是原子性的,volatile变量只能保证可见性(一个线程修改后其它线程能够理解看到此变化后的结果),要想保证原子性,目前为止只能加锁!
数据同步:
线程同步的特征:
1、 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制)
2、 每个对象都有唯一的同步锁
3、 在静态方法前面可以使用synchronized修饰符。
synchronized(this)与synchronized(static XXX)的区别:
在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步、互斥操作。synchronized就是针对内存区块申请内存锁,this关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而static成员属于类专有,其内存空间为该类所有成员共有,这就导致synchronized()对static成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例。
4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。
5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。
相关推荐
java concurrent 阻塞队列 线程 里面有详细的例子,下载后请认真阅读里面的内容,可能有点难以理解,请耐心
Concurrent.Thread.js是一个日本人开发的,用来让javascript也进行多线程开发的包,可以让我们将耗时的任务利用前端来模拟多线程。
Concurrent Programming in Java Thread 看看吧
java thread
j-concurrent 00 IBM developerWorks 中国 : Java 多线程与并发编程专题 02 Java 程序中的多线程 03 编写多线程的 Java 应用程序 04 如果我是国王:关于解决 Java编程语言线程问题的建议 (2) 05 构建Java并发...
NULL 博文链接:https://wuaner.iteye.com/blog/998696
一个高性能的Java线程库,该库是 JDK 1.5 中的 java.util.concurrent 包的补充,可用于基于并发消息机制的应用。该类库不提供远程的消息功能,其设计的宗旨是实现一个内存中的消息传递机制. 主要特点有: * All ...
主要介绍了java.util.concurrent.ExecutionException 问题解决方法的相关资料,需要的朋友可以参考下
* Thread类中的run方法内部的任务并不是我们所需要的,既然thread类已经定义了线程任务的位置只要重写run方法定义任务的代码即可.* 多线程执行时,
java concurrent源码 Java并发编程基础中的一些源码 com.ls.thread.connectionpool 一个简单的数据库连接池示例 com.ls.thread.threadpool 线程池技术示例 com.ls.repeatRequest 业务防重功能实现
Concurrent.and.Real.Time.Programming.in.Java.eBook-LiB.chm
Java is a concurrent platform and offers a lot of classes to execute concurrent tasks inside a Java program. With each version, Java increases the functionalities offered to programmers to facilitate ...
Writing concurrent and parallel programming applications is an integral skill for any Java programmer. Java 9 comes with a host of fantastic features, including significant performance improvements ...
Master the principles and techniques of multithreaded programming with the Java 8 Concurrency API, About This Book, Implement concurrent applications using the Java 8 Concurrency API and its new ...
This concise book empowers all Java developers to master the complexity of the Java thread APIs and concurrency utilities. This knowledge aids the Java developer in writing correct and complex ...
无论是工作学习,不断的总结是必不可少的。只有不断的总结,发现问题,弥补不足,才能长久的...java.util.concurrent.locks包下常用的类 326 NIO(New IO) 327 volatile详解 337 Java 8新特性 347 Java 性能优化 362
Over 60 simple but incredibly effective recipes for ... Intermediate–advanced level Java developers will learn from task-based recipes to use Java’s concurrent API to program thread safe solutions.
第2章介绍了 Java 并行程序开发的基础, 包括 Java 中 Thread 的基本使用方法等第3章介绍了 JDK 内部对并行程序开发的支持, 主要介绍 JUC (Java.util.concurrent) 中些工具的使用方法、 各自特点及它们的内部实现...