synchronized的三种应用方式jvm中synchronized的实现原理,什么是对象头

synchronized的三种应用方式

synchronsized有三种应用方式:synchronized作用于实例方法,sychronized作用于静态方法,synchronized同步代码块
造成线程安全问题的原因,有共享数据,多个线程操作共享数据。
解决方式,保证同一时刻只有一个线程操作数据,其他线程必须在线程处理完成之后在进行,访问互斥
synchronized保证统一时刻只有一个线程可以执行某个方法或者某个代码块中的共享数据进行操作,synchronized保证一个线程导致共享数据的变化能够被其他线程看到,保证可见性,完全可以替代volatile功能
修饰实例方法,作用于当前实例加锁,进入同步代码前要首先获得当前实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步方法之前首先要获取当前类对象的锁,这种用类对象加锁的方式实际上为了解决多个实例对象加锁的情况下带来的线程不安全的情况
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁
synchronized作用于实例方法
实例对象锁作用于实例方法就是用synchronized修饰实例对象中的实例方法,注意修饰的是实例方法,而不是静态方法,什么是实例方法:类中没有静态关键字修饰的方法,想使用实例方法就要先创建一个实例对象,通过创建的实例对象调用这个实例方法,静态方法就是直接通过类名就可以调用类中声明的实例方法

package binfabianchen;
// 创建Tread实例,实现Runnable实例
public class AccountingSync implements Runnable{
	static int i=0;// 共享资源(临界资源)	
	public synchronized void increase() {// synchronized修饰的是实例方法increase(),当前的所对象就是实例对象,java中的线程同步锁可以是任意对象
		i++; // i++不具有原子操作,i++操作时先读取值,之后再原来的值的基础上面加上1,分两步完成
	}
	@Override
	public void run() { // 实现runnable接口,runnable接口中run方法就是要实现的方法
		for(int j=0;j<10000;j++) {
			increase();
			if (j<2) {
				System.out.println(Thread.currentThread().getName());
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		AccountingSync instance=new AccountingSync();
		Thread t1=new Thread(instance);//thread内部是实现runnable接口的实例
		Thread t2=new Thread(instance);//同一个实例创建了两个线程
		t1.start();
		t2.start();// java中的join()方法,将交替执行的线程合并为顺序执行的线程。
		t1.join();//在主线程中调用了join()方法,等于将主线程和t1线程执行的方式改为了串行
		t2.join();//必须当线程全部执行结束之后才会调用线程,join方法还可以调用过期时间,就是说在执行到预定时间后
				 // 执行方式将继续以并行的方式执行
		System.out.println(i);
	}
}

上面的代码中,创建了AccountingSync类并且实现了runnable接口,重写了run方法,在run方法中调用了synchronized修饰的实例方法,实例方法中有一个共享变量,开启了两个线程操作同一个共享资源,因为i++操作不具有原子性,该操作实际上是先读取值,之后写一个新值,分两步,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的值,那么第二个线程就会与第一个线程一起看到同样一个值并执行加1操作,实际上就咋成了线程不安全,因此对于incerea方法必须要使用synchronized方法修饰,以便保证线程是安全的。
synchronized修饰的是实例方法increase()方法,这时候锁的对象就是实例对象instance
如果没有synchronized关键字最终输出结果就很可能小于200000。
需要注意的是,一个线程正在访问一个对象的synchronized实例方法的时候,其他线程也不能访问该对象的其他的synchronizd方法,因为一个对象只有一把锁,当一个线程获取该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的,其他synchronized实例方法,但是其他线程可以访问该实例对象的其他非synchronized方法,当然一个线程A正在访问一个对象的synchronized方法,另一个线程可以访问其他对象的synchronized方法,这样是允许的,因为两个实例对象锁并不相同,如果此时两个线程操作的数据并非共享的,线程安全是有保障的,但是如果方法中有共享数据,那么线程安全就可能无法保证:如下创建了两个实例对象,这个时候存在两把锁,这个时候,创建两个线程,一个线程可以访问这个类中的synchronnized方法,另一个对象也可以访问这个类中的synchronized方法,因为创建了同一个类的两个不同实例。

package binfabianchen;

public class AccountingSyncBad  implements Runnable{
	static int i=0;
	public synchronized void increase() {
		i++;
	}
	@Override
	public void run() {
		for(int j=0;j<10000;j++) {
		increase();
		if (j<2) {
			System.out.println(Thread.currentThread().getName());	
		}
	}
 }
	public static void main(String[] args) throws InterruptedException {
		AccountingSyncBad instance1=new AccountingSyncBad();
		AccountingSyncBad instance2=new AccountingSyncBad();
		// 线程t0和t1使用的是不同的锁,因此两个线程都可以操作类中synchronized修饰的实例访问中的共享变量,因此就出现了多个线程同时访问共享资源的情况,就无法保证线程安全。如何解决这个多个实例锁的出现的线程安全问题,通过对类对象进行加锁而不是实例对象,这样无论创建多少个实例对象也不会影响线程安全synchronized修饰类中的静态方法的时候
		// 但是对于类对象的拥有只有一个,这种情况下对象锁就是唯一的。
		Thread t0=new Thread(instance1);
		Thread t1=new Thread(instance2);
		t0.start();
		t1.start();
		t0.join();
		t1.join();
		System.out.println(i);
	}
}

两个线程访问不同对象的synchronized方法是可以的,前提是sychronised修饰的方法中的变量是不是同一个共享变量,如果synnchronsized方法中的共享变量是同样的额就会造成线程不安全,解决这种多个锁的方法是,synchronized方法作用于静态的increase方法,这样的话当前类对象就是锁对象,无论创建多少个实例对象,但是类对象只有一个,所以这种情况下对象锁就是唯一的。
synchronized作用于静态方法
synchronized作用于静态方法的时候,它的锁就是当前类的class对象。由于静态成员不转属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态成员的并发操作。如果一个线程A调用一个实例对象的非static synchronized方法,而线程B调用这个实例对象所属类对象的静态synchronized方法,这种情况是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁是当前类的class对象,而访问非静态synchronized方法占用的锁是当前实例对象锁,二者不会产生互斥现象,但是可能会造成线程安全问题
package binfabianchen;

public class AccountingSyncClass implements Runnable{

public static int i=0;
/*
 * 多线程执行,要实现runnable接口,并将要执行的方法使用run方法
 * synchronized方法作用于静态方法
 */
public static synchronized void increase() {
	i++;
}
/*
 * synchronized作用于实例方法
 * 非静态方法,和实例方法访问的时候锁不一样不会发生互斥
 */
public synchronized void increase2() {
	i++;
}
@Override
public void run() {
	for(int i=0;i<10000;i++) {
		increase();	
		increase2();
	}
}
public static void main(String[] args) throws InterruptedException {
	// 创建新实例
	Thread t1=new Thread(new AccountingSyncClass());
	Thread t2=new Thread(new AccountingSyncClass());
	// 启动线程
	t1.start();
	t2.start();
	t1.join();
	t2.join();
	System.out.println(i);
}

}
synchronized除了修饰实例方法,静态方法之外,还可以同步代码块,为什么要修饰同步代码块,因为修饰同步方法的时候,实际上是将同步方法中所有的内容进行加锁的,有些方法比较大同时比较耗时,而我们只需要同步其中的一小部分就行,因此使用synchronized同步代码块的方式可以实现只同步其中的一小部分。同步代码块中的实例就是锁对象,当线程进入同步代码块时候就要先持有当前线程的实例对象锁,如果当前有其他线程持有该对象锁那么该线程必须要进行等待。实例对象,this对象(当前实例对象)当前类对象都可以作为锁

synchronized在jvm中的是实现原理

java虚拟机中的synchronized是基于进入和退出monitor对象实现的,同步分为显式同步和隐式同步,同步代码块代表着显式同步,指的是有明确的monitorenter和monitorexit指令。同步方法代表着隐式同步,同步方法是由方法调用指令读取运行时常量池中方法的ACC_SYNCHRONIZED标志来隐式实现的。

什么是对象头

对象在堆内存中存在三块区域,包括对象头,实例变量和对齐填充
实例变量,存放着类的属性的相关信息,包括父类的属性信息,如果是数组对象还包括数组的长度
填充数据,仅仅是为了字节对齐,因为java虚拟机要求对象起始地址必须是8字节的整数倍
对象中的对象头是实现synchronized的基础,synchronized使用的锁对象就是存储在java对象中的对象头中,java虚拟机使用2个字来存储对象头,如果对象是数组,使用三个字存储对象头,多出来的一个字用于存放数组的长度。

对象头中的主要结构是MarkWord 和Class Metadata Address组成
其中:
MarkWord中存储对象的hashCode、分代年龄、GC标志、锁信息
MetadataAddress(元数据地址)
jvm通过MetadataAddress这个指针确定该对象是哪一个类的实例

对象的头信息是与对象自身定义的没有关系的额外存储,MarkWord默认情况下存放着对象的hashcode、分代年龄,锁标记位、锁信息等等,考虑对象头信息占用的内存会影响jvm的空间,Markword被设计称为变化的数据结构,它会根据对象本身的状态复用自己的存储空间,如下看MarkWord可能存在的存储结构为:

在这里插入图片描述
重量级锁就是synchronized锁,锁的标记位为10,其中指针指向monitor对象的起始地址。每一个对象都存在一个monitor与之相关联,monitor对象可以与对象一起创建销毁或者当线程试图获取对象锁的时候自动生成,monitor被线程持有之后就处于锁定的状态java虚拟机中monitor是有ObjectMonitor实现的,其主要的数据结构为:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

monitor对象中有内部有两个队列,一个用来保存一个ObjectWaiter对象列表(每个等待这个锁的线程都会被封装成为ObjectWaiter对象),owner指向的是持有持有monitor对象的线程,当多个线程同时访问一段代码的时候,首先会进入_Entry_list集合,当线程获取到对象的Monitor后进入,会进入 _owner区域并把monitor中的owner设置为当前线程,同时monitor中的计数器count加1,如果线程调用wait()方法,将会释放当前持有的monitor,owner设置为null,count减1,同时该线程进入WaitSet集合中等待被唤醒,如果当前线程执行完毕也将会释放monitor锁,owner变为null,count减1
总结上面的内容可以知道为什么所有的对象都可以作为锁,另外synchronized是怎么获取锁的,表示锁的monitor对象是什么,monitor对象存在每个java对象的对象头中(MarkWord结构中存储了指向monitor对象的指针),也就是为什么notify,notifyall,wait方法都存在Object对象中的原因
在这里插入图片描述

synchronized在字节码层面的实现原理

**

synchronized修饰的代码块在字节码层面给上的实现原理

**
如下类中包含了代码块

public class SyncCodeBlock {

   public int i;

   public void syncTask(){
       //同步代码库
       synchronized (this){
           i++;
       }
   }
}
Classfile /Users/zejian/Downloads/Java8_Action/src/main/java/com/zejian/concurrencys/SyncCodeBlock.class
  Last modified 2017-6-2; size 426 bytes
  MD5 checksum c80bc322c87b312de760942820b4fed5
  Compiled from "SyncCodeBlock.java"
public class com.zejian.concurrencys.SyncCodeBlock
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
  //........省略常量池中数据
  //构造函数
  public com.zejian.concurrencys.SyncCodeBlock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
  //===========主要看看syncTask方法实现================
  public void syncTask();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter  //注意此处,进入同步方法
         4: aload_0
         5: dup
         6: getfield      #2             // Field i:I
         9: iconst_1
        10: iadd
        11: putfield      #2            // Field i:I
        14: aload_1
        15: monitorexit   //注意此处,退出同步方法
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit //注意此处,退出同步方法
        22: aload_2
        23: athrow
        24: return
      Exception table:
      //省略其他字节码.......
}
SourceFile: "SyncCodeBlock.java"

上面已经说过synchronized修饰代码块的方式属于显式同步,查看字节码文件之后会发现,synchronized修饰的同步代码块中,从monitorenter进入同步代码块,从monitorexit退出同步代码块。当执行monitorenter指令的时候,当前线程将会试图获取对象头中指向的monitor对象,当monitor对象结构中的conut为0的时候,线程可以成功的获取到monitor,并将count的值设置为1,表示锁获取成功如果当前线程中已经拥有了monitor,那么可以重新进入这个monitor,此时count再加1。如果monitor已经被其他线程持有了,那么当前线程就会被阻塞,只当正在执行的线程执行完毕,执行完monitorexit指令,这时候,owner变为null,count为0,其他线程将有机会持有monitor对象,编译器将会确保无论每一个执行过monitorentor的方法都有一个monitorexit指令与其相对应,不论这个方法时候正常结束,即使是异常也会执行monitorexit指令。这就是synchtonized修饰的同步代码块中为什么出现两个monitorexit指令的原因

synchronized修饰方法的底层实现的原理

如下代码是使用synchronized修饰的方法:

public class SyncMethod {
\
   public int i;

   public synchronized void syncTask(){
           i++;
   }
}
Classfile /Users/zejian/Downloads/Java8_Action/src/main/java/com/zejian/concurrencys/SyncMethod.class
  Last modified 2017-6-2; size 308 bytes
  MD5 checksum f34075a8c059ea65e4cc2fa610e0cd94
  Compiled from "SyncMethod.java"
public class com.zejian.concurrencys.SyncMethod
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool;

   //省略没必要的字节码
  //==================syncTask方法======================
  public synchronized void syncTask();
    descriptor: ()V
    //方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field i:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field i:I
        10: return
      LineNumberTable:
        line 12: 0
        line 13: 10
}
SourceFile: "SyncMethod.java"

之前有说过同步有隐式同步和显式同步,synchronized修饰的同步代码块是显式同步,而synchronized修饰的方法是隐式同步,是通过方法调用指令读取运行时常量池中的ACC_SYNCHRONIZED标志来区分这个方法是否是同步方法。在方法调用的时候会检查方法的ACC_SYNCHRONIZED访问标志是否被设置了,如果被设置了,执行线程将会现持有monitor当方法调用的时候将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置了,如果被设置了,表明是一个同步方法,这个时候,执行线程将会先持有monitor对象(尝试持有,可能没有持有成功),然后再访问执行方法,最后再方法完成的时候,无论是正常完成还是出现异常都会释放monitor。在方法执行期间其他任何一个线程都不能获得同一个monitor对象。如果在方法执行的时候出现了异常并且方法内部无法处理这个异常的时候,这个同步方法持有的monitor将会在异常抛到方法为的时候自动释放
总结synchronized修饰方法可以看出,jvm通过判断ACC_SYNCHRONIZED访问标志来判别一个方法是否是同步方法,进而获取monitor对象,在早期synchronized是重量锁效率低,因为monitor是依赖底层操作系统来实现的,而操作系统实现线程之间的转换需要从用户太转换到核心态,这个转换要浪费很多时间,后来从jvm层面对synchronized有了很大的优化,为了减少获得锁或者是释放锁的性能消耗,引入了轻量级锁和偏向锁,所以现在的synchronized还可以。

jvm对synchronzied的优化

通常所说的synchronized对象锁指的就是重量级锁,锁有四种状态,无锁偏向锁轻量级锁和重量级锁,在轻量级锁和重量级锁中间还有一个自旋锁的synchronized的优化手段,随着锁的竞争,锁可以从偏向锁升级到轻量级锁在升级到重量级锁,但是锁的升级总是单向的,也就是说这能从低到高升级不会出现锁的降级

偏向锁

偏向锁是加入的新锁,是一种针对加锁操作的优化手段。因为很多情况下,锁不仅存在多线程竞争而且总是由同一线程多次获得,为了减少同一线程获取锁的消耗(CAS操等等)引入了偏向锁。偏向锁的核心是,如果一个线程获得了锁,那么锁就进入了偏向模式,此时MarkWord的结构就变为了偏向锁结构,当这个线程再次请求锁的时候,就无需再做任何同步的操作省去了申请锁的操作,从而提高了程序的性能。对于没有锁竞争的场合,偏向锁有很好的优化效果因为很有可能连续多次是同一个线程申请锁,而对于锁竞争比较严重的地方每次申请所得线程可能都是不相同的不应该使用偏向锁,偏向锁失败后,不会立即膨胀为重量级锁,而是先升级为轻量级锁

轻量级锁

如果偏向锁失败的话,虚拟机不会立即将锁升级为重量级锁,会尝试使用一种称为轻量级锁的优化手段,此时markword就变成了轻量级锁的结构,轻量级锁作为对synchronized锁的优化是依据经验:对于程序中绝大多数的锁再整个同步周期内不会存在竞争。轻量级中的锁适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一把锁的场合因此就,轻量级锁所适应的场景是线程交替执行同步块的场合,如果出现同一时刻多个线程访问同一把锁的情况下,此时就升级为重量级锁。

自旋锁

在轻量级锁失败之后,虚拟机为了避免线程在操作系统层面挂起,会进行一项称为自旋锁的优化手段,自旋锁基于大多数情况下线程持有锁的时间都不会太长,如果直挂起操作系统层面的线程可能得不偿失,因为操作系统实现线程之间的切换时候要从用户态切换到核心态,这个切换要消耗很多时间,时间成本较高,因此,自旋锁假设在不久的将来当前线程可以获得锁,让虚拟机对当前想要获得锁的线程先做几个空循环,这就是自旋的原因,一般不会太久一般情况是50个或者是100个循环,经过若干次循环之后,如果得到锁就顺利的进入临界区,如果还是没有得到锁,将会在操作系统层将线程挂起,这就是自旋锁的优化方式,能够提高效率

锁消除

锁消除是另一种锁优化手段,在java虚拟机即时编译的时候,通过对运行上下文的扫描会除去不可能存在共享资源竞争的锁,消除没有必要的锁,节省没有意义的请求锁时间
比如

package binfabianchen;
public class StringBufferRemoveSync {
	public void add(String str1,String str2) {
		// StringBuffer是线程安全的,使用stringbuffer的变量或者是实例的时候会自动进行加锁操作
		// 但是此时sb此时是一个局部变量不会被其他线程使用,因此stringbuffer不存在共享资源竞争的情景,jvm会自动将锁消除
		StringBuffer sb=new StringBuffer();
		sb.append(str1).append(str2);
	}
		public static void main(String[] args) {
		StringBufferRemoveSync  bufferRemoveSync=new StringBufferRemoveSync();
		for(int i=0;i<100000;i++) {
			bufferRemoveSync.add("abc", "123");
		}
	}
}

synchronized的可重入性

当一个线程试图操作一个由其他线程持有对象锁的共享变量的时候,将会处于阻塞状态,但是当一个线程再次请求自己持有的对象锁的共享变量,这种情况就是重入锁,请求将会成功。java种synchronized是基于原子性的内部锁机制,是可重入的,因此在一个线程调用synchronized方法的同时在其方法体内部调用该对象另外一个synchronized方法,也就是一个线程在得到一个对象锁之后再次请求该对象锁,是允许的,这就是synchrongized的可重入性

package binfabianchen;

public class AccountingSync3 implements Runnable{
	// 创建一个静态实例
	static AccountingSync3 instance=new  AccountingSync3();
	static int i=0;
	static int j=0;// 不是局部变量,是共享变量
	@Override
	public void run() {
		for(int j=0;j<10000;j++) {
			synchronized (this) {
				i++;
				increase();
			}
		}
		
	}
	
	public synchronized void  increase() {
		j++;
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread t1=new Thread(instance);
		Thread t2=new Thread(instance);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(i);
	}
}

在获取了 当前对象的实例锁之后,执行同步代码块中内容,之后又在代码块中调用了当前实例对象的另一个synchronized方法,再次请求当前实例的时候,将被允许,进而执行方法体,这就是可重入锁的直接体现,除此之外,当子类继承父类的时候子类也是也可以重入父类的同步方法,由于sychronized是基于mnitor实现,每次重入后monitor计数器加1;

**

线程中断与synchronized

什么是线程中断

**
中断表示打断正在线程运行中(run方法中间),有三个中断的方法中断线程、是否被中断、是否被中断并清楚中断的状态

// 中断线程
public void Thread.interruppt();// 该方法用在阻塞的的线程时候会重置中断状态,非阻塞不会
//判断线程是否被中断
public boolean Thread.isInterrupted();
//判断是否被中断并清除中断的状态(静态方法)
public static boolean Thread.interrupted();

实例:
中断阻塞的线程时候,使用interrupt()方法将会中断t1线程,并且会抛出异常,抛出的异常要进行捕获,同时中断状态会被复位

package binfabianchen;
import java.util.concurrent.TimeUnit;
public class InterruptSleepThread3 {
	public static void main(String[] args) throws InterruptedException {
		Thread t1=new Thread() {// 新建了一个线程
			public void run() {
				
				while(true) {// 该线程在启动后就一直执行
					try {
						TimeUnit.SECONDS.sleep(2);//阻塞
					} catch (InterruptedException e) {// 线程t1处于阻塞状态,调用中断操作会抛出异常,必须要进行捕获处理
						System.out.println("Interrupted when sleep");
						boolean interrupt=this.isInterrupted();
						System.out.println("interrupt"+interrupt);
						e.printStackTrace();
					}
				}
			}
		};
		
		t1.start();
		TimeUnit.SECONDS.sleep(2);
		t1.interrupt();
	}
}

/**
         * 输出结果:
           Interruted When Sleep
           interrupt:false
         */

创建了一个线程,并启动线程,当前线程执行sleep()方法,线程处于阻塞状态,调用interrupt()方法进行中断阻塞,会抛出中断异常报错,同时中断状态也将会被复位,为什么不用Thread.sleep()而使用TimeUnit.Seconds.SLEEP(2),原因简单,Thread.sleep(2000)没有使用明确的单位说明,后者明确了单位的定义使用的是秒的定义,事实上TimeUnit,Second.sleep(2)方式仍然调用了Thread.sleep(2000),但是为了编写代码的语义更加清洗,推荐使用TmieUnitSeconds.sleep(2)的方式

2.中断运行期间并非阻塞状态的线程,这种情况下调用Thread.interrupt()中断线程,线程不会得到响应,必须要通过if(this.isInterrupted())方法进行手动捕获中断状态并编写中断线程的代码,结束run方法体的代码,例如

package binfabianchen;
import java.util.concurrent.TimeUnit;
public class InterruptThread {
	public static void main(String[] args) throws InterruptedException {
		Thread thread=new Thread() {
			public void run() {
				while(true) {// 此时,线程thread一直在运行
					System.out.println("没有被中断");
				}
			}
		};
		thread.start();
		TimeUnit.SECONDS.sleep(2);
		thread.interrupt();//  在这里调用thread线程的中断,thread不会中断
	}
}
因为是非阻塞状态的线程,虽然调用了interrupt方法,但是线程thread没有被中断,需要我们手动进行中断检测并结束程序
package binfabianchen;

import java.util.concurrent.TimeUnit;

public class InterruptThread2 {
	public static void main(String[] args) throws InterruptedException {
		Thread t1=new Thread() {
			public void run() {
				while(true) {
					if(this.isInterrupted()) {
						System.out.println("线程中断");
						break;
					}	
				}
				System.out.println("已经跳出循环,线程中断");
			}
		};
		t1.start();
		TimeUnit.SECONDS.sleep(2);
		t1.interrupt();
	}
}
   /**
         * 输出结果:
            线程中断
            已跳出循环,线程中断!
         */

在代码中使用了isInterrupted()判断线程是否已经被中断,如果执行了interrupt()中断方法,isinterrupted()将会检测到被中断跳出循环结束线程。isInterrupted能够检测到没有阻塞的线程被中断,但是非阻塞状态的线程调用interrupt()方法的时候不会导致中断状态重置。
简单总结,一种是线程处于阻塞状态或者试图执行一个阻塞操作时候,我们可以使用实例方法interrupt()进行线程中断,执行中断操作之后将会抛出interruptException异常,该异常必须捕捉,无法向外抛出,同时会将中断状态复位,另一种是当线程处于运行状态时候,我们可以调用实例方法interrupt()进行线程中断,但是同时必须要手动通过isInterrupted()方法判断中断状态,并编写程序中断线程(编写中断run方法的线程)
3.同时兼顾中断阻塞状态的线程和中断正在运行状态的线程,可以使用interrupted()方法进行判断,interrupted()方法是静态方法,执行后会检测线程是否被中断,并对中断状态进行复位

public void run(){
    try {
    //判断当前线程是否已中断,注意interrupted方法是静态的,执行后会对中断状态进行复位
    while (!Thread.interrupted()) {
        TimeUnit.SECONDS.sleep(2);
    }
    } catch (InterruptedException e) {

    }
}

中断与synchronized

线程的中断操作对于正在等待获取锁对象的synchronized方法或者代码块是不起作用的,就是说,synchronized修饰的方法或者状态,如果一个线程正在等待锁,,要么获得这把锁继续执行,那么这个线程要么继续等待,即使调用了中断线程的方法也不会生效

等待唤醒机制与synchronzied

notify notifyAll wait 方法这三个方法必须储在synchronized代码块或者是synchronized修饰的方法中,否则会包illegalMonitorStateException异常,因为调用这几个方法前必须要拿到当前对象的monitor对象。与sleep方法不同的是,执行wait方法完成后线程,线程将会等待,同时会释放当前持有的monitor,直到获取到monitor的线程调用notify或者notifyall方法的时候才能继续执行,sleep只是让线程休眠但是不释放monitor,同时notify notifyAll方法调用后,并不会马上释放monitor而是在synchronized修饰的同步方法或者同步代码块执行结束的时候才自动释放monitor。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值