【Java并发编程】Java的Synchronized的三种用法

Java的Synchronized的三种用法以及一些问题


我们这里从Synchronized在用法的分类上分别介绍三种使用方式。

  • 前提概念
    • 从锁类型分类
    • 从使用方式分类
  • Synchronized修饰的方法
  • Synchronized修饰的静态方法
  • Synchronized修饰的代码块
  • 关于Synchronized的一些问题
    • 对三种用法的一个小结
    • 使用Synchronized锁一个常量
    • Synchronized只能锁对象吗?可以锁基本变量吗?

前提概念


从锁类型分类:

  • 类级锁
    锁的是当前这个类的所有对象
  • 对象锁
    锁的是当前实例对象

从用法中分类:

  • Synchronized修饰的方法
    锁是当前实例对象
  • Synchronized修饰的静态方法
    锁是当前类的Class对象
  • Synchronized修饰的代码块
    锁是Synchronized括号里配置的对象


Synchronized修饰的方法


public synchronized void show (){
	//TODO
}

这代表着,调用该方法需要获取当前实例对象的对象锁,也就是同一个对象被synchronized修饰的方法的调用会进入同步队列。

注意:

  • synchronized需要放在在返回值前,如上在void前。可以在public之前也可以之后
  • 在定义接口方法时不能使用synchronized关键字。
  • 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
  • synchronized 关键字不能被继承 。如果子类覆盖了父类的被synchronized修饰的方法,子类是不会继承父类的synchronized关键字的

Synchronized修饰的静态方法


public  class SynClass {
	 public static synchronized void show (){}
}

我们看到该方法是一个静态方法~所以这代表着synchronized修饰的是一个类的一部分。
所以要调用这个静态方法,必须要获得SynClass类的类级锁,也就是SynClass的Class类型对象的对象锁。再通俗点讲就是锁的是SynClass这个类的所有对象

注意:

  • synchronized需要放在在返回值前,如上在void前。可以在public 和 static之间或中间,与他两没有关系
  • 在定义接口方法时不能使用synchronized关键字。
  • 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
  • synchronized 关键字不能被继承 。如果子类覆盖了父类的被synchronized修饰的方法,子类是不会继承父类的synchronized关键字的

Synchronized修饰的代码块


使用synchronized修饰的代码块的好处就是可以将锁的范围缩小,让锁更加的灵活,减小开销。

同步代码块大致有三种使用途径,可以实现类级锁也可以实现对象锁:

第一种,实现类级锁

public  class SynClass {

	public static void main(String[] args) {
		synchronized(SynClass.class){
			//TODO
		}
	}

}

效果可以说等同于静态方法锁,但是使用代码块的方式可以实现别的类的类级锁,既括号内是别的类的类类型

第二种,实现当前实例对象的对象锁

public  class SynClass {
	public void show(){
		synchronized(this){
			//TODO
		}
	}
}

效果等同于方法锁,锁的是当前实例对象,但要记住this是不能再静态方法内部使用的。

第三种,实现某个对象的对象锁

public  class SynClass {

	Object lock = new Object();
	
	public void show(){
		synchronized(lock){
			//TODO
		}
	}
}

这里定义了一个Object对象lock,转门用来当做锁。这样的好处是缩小了锁的范围,是针对当前类的实例对象中的某个成员做为锁,这样可以减少锁同步的开销,让锁更加的灵活。


关于Synchronized的一些问题


对三种用法的一个小结
  • 同步方法锁的是当前实例对象,是类级锁
  • 同步静态方法锁的是当前类的Class对象,是类级锁
  • 同步代码块可以实现类级锁也可以实现对象锁,是一种灵活的使用方式。

实践角度理解什么是对象锁
public class ObjectSyn {

	public static void main(String[] args) {
		//一个实例对象
		ObjectSyn obj = new ObjectSyn();
		
		Thread t1 = new Thread(new Runnable() {

			@Override
			public void run() {
				obj.method1(); //执行obj对象的method1方法
			}
		},"t1");


		Thread t2 = new Thread(new Runnable() {

			@Override
			public void run() {
				obj.method2(); //执行obj对象的method2方法
			}
		},"t2");


		t1.start();
		t2.start();
	}

	//方法一
	public synchronized void method1(){
		AtomicInteger i = new AtomicInteger(0);
		while(true){
			i.incrementAndGet();
			System.out.println(Thread.currentThread().getName() + " Integer is " + i.get());
			if(i.get() == 100)
				break;
		}

	}

	//方法二
	public synchronized void method2(){
		AtomicInteger i = new AtomicInteger(0);
		while(true){
			i.incrementAndGet();
			System.out.println(Thread.currentThread().getName() + " Integer is " + i.get());
			if(i.get() == 100)
				break;
		}
	}

}

结果:

t2 Integer is 95
t2 Integer is 96
t2 Integer is 97
t2 Integer is 98
t2 Integer is 99
t2 Integer is 100
t1 Integer is 1
t1 Integer is 2
t1 Integer is 3
t1 Integer is 4
t1 Integer is 5

t2线程执行完毕后才轮到t1线程执行,因为这里仅仅实例化了一个对象obj,虽然执行的是obj对象的不同方法,但是t1和t2线程需要获得的都是obj这把锁,所以是同步执行的。如果我们定义另一个obj2对象,在线程t2中修改为obj2.method2.则整个结果则是异步的。


使用Synchronized锁一个常量
public class SynString {

	public static void main(String[] args) {
		Thread t1= new Thread(new Runnable() {//线程t1

			@Override
			public void run() {
				SynString.show("a");  //对字符串对象赋值一个字符串常量 等同String str = "a"

			}
		},"t1");

		Thread t2= new Thread(new Runnable() {//线程t2

			@Override
			public void run() {
				SynString.show("a");

			}
		},"t2");

		t1.start();
		t2.start();

	}


	public static void show(String str){  //测试的同步方法
		synchronized(str){ //锁的是一个字符串对象
			AtomicInteger i = new AtomicInteger(0);
			while(true){
				i.incrementAndGet();
				System.out.println(Thread.currentThread().getName() +" "+ i.get());
				if(i.get() == 100)
					break;
			}
		}
	}
}

测试结果:

...
t2 96
t2 97
t2 98
t2 99
t2 100
t1 1
t1 2
t1 3
t1 4
...

线程t2执行完才到线程t1执行
这是为什么呢?这是因为"a"是一个字符串常量,是存储在字符串常量池中的。虽然赋予给不同的变量,但是这个对象任然是常量池中的同一个对象。所以也就是同一个对象锁。t2线程在获取锁的时候自然t1线程需要等待t2线程释放锁才能获得该锁

如果我们要想要获得不同对象锁,那么我们要怎么修改呢?

Thread t1= new Thread(new Runnable() {
	@Override
	public void run() {
		SynString.show("a");
	}
},"t1");

Thread t2= new Thread(new Runnable() {
	@Override
	public void run() {
		SynString.show(new String("a")); //将"a"修改为new String("a")
	}
},"t2");

测试结果:

...
t2 73
t1 87
t2 74
t1 88
t2 75
t1 89
t2 76
t1 90
t2 77
t1 91
...

我们只需要重写new一个字符串对象既可,因为"a"是存储在字符串常量池中,但是new String(“a”),则是根据常量池的"a"对象在堆中的一个新的拷贝,内存位置是不一样的。所以自然获取的对象锁是不一样的,也不会造成同步。注意如果是new String(“a”).intern()则依然获得的是同一个对象锁

同理,还有Integer等其他设计有常量池技术的类型都存在这样的问题。所以一般情况下,Synchronized不修饰常量对象,避免出错


Synchronized只能锁对象吗?可以锁基本变量吗?
public class SynObject {

	public void show(int i){
		synchronized(i){ //error,int is not a valid type's argument for the synchronized statement

		}
	}
}

很明显…这是不可以的~那么为什么呢?我们这里引用一个网站回答的答案:

remoteJavaSky 写道:
如果基本类型可以被锁,那么它就需要有一个锁对象与之关联(在方法执行的时候会获取或释放),还需要一个wait集合与之关联(wait,notify,notifyall使用) 那这个基本类型还算是基本类型了吗?并且已经有了基本类型的包装了,这个不是问题了吧我想

我觉得是非常有道理的~同时在《Java并发编程艺术》和《深入理解Java虚拟机》原理两书中有提到一个概念,叫对象头,而对象头的Mark Word区域存放了锁的信息。也就是说这个对象头的Mark Word区域存放了对象锁的信息,比如谁获得了这个锁,目前这个锁是什么锁等等而基本类型必然是没有这个对象头,不然那就叫对象了。所以理由不言而喻了~

参考资料


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值