当前位置:   article > 正文

synchronized锁的常用方法以及原理_锁类的字节码

锁类的字节码

一 :sync的三种常用方式

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

1:作用于实例方法

package com.test;

/**
 * @author hequan
 * Date:2019/11/26
 * Time:9:43
 */
public class  AccountingSycnBan implements Runnable{

	static  int i =0;

	public synchronized void increase(){
		 i++;
	}

	@Override
	public void run() {
		for (int j = 0; j < 10000; j++) {
			System.out.println(Thread.currentThread().getName());
			increase();
			
		}
	}

	public static void main(String[] args) throws InterruptedException {
		AccountingSycnBan b= new AccountingSycnBan();
		AccountingSycnBan c= new AccountingSycnBan();
		
		Thread t1 = new Thread(b);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();

		t1.join();
		t2.join();

		System.out.println(i);
	}
	 /**
     * 输出结果:并不能保证
     * 2000000
     * 1974600
     * 1989600
     */

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

我们开了2个线程,但是作用于两个实例对象,都调用的increase方法;
这里我们要清楚,变量i是非原子性的,我们多线程同时操作同一个共享变量时候,是不能保证原子性的,还有其实sync是锁的是this对象,也就是AccountingSycnBan的实例,但是在测试方法中,我们是new了两个实例,然后同时操作increase方法,其实这时候是有两把锁,并不能保证原子性;所以结果不能保证是正确的;

这里我们还需要意识到,当一个线程正在访问一个对象的 synchronized 实例方法,那么其他线程不能访问该对象的其他 synchronized 方法,毕竟一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized实例方法,但是其他线程还是可以访问该实例对象的其他非synchronized方法,当然如果是一个线程 A 需要访问实例对象 obj1 的 synchronized 方法 f1(当前对象锁是obj1),另一个线程 B 需要访问实例对象 obj2 的 synchronized 方法 f2(当前对象锁是obj2),这样是允许的,因为两个实例对象锁并不同相同,此时如果两个线程操作数据并非共享的,线程安全是有保障的,遗憾的是如果两个线程操作的是共享数据,那么线程安全就有可能无法保证了。其实这里我们只要new一次实例就行了。

那么如果这里我们就是new了两个实例,有办法保证原子性吗?
答案当然是有的,我们上面讲到了,这里我们锁的是实例对象,java中线程同步锁可以是任何对象,但是我们是否可以锁类字节码对象。这是不是唯一的?

2:作用于静态方法

package com.test;

/**
 * @author hequan
 * Date:2019/11/26
 * Time:9:43
 */
public class  AccountingSycnBan implements Runnable{

	static   int i =0;

	public synchronized static void increase(){
		 i++;
	}

	@Override
	public void run() {
		for (int j = 0; j < 10000; j++) {
			System.out.println(Thread.currentThread().getName());
			increase();

		}
	}

	public static void main(String[] args) throws InterruptedException {
		AccountingSycnBan b= new AccountingSycnBan();
		AccountingSycnBan c= new AccountingSycnBan();

		Thread t1 = new Thread(b);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();

		t1.join();
		t2.join();

		System.out.println(i);
	}
	 /**
     * 输出结果:
     * 2000000
     */

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

接着我们来验证下一个锁对象实例,一个锁类class对象,看下是不是能共存;

package com.test;

/**
 * @author hequan
 * Date:2019/11/26
 * Time:9:43
 */
public class  AccountingSycnBan implements Runnable{

	static   int i =0;

	public synchronized static void increase(){
		 i++;
	}public synchronized  void increase2(){
		i++;
	}

	@Override
	public void run() {
		for (int j = 0; j < 10000; j++) {
			System.out.println(Thread.currentThread().getName());
			increase();
			increase2();

		}
	}

	public static void main(String[] args) throws InterruptedException {
		AccountingSycnBan b= new AccountingSycnBan();
		AccountingSycnBan c= new AccountingSycnBan();

		Thread t1 = new Thread(b);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();

		t1.join();
		t2.join();

		System.out.println(i);
	}
 /**
     * 输出结果:
     * 39998
     * 40000
     * 39999
     */


}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

我们发现这是共存的,但是结果不唯一;
此时两个线程操作数据并非共享的,线程安全是有保障的,遗憾的是如果两个线程操作的是共享数据,那么线程安全就有可能无法保证了,我们结果也验证了这点;

3:作用于代码块

package com.test;

/**
 * @author hequan
 * Date:2019/11/26
 * Time:9:43
 */
public class  AccountingSycnBan implements Runnable{

	static int i =0;

	public synchronized static void increase(){
		 i++;
	}

	@Override
	public void run() {

		synchronized (this) {
			for (int j = 0; j < 10000; j++) {
				System.out.println(Thread.currentThread().getName());
				increase();

			}
		}
	}

	public static void main(String[] args) throws InterruptedException {
		AccountingSycnBan b= new AccountingSycnBan();


		Thread t1 = new Thread(b);
		Thread t2 = new Thread(b);
		t1.start();
		t2.start();

		t1.join();
		t2.join();

		System.out.println(i);
	}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

当然除了锁this对象,也可换成AccountingSycnBan 的实例对象,或类字节码对象;

// instance实例对象
static AccountingSycnBan instance= new AccountingSycnBan();
		synchronized (instance) {
			for (int j = 0; j < 10000; j++) {
				System.out.println(Thread.currentThread().getName());
				increase();

			}
		}


// 类对象
synchronized (AccountingSycnBan.class) {
			for (int j = 0; j < 10000; j++) {
				System.out.println(Thread.currentThread().getName());
				increase();

			}
		}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

二 :sync的原理

内置锁 不需要自己释放锁,jvm会自动释放。 锁比较重。
我们要明白一点,如果要做到原子性的操作,肯定是要对一个变量进行操作处理,来标识线程的操作记录;(monitor)

首先我们的明白对象在jvm中怎么存储的?
64位jvm虚拟机 8 byte 倍数存储。
包含三部分:
对象头
属性变量(实例数据)
填充数据(对齐填充)

可以在下面参考的网页中了解monitorenter和monitorexit的作用,我就不盗用他们的话了,大致意思是,每个对象都有一个monitor监视器,调用monitorenter就是尝试获取这个对象,成功获取到了就将值+1,离开就将值减1。如果是线程重入,在将值+1,说明monitor对象是支持可重入的。

ReenternLock,概念都是类似的,只是锁是自身维护了一个volatile int类型的变量,通过对它加一减一表示占有锁啊重入之类的概念。

注意,如果synchronize在方法上,那就没有上面两个指令,取而代之的是有一个ACC_SYNCHRONIZED修饰,表示方法加锁了。它会在常量池中增加这个一个标识符,获取它的monitor,所以本质上是一样的。

synchronized的可重入性

从互斥锁的设计上来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁,请求将会成功,在java中synchronized是基于原子性的内部锁机制,是可重入的,因此在一个线程调用synchronized方法的同时在其方法体内部调用该对象另一个synchronized方法,也就是说一个线程得到一个对象锁后再次请求该对象锁,是允许的,这就是synchronized的可重入性。

package com.test;

/**
 * @author hequan
 * Date:2019/11/26
 * Time:9:43
 */
public class  AccountingSycnBan implements Runnable{
	static AccountingSycnBan instance= new AccountingSycnBan();
	static int i =0;

	public synchronized static void increase(){
		 i++;
	}

	@Override
	public void run() {

		synchronized (instance) {
			for (int j = 0; j < 10000; j++) {
				System.out.println(Thread.currentThread().getName());
				increase();

			}
		}
	}

	public static void main(String[] args) throws InterruptedException {
		//AccountingSycnBan b= new AccountingSycnBan();

		Thread t1 = new Thread(instance);
		Thread t2 = new Thread(instance);
		t1.start();
		t2.start();

		t1.join();
		t2.join();

		System.out.println(i);
	}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

synchronized的线程中断

在线程运行(run方法)中间打断它。如果当前线程是非阻塞的,那么需要手动打断。

  1. 中断线程(实例方法)

    public void Thread.interrupt()

  2. 判断线程是否被中断(实例方法)
    public boolean Thread.isInterrupted()

  3. 判断是否被中断并清除当前中断状态(静态方法)
    public static boolean Thread.interrupted()

public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread() {
			@Override
			public void run() {
				while (true) {
					try {
						TimeUnit.SECONDS.sleep(2);
					} catch (InterruptedException e) {
						System.out.println("sleep.....");
						boolean interrupted = this.isInterrupted();
						System.out.println("interrupted..."+interrupted);
					}
				}
			}
		};

		t.start();
		TimeUnit.SECONDS.sleep(2);
		//中断处于阻塞状态的线程
		t.interrupt();
		boolean interrupted = t.isInterrupted();
		System.out.println("interrupted2..."+interrupted);

	}
	/**输出结果 直接中断阻塞的线程
	*sleep.....
	*interrupted...false
	*/

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

除了阻塞中断的情景,我们还可能会遇到处于运行期且非阻塞的状态的线程,这种情况下,直接调用Thread.interrupt()中断线程是不会得到任响应的,如下代码,将无法中断非阻塞状态下的线程:

public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread() {
			@Override
			public void run() {
				while (true) {
					System.out.println("未被中断操作");
				}
			}
		};

		t.start();
		TimeUnit.SECONDS.sleep(2);
		//中断处于阻塞状态的线程
		t.interrupt();
	}
	/**输出结果 未被中断
	*未被中断操作
	*未被中断操作
	....
	*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

虽然我们调用了interrupt方法,但线程t并未被中断,因为处于非阻塞状态的线程需要我们手动进行中断检测并结束程序,改进后代码如下:

public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread() {
			@Override
			public void run() {
				while (true) {
					if (this.isInterrupted()) {
						System.out.println("手动中断线程");
						break;
					}
				}
				System.out.println("已跳出循环,线程中断!");
			}
		};

		t.start();
		TimeUnit.SECONDS.sleep(2);
		//中断处于阻塞状态的线程
		t.interrupt();

		/**
		 * 输出结果
		 *手动中断线程
		 * 已跳出循环,线程中断!
		 */

	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

种是当线程处于阻塞状态或者试图执行一个阻塞操作时,我们可以使用实例方法interrupt()进行线程中断,执行中断操作后将会抛出interruptException异常(该异常必须捕捉无法向外抛出)并将中断状态复位,另外一种是当线程处于运行状态时,我们也可调用实例方法interrupt()进行线程中断,但同时必须手动判断中断状态,并编写中断线程的代码(其实就是结束run方法体的代码)。有时我们在编码时可能需要兼顾以上两种情况,优化如下;

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

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

事实上线程的中断操作对于正在等待获取的锁对象的synchronized方法或者代码块并不起作用,也就是对于synchronized来说,如果一个线程在等待锁,那么结果只有两种,要么它获得这把锁继续执行,要么它就保存等待,即使调用中断线程的方法,也不会生效。演示代码如下

package com.test;

import java.util.concurrent.TimeUnit;

public class SynchronizedBlocked implements Runnable{

	public synchronized void f() {
		System.out.println("Trying to call f()");
		while(true) // Never releases lock
			Thread.yield();
	}

	/**
	 * 在构造器中创建新线程并启动获取对象锁
	 */
	public SynchronizedBlocked() {
		//该线程已持有当前实例锁
		new Thread() {
			@Override
			public void run() {
				f(); // Lock acquired by this thread
			}
		}.start();
	}
	@Override
	public void run() {
		//中断判断
		while (true) {
			if (Thread.interrupted()) {
				System.out.println("中断线程!!");
				break;
			} else {
				System.out.println("进入while。。");
				f();
			}
		}
	}


	public static void main(String[] args) throws InterruptedException {
		SynchronizedBlocked sync = new SynchronizedBlocked();
		Thread t = new Thread(sync);
		//启动后调用f()方法,无法获取当前实例锁处于等待状态
		t.start();
		TimeUnit.SECONDS.sleep(1);
		//中断线程,无法生效
		t.interrupt();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

等待唤醒机制与synchronized

所谓等待唤醒机制本篇主要指的是notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,这是因为调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,在前面的分析中,我们知道monitor 存在于对象头的Mark Word 中(存储monitor引用指针),而synchronized关键字可以获取 monitor ,这也就是为什么notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法调用的原因。

synchronized (this) {
       obj.wait();
       obj.notify();
       obj.notifyAll();         
 }
  • 1
  • 2
  • 3
  • 4
  • 5

需要特别理解的一点是,与sleep方法不同的是wait方法调用完成后,线程将被暂停,但wait方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll方法后方能继续执行,而sleep方法只让线程休眠并不释放锁。同时notify/notifyAll方法调用后,并不会马上释放监视器锁,而是在相应的synchronized(){}/synchronized方法执行结束后才自动释放锁。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/292074
推荐阅读
相关标签
  

闽ICP备14008679号