• 线程的状态

java阻塞线程 java多线程阻塞_jvm

 

新建状态:用new语句创建的线程对象处于新建状态,此时它和其它的java对象一样,仅仅在堆中被分配了内存空间。
就绪状态:当一个线程创建了以后,其他的线程调用了它的start()方法,该线程就进入了就绪状态;处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
运行状态:处于这个状态的线程占用CPU,执行线程体的代码。
阻塞状态:当线程处于阻塞状态时,java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。

(阻塞状态分为三种情况:
1、等待-位于对象等待池中的阻塞状态:当线程运行时,如果执行了某个对象的wait()方法,java虚拟机就回把线程放到这个对象的等待池中。
2、阻塞-位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程占用,JVM就会把这个线程放到这个对象的琐池中。
3、睡眠-其它的阻塞状态:当前线程执行了sleep()方法,或者调用了其它线程的join()方法让其它线程等待当前线程执行完再执行,或者发出了I/O请求时,就会进入这个状态中。)


死亡状态:当线程退出了run()方法,就进入了死亡状态,该线程结束了生命周期。
                  或者正常退出;
                      或者遇到异常退出;
                      Thread类的isAlive()方法判断一个线程是否活着,当线程处于死亡状态或者新建状态时,该方法返回false,在其余的状态下,该方法返回true.

  • 线程调度

线程调度模型:分时调度模型和抢占式调度模型。


JVM采用抢占式调度模型


所谓的多线程的并发运行,其实是指宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。(线程的调度不是跨平台,它不仅取决于java虚拟机,它还依赖于操作系统)



如让一个线程给另外一个线程运行的机会,可以采取以下的办法之一:


1、 调整各个线程的优先级;


2、 让处于运行状态的线程调用Thread.sleep()方法;


3、 让处于运行状态的线程调用Thread.yield()方法;


4、 让处于运行状态的线程调用另一个线程的join()方法。



调整各个线程的优先级


Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。


如果希望程序能够移值到各个操作系统中,应该确保在设置线程的优先级时,只使用

MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY这3个优先级。



线程睡眠:当线程在运行中执行了sleep()方法时,它就会放弃CPU,转到阻塞状态。


线程让步:当线程在运行中执行了Thread类的yield()静态方法时,如果此时具有相同优先级的其它线程处于就绪状态,那么yield()方法将把当前运行的线程放到运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。

Sleep()方法和Yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程,两者的区别
         1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低优先级的线程一个运行机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。

 2、当线程执行了sleep(long millis)方法后,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法后,将转到就绪状态。
         3、sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常。
         4、sleep()方法比yield()方法具有更好的移植性。

等待其它线程的结束:join()
          当前运行的线程可以调用另一个线程的 join()方法,当前运行的线程将转到阻塞状态,直到另一个线程运行结束,它才恢复运行。

线程的同步

synchronized修饰符在静态方法与非静态方法上的区别:

如:ThreadDemo.class,实例化对象:ThreadDemo  threadDemo=new threadDemo()

静态:public static synchronized void getValues(){...} --> 在同步前要获得 ThreadDemo.class 的锁。

非静态:public synchronized void getValues(){...} --> 在同步前要获得实例化对象 threadDemo 的锁。

原子操作:根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括long和double, 因为JVM看到的基本存储单位是32位,而long 和double都要用64位来表示。所以无法在一个时钟周期内完成。

自增操作(++)不是原子操作,因为它涉及到一次读和一次写。

原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。

同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。

同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。

当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的执行方式。
1、假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
2、 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。
(一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁,如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中)

线程同步的特征:
1、如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制)
2、 每个对象都有唯一的同步锁。
3、 在静态方法前面可以使用synchronized修饰符。
4、当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。
5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。

线程安全的类:
1、 这个类的对象可以同时被多个线程安全的访问。
2、 每个线程都能正常的执行原子操作,得到正确的结果。
3、 在每个线程的原子操作都完成后,对象处于逻辑上合理的状态。

释放对象的锁:
1、 执行完同步代码块就会释放对象的锁。
2、 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。
3、 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。

死锁
当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁。JVM不监测也不试图避免这种情况,因此保证不发生死锁就成了程序员的责任。

如何避免死锁
一个通用的经验法则是:当几个线程都要访问共享资源A、B、C 时,保证每个线程都按照同样的顺序去访问他们。

线程通信
Java.lang.Object类中提供了两个用于线程通信的方法:
1、 wait():执行了该方法的线程释放对象的锁,JVM会把该线程放到对象的等待池中。该线程等待其它线程唤醒。
2、 notify():执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。


常用方法介绍:

  1. start():启动线程(注:run()也可以启动线程,但其是启动线程体本身,只是启动一个线程;start()可以启动多个线程,也是标准用法)。
  2. sleep():线程休眠。
  3. wait():线程等待。
  4. notify():唤醒单个线程。
  5. notifyAll():唤醒多个线程。
  6. yield(): 为静态方法,功能是暂停当前正在执行的线程对象,并执行其他线程,即线程让步。
  7. join():将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法,即异步执行的线程变成同步执行。
  8. interrupt():中断线程,捕获异常后break即可。
  9. setPriority():设置线程的优先级,线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认的优先级为5;在start()前调用。
  10. setDaemon(boolean on):将该线程标记为守护线程或用户线程;当正在运行的线程都是守护线程时,Java 虚拟机退出; 该方法必须在启动线程前调用。
  • 线程死锁的原因
  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。