synchronized补充学习

       对于synchronized语句当Java源代码被javac编译成bytecode的时候,会在同步代码块的入口位置和退出位置分别插入monitorenter和monitorexit(2个)字节码指令。而synchronized方法则会被翻译成普通的方法调用和返回指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用方法的对象或该方法所属的Class在JVM的内部对象表示Class做为锁对象。

  • 执行monitorenter指令时,线程会为锁对象关联一个ObjectMonitor对象(c++)。
  • 线程遇到synchronized同步时,先会进入ObjectMonitor对象的EntryList队列中,然后尝试把ObjectMonitor对象的owner变量设置为当前线程,同时ObjectMonitor对象的monitor中的计数器count加1,即获得对象锁。否则通过尝试自旋一定次数加锁,失败则进入ObjectMonitor对象的cxq队列阻塞等待。
  • synchronized是可重入,非公平锁,因为entryList的线程会先自旋尝试加锁,而不是加入cxq排队等待,不公平。
public class A1 {
	public static final Object obj="12345";
	public static void main(String[] args) {
	}
	//同步实例方法   --- 使用this充当锁   synchronized(this){}
	public synchronized void pp1() {
		System.out.println("pppp");
	}
	//同步静态方法   --- 使用A1.class充当锁  synchronized(A1.class){}
	public synchronized static void pp2() {
		System.out.println("dddddd");
	} 
	//同步代码块
	public void pp3() {
		System.out.println("3333333");
		synchronized (obj) {
			System.out.println("ssssss");
		}
		System.out.println("4444444");
	}
}

同步代码块的同步方法的实现区别

- 同步语句块使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令指向同步代码块的结束位置
- 方法的同步是隐式的,也就是说 synchronized 修饰方法的底层无需使用字节码来控制。

synchronized修饰的方法并没有使用 monitorenter 和 monitorexit 指令,取得代之是ACC_SYNCHRONIZED 标识,该标识指明了此方法一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。这就是 synchronized 锁在同步代码块上和同步方法上的实现差别。

Monitor 对象

        任何对象都关联了一个管程,管程就是控制对象并发访问的一种机制。可以理解 synchronized 就是 Java 中对管程的实现。管程提供了一种排他访问机制,这种机制也就是互斥。互斥保证了在每个时间点上,最多只有一个线程会执行同步方法。所以理解了 Monitor 对象其实就是使用管程控制同步访问的一种对象。
        monitor对象是monitor机制的核心,它本质上是jvm用c语言定义的一个数据类型。对应的数据结构保存了线程同步所需的信息,比如保存了被阻塞的线程的列表,还维护了一个基于mutex的锁,monitor的线程互斥就是通过mutex互斥锁实现的。

synchronized底层语义原理

        Synchronized是通过对象内部的一个叫做监视器锁monitor来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock互斥锁来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁称之为重量级锁。
       Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”:锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级。
       偏向锁是JDK1.6中引用的优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的性能
       轻量级锁也是在JDK1.6中引入的新型锁机制。它不是用来替换重量级锁的,它的本意是在没有多线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。
       Synchronized的重量级锁是通过对象内部的一个叫做监视器锁monitor来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock互斥锁来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。

生产者消费者模式

为什么要使用生产者/消费者模式

       在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。


生产者/消费者模型优点
1、解耦。因为多了一个缓冲区,所以生产者和消费者并不直接相互调用,这一点很容易想到,这样生产者和消费者的代码发生变化,都不会对对方产生影响,这样其实就把生产者和消费者之间的强耦合解开,变为了生产者和缓冲区/消费者和缓冲区之间的弱耦合。
2、通过平衡生产者和消费者的处理能力来提高整体处理数据的速度,这是生产者/消费者模型最重要的一个优点。如果消费者直接从生产者这里拿数据,如果生产者生产的速度很慢,但消费者消费的速度很快,那消费者就得占用cpu的时间片白白等在那边。有了生产者/消费者模型,生产者和消费者就是两个独立的并发体,生产者把生产出来的数据往缓冲区一丢就好了,不必管消费者;消费者也是,从缓冲区去拿数据就好了,也不必管生产者,缓冲区满了就不生产,缓冲区空了就不消费,使生产者/消费者的处理能力达到一个

生产者/消费者模式的作用
 - 支持并发 - 解耦 - 支持忙闲不均
 调用wait/notify之类的方法要求必须在当前线程对象内部,例如synchronized方法

用例说明

//存储多个数据的生产者/消费者模型

public class Basket {
	private Object[] arr;
	
	public Basket() {
		arr=new Object[5];
	}

	public synchronized void produce(Object obj) {
		int pos=-1;
		while (true) {
			pos = indexOfNull();
			if (pos == -1)
				try {
					this.wait();
				} catch (InterruptedException e) {
					e.printstacktrace();
				}
			else break;
		}
		System.out.println(Thread.currentThread()+"生产了一个数据:"+obj);
		arr[pos]=obj;
		this.notifyAll();
	}
	public synchronized void consume() {
		int pos=-1;
		while(true) {
			pos=indexOfNonNull();
			if(pos==-1) {
				try {
					this.wait();
				} catch (Exception e) {
					e.printstacktrace();
				}
			}else
				break;
		}
		System.out.println(Thread.currentThread()+"消费了一个数据:"+arr[pos]);
		arr[pos]=null;
		this.notifyAll();
	}
	
	// 是否有空位置,如果有则返回对应下标,如果没有则返回-1
	private synchronized int indexOfNull() {
		int res = -1;
		for (int i = 0; i < arr.length; i++) {
			Object tmp = arr[i];
			if (tmp == null) {
				res = i;
				break;
			}
		}
		return res;
	}
	// 是否有非空位置,如果有则返回对应下标,如果没有则返回-1
		private synchronized int indexOfNonNull() {
			int res = -1;
			for (int i = 0; i < arr.length; i++) {
				Object tmp = arr[i];
				if (tmp != null) {
					res = i;
					break;
				}
			}
			return res;
		}
}
import java.util.Date;

public class Consumer implements Runnable {
	private Basket resource = null;

	public Consumer(Basket resource) {
		this.resource = resource;
	}

	public void run() {
		for (int i = 0; i < 2000; i++) {
			resource.consume();
		}
	}
}
import java.util.Date;

public class Producer implements Runnable {
	private Basket resource = null;

	public Producer(Basket resource) {
		this.resource = resource;
	}

	public void run() {
		for (int i = 0; i < 20; i++) {
			Object obj = new Date();
			resource.produce(obj);
		}
	}
}
public class Test {
	public static void main(String[] args) {
		Basket resource = new Basket();
		for (int i = 0; i < 10; i++) {
			new Thread(new Producer(resource)).start();
			new Thread(new Consumer(resource)).start();
		}
	}
}


 

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...