java - 线程- synchronized
锁定的对象: 对象(Object) 和 类(Class)
对于jvm而言,实际锁定的是Object还是Class 是没有区别的,原因在于Class实际也是一个Object,唯一的不同点在于, Object对象是存在于Heap中,而Class是存在于MetaData(java8+)中;
对于这两种竞态条件而言,对于存在于MetaData中的class,每个jvm中最多只会存在一个,而对于堆中的Object,是可以人为操作的;对于堆中的静态条件也可以人为实现类似于MetaData中数据的竞态效果
// 对于锁定 对象和类 的区别在于,其锁定的范围不同,对于锁定对象实际临界条件为当前对象,而当锁定类时,实际锁定的当前类 // 对于 .class 实际也是一个Object 对象,因此实际并没有太大的区别 public static void main(String[] args) { // 对象锁 synchronized (new Object()) { // 对象存在于堆中 } // 类锁 synchronized (SynchronizedObjectAndClassDemo.class) { // 类存在于 metadata(java8+); //类存在于 perm(java7-) } Object o = SynchronizedObjectAndClassDemo.class; }
修饰范围:方法(method) 和 代码块(block)
- 当修饰静态方法时,实际默认的竞态条件为当前类(.class)
- 当修饰非静态方法时,实际默认的竞态条件为调用当前方法的对象实例(Object)
// 锁定的静态条件是相同的 // public synchronized static void echo(String message){ public static void echo(String message) { synchronized (SynchronizedObjectAndClassDemo.class) { } System.out.println(message); } // 锁定的对象是相同的 // public synchronized void echoNoStatic(String message) { // 方法 public void echoNoStatic(String message) { // 代码块 synchronized (this) { // 对于当前的竞态条件为当前调用实例 } }
重进入(reentrant)
重进入的限制条件为相同的线程以及相同的竞态条件,对于同一个线程对于同一个竞态条件已经在最外围获取到了所有权,则在内部再次遇到相同的竞态条件时,由于外部已经获取到了竞态条件所有权,因此在内部并不需要再次竞争,对于这种情况而言就属于重进入;在重进入操作时会对当前对象的monitor 进行累加操作(+1)来记录当前重进入次数
public final static Object MONITOR_A = new Object(); public final static Object MONITOR_B = new Object(); public static void lockA(){ synchronized (MONITOR_A){ System.out.println("lockA"); lockB(); } } public static void lockB(){ synchronized (MONITOR_B){ System.out.println("lockB"); } } public static void main(String[] args) { synchronized (MONITOR_B){ // 对于lockB而言的 MONITOR_B 实际就属于重进入,原因在于,在最外层以及得到了MONITOR_B 竞态条件的所有权,当在内部再次 使用 synchronized (MONITOR_B),由于线程在外部已经得到了竞态条件所有权,因此在内部再次使用时并不需要对竞态条件再次进行判断,直接进入;因此这种操作就属于重进入 lockA(); } }
Thread.sleep和 sync
Object l1 = new Object(); Object l2 = new Object(); new Thread(() -> { synchronized (l1) { log.info("当前线程:{}持l1共享变量", Thread.currentThread().getId()); try { log.info("当前线程:{}准备休眠释放l1共享变量", Thread.currentThread().getId()); /** * 在其注解中可以看到 * The thread does not lose ownership of any monitors. * 对于sync 实际是利用的monitor ,因此当当前线程休眠时,并不会释放l1的所有权,因此另外一个线程还是被阻塞 */ Thread.sleep(10000L); } catch (InterruptedException e) { e.printStackTrace(); } log.info("当前线程:{}休眠结束重新持有l1共享变量", Thread.currentThread().getId()); synchronized (l2){ log.info("当前线程:{}持l2共享变量", Thread.currentThread().getId()); } log.info("当前线程:{}释放l2共享变量", Thread.currentThread().getId()); } log.info("当前线程:{}释放l1共享变量", Thread.currentThread().getId()); }).start(); new Thread(() -> { synchronized (l2) { log.info("当前线程:{}持l2共享变量", Thread.currentThread().getId()); synchronized (l1){ log.info("当前线程:{}持l1共享变量", Thread.currentThread().getId()); } log.info("当前线程:{}释放l1共享变量", Thread.currentThread().getId()); } log.info("当前线程:{}释放l2共享变量", Thread.currentThread().getId()); }).start(); /** * 对于第一个线程首先持有了 l1 并未释放,去竞争l2发现第二个线程正在持有l2,此时当前线程就需要等待另外的线程释放l2 * 第二个线程首先持有了 l2 并未释放,去竞争l1发现第一个线程正在持有l1,需要等待另外的线程释放l1 * 在这种情况下,由于双方互相等待对方释放竞争资源,导致双方都获取不到资源 */ }
java.lang.Thread#sleep(long)
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
*
* @param millis
* the length of time to sleep in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;

浙公网安备 33010602011771号