深入java多线程,多线程问题及synchronized的jvm原理

1、多线程安全新问题

什么时候会出现多线程问题以及出现多线程问题的条件有哪些,下面通过一个简单的例子来看下。

假如系统中需要生成累加不重复的数字,用来设置单号或流水号,我们通过一段简单的代码来实现:

public class Thread4 {

	private int num;
	
	private int getNext() {
		return num++;
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread4 t4 = new Thread4();
		while(true) {
			Thread.sleep(500);
			System.out.println(t4.getNext());
		}
	}
}

只用单线程来获取值,输出0开始依次递增数字,单线程下没有任何问题。

然后开启三个线程来操作:

public class Threadt3 {

	private int num;
	
	private int getNext() {
		return num++;
	}
	
	public static void main(String[] args) {
		
		Threadt3 t3 = new Threadt3();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + ":" + t3.getNext());
				}
			}
		}).start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + ":" + t3.getNext());
				}
			}
		}).start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + ":" + t3.getNext());
				}
			}
		}).start();
		
	}
}

看下结果:

可以看出,在多线程场景下出现了明显的问题,多个线程获取到的值是一样的,出现了值重复的现象。

出现多线程安全性问题必要条件:

  1. 是处于多线程环境之下;
  2. 多个线程共享一个资源,就像上述例子中的num变量;
  3. 对资源进行非原子操作,读写操作等;

那怎么解决上述问题呢,只需使用synchronized关键字,使方法变为同步方法:

private int num;
private synchronized int getNext() {
	return num++;
}

这样,保证了方法只能同时被一个线程访问,其余线程处于阻塞等待状态。

 

2、synchronized的使用和原理

在Java中,每一个对象都拥有一个对象锁,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问这个对象。在Java中,可以使用synchronized关键字来修饰一个方法或者代码块,synchronized不能用来修饰变量,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

 

synchronized的使用一,synchronized修饰方法:

private synchronized int getNext() {
	return num++;
}
private static synchronized int getNext() {
	return num++;
}

synchronized修饰方法时放在返回类型void等之前,能修饰非静态的实例方法和静态方法,修饰实例方法时锁住的是实例对象,修改静态方式时锁住的是类字节码Class对象。

 

synchronized的使用二,synchronized修饰代码块,修饰方法体内某一代码块:

private Integer index;
//修饰Object对象
private int getNext() {	
	synchronized (index) {
		return -1;
	}
}
//修饰类的实例
private int getNext() {
	synchronized (this) {
		return -1;
	}
}
//修饰类的class对象
private int getNext() {
	synchronized (Threadt3.class) {
		return -1;
	}
}

synchronized修饰代码块时,synchronized后括号中可接类的实例对象this、class对象、任意对象Object,锁住的也是括号中的对象。

synchronized使用分类
修饰分类修饰对象被锁资源说明
修饰方法实例方法(非静态)类的实例对象public synchronized int getNum(){ }
静态方法类的class对象(字节码)public static synchronized int getName(){ }
修饰代码块任意Object对象Object对象本身Integer n=1; synchronized(n){  }
类的实例对象类实例对象synchronized(this){  }
class对象类class对象(字节码)synchronized(Person.class){  }

 

3、synchronized的jvm实现原理解析

我们用一个简单的同步方法,通过查看字节码方式来验证下。

同步方法:

public class Threadt5 {
	private Integer val = 2;
	public int getNum() {
		synchronized (val) {
			val ++;
			return val;
		}
	}
	public static void main(String[] args) {
		Threadt5 t5 = new Threadt5();
		System.out.println(t5.getNum()); 
	}
}

上述在getNum()方法中有个synchronized同步块,锁住的对象是val对象,我们看下getNum()方法的字节码:

同步块字节码

(javap -verbose查看字节码)

如果把方法getNum()改为非同步:

public int getNum() {
	val ++;
	return val;
}

再查看字节码:

非同步方法字节码

 

对比上述两字节码可以发现,同步方法字节码比非同步的多出monitor对象的操作,分别是monitorenter、monitorexit,可见,jvm实现同步代码块的原理是基于进入和退出monitor对象来实现的,monitorenter、monitorexit是字节码指令,monitorenter代表进入同步代码块,monitorexit表示同步结束。

(在上一节中我们提到了java的每个对象,不管是Object还是类的class,都有对象锁,都具备锁信息,那么,对象的锁信息存在对象的什么地方呢?每个java对象都有对象头,锁的信息就存放在对象头中。)

那么monitor是什么呢?monitor是存在于每个java对象中,java中每个对象(Object/class)都有唯一的一个monitor,且同时只能有一个线程可以获取某个对象的monitor。任意线程对Object的访问,首先要获得Object的监视器,如果获取失败,该线程就进入同步状态,线程状态变为BLOCKED,当Object的监视器占有者释放后,在同步队列中得线程就会有机会重新获取该监视器。

monitor的概念:可以说monitor是一种机制也可以说是一个对象,操作系统为进一步优化并发问题,提出了monitor概念,操作系统本身并不支持 monitor 机制,实际上,monitor 是属于编程语言的范畴,当你想要使用 monitor 时,先了解一下语言本身是否支持 monitor 原语,例如 C 语言它就不支持 monitor,Java 语言支持 monitor,简单的说,是操作系统范畴的,在java字节码中使用monitorenter、monitorexit就能传达至操作系统。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值