`

synchronized关键字的实质及用法

 
阅读更多

 synchronized关键字的实质及用法

   本文旨在揭示用synchronized关键字实现同步的本质,由于纯粹的理论比较枯燥,所以在阐述了基本观点以后,本文的后办部分提供了大量实例并对实例进行了具体的对比分析,通过这些现象及解析,我们可以对文章中的理论知识有更加直观和深刻的理解。希望能给对java多线有疑惑的朋友们一些帮助。这些只是我个人的看法,如果有问题的话请大家及时指出,在此谢过。

  synchronized关键字用于实现多线程并发操作时对某一资源的互斥访问。通俗地讲,就是说当多个线程同时访问被synchronized修饰的方法或者语句块的时候,只能有其中的一个线程获得对被修饰内容的操作权。

synchronized关键字的实质

    在研究的过程中必须谨记的是:synchronized的本质是使用锁的机制来实现线程互斥的。如果有多个资源被同一个锁锁定,那么只要其中一个资源在被一个线程访问,那么这些资源均不可在此期间被其他线程访问。也就是说只有用相同的锁来锁定的资源在同一时刻才只能被一个线程访问,如果用两个不同的锁来锁定两块资源,那么这两块资源可以被两个不同的线程同时访问。只有理解了这句话,对synchronized关键字的使用才能一通百通。多个资源被同一个锁锁定和这些资源在同一时刻只能被一个线程访问是互为充要条件的。


synchronized关键字的基本用法

 

其实,synchronized关键字的基本用法只有一种,就是synchronized(object){……被锁定的代码……}。其中的object就是用已锁定代码的锁,它可以是任何类的实例。如果objectthis,那么就意味着用当前对象来加锁。

synchronized关键字同步整个实例方法体

       如果一个类的某个实例方法的整个方法体都需要同步,我们就可以使用下边的方式来实现:

 

public void simpleMethod(){
		synchronized (this) {
			//省略被加锁的代码
		}
}
 

 

     这里的“this”指代的就是当前对象,它也可以换做任何类的实例。但是为了方便起见,我们通常用this关键字。还有一种更加简便的方式就是,我们可以省略this,而只将synchronized放在方法的返回值前做为修饰就可以了,这可以理解为java为我们实现整个方法的同步提供了一种快捷方式。这种方式如下所示:

 

public synchronized void simpleMethod(){
		//省略被加锁的代码
}
 

 

    以上两种形式是完全等价的。对于一个类来说,多个实例方法都被synchronized修饰就相当于这些所有被修饰的方法的整个方法体都被同一个默认的锁——当前实例this——锁定了,因此他们都是同步的。

synchronized关键字同步代码块

    我们都知道,被synchronized修饰的资源如果正在被一个线程访问,那么其余要访问这个资源的线程必须等待,所以为了让在等待中的线程等待的时间更短,我们就应该尽可能地减少被synchronized修饰的资源。有的时候并不是整个方法都需要同步,而仅仅需要同步这个方法中的一部分,那么我们就可以使用synchronized修饰部分代码片段而非整个方法,如下所示:

 

//用当前对象同步方法中的部分语句
	public void method10() {
		for (int i = 0; i < 100; i++) {
			print("*");
		}
		synchronized (this) {
			for (int i = 0; i < 100; i++) {
				print("method10");
			}
		}
	}

 

      这样只有当线程推进到到被synchronized包围的代码时,线程才会开始等待,这样就减少了等待时间,提高了程序的执行效率。

synchronized关键字同步类方法

对类方法(用static修饰的方法),但是synchronized后的参数不能为this了,只能为static类型的任何类的实例:

 

public class SimpleClass {
	private static Object object=new Object();
	public static void simpleMethod(){
		synchronized (object) {
			//省略被加锁的代码
		}
	}
}

 

 这与下边的方式是等价的:

 

public class SimpleClass {
	public synchronized static void simpleMethod(){
			//省略被加锁的代码
	}
}

 

 只是此时默认的锁对象已经不是this了,而是SimpleClass类本身。因此如果一个类中有多个类方法被synchronized修饰,就相当于他们都被SimpleClass类本身加锁了从某种程度上一样变成了一个整体,所以,它们都是同步的。

几个实验

    为了对上述理论知识有更加深刻的理解,特提供以下实例。实例中涉及到的所有类如下所示:

 

OperateNum 提供了各种同步的非同步的实例或者类方法的简单实现。
Operator 根据不同的参数执行OperateNum中的不同方法。
Test 可执行类,需要按照不同的实验目的提供不同的实现。


 

     前两个类的代码如下所示

   OperateNum.java源码如下:

public class OperateNum{
	//当前实例的名称
	private String name;
	//充当锁对象的Object实例
	private Object lock1 =new Object();
	//另一个充当锁对象的Object实例
	private Object lock2 =new Object();
	
	public OperateNum(String name) {
		this.name = name;
	}
	
	//用synchronized同步的实例方法
	public synchronized void  method1() {
		for (int i = 0; i < 100; i++) {
			print("method1");
		}
	}

	//用synchronized同步的实例方法
	public synchronized void method2() {
		for (int i = 0; i < 100; i++) {
			print("method2");
		}
	}
	
	//未同步的实例方法
	public void method3() {
		for (int i = 0; i < 100; i++) {
			print("method3");
		}
	}
	//未同步的实例方法
	public void method4() {
		for (int i = 0; i < 100; i++) {
			print("method4");
		}
	}
	//用synchronized同步的静态方法
	public synchronized static void method5(){
		for (int i = 0; i < 100; i++) {
			System.out.println("当前的线程是:"+Thread.currentThread()+",当前执行的方法是:method5");
		}
	}
	
	//用synchronized同步的静态方法
	public synchronized static void method6(){
		for (int i = 0; i < 100; i++) {
			System.out.println("当前的线程是:"+Thread.currentThread()+",当前执行的方法是:method6");
		}
	}
	
	//未同步的静态方法
	public static void method7(){
		for (int i = 0; i < 100; i++) {
			System.out.println("当前的线程是:"+Thread.currentThread()+",当前执行的方法是:method7");
		}
	}
	//未同步的静态方法
	public static void method8(){
		for (int i = 0; i < 100; i++) {
			System.out.println("当前的线程是:"+Thread.currentThread()+",当前执行的方法是:method8");
		}
	}
	//用当前对象同步整个方法体
	public void method9() {
		synchronized (this) {
			for (int i = 0; i < 100; i++) {
				print("method9");
			}
		}
	}
	
	//用当前对象同步方法中的部分语句
	public void method10() {
		for (int i = 0; i < 100; i++) {
			print("*");
		}
		synchronized (this) {
			for (int i = 0; i < 100; i++) {
				print("method10");
			}
		}
	}
	
	//用lock1锁定整个方法体
	public void method11() {
		synchronized (lock1) {
			for (int i = 0; i < 100; i++) {
				print("method11");
			}
		}
	}
	//用lock1锁定整个方法体
	public void method12() {
		synchronized (lock1) {
			for (int i = 0; i < 100; i++) {
				print("method12");
			}
		}
	}
	//用lock1锁定整个方法体
	public void method13() {
		synchronized (lock2) {
			for (int i = 0; i < 100; i++) {
				print("method13");
			}
		}
	}
	public void print(String string){
		System.out.println("当前的线程是:"+Thread.currentThread()+",当前实例的名称是:"+this.name+",当前执行的方法是:"+string);
	}
}

      Operator.java的源码如下所示:

public class Operator extends Thread{
	private int operationType;
	private OperateNum operateNum;
	public Operator(int operationType,OperateNum operateNum) {
		this.operationType = operationType;
		this.operateNum=operateNum;
	}
	
	public void run() {
		switch (operationType) {
		case 1:
			operateNum.method1();
			break;
		case 2:
			operateNum.method2();
			break;
		case 3:
			operateNum.method3();
			break;
		case 4:
			operateNum.method4();
			break;
		case 5:
			OperateNum.method5();
			break;
		case 6:
			OperateNum.method6();
			break;
		case 7:
			OperateNum.method7();
			break;
		case 8:
			OperateNum.method8();
			break;
		case 9:
			operateNum.method9();
			break;
		case 10:
			operateNum.method10();
			break;
		case 11:
			operateNum.method11();
			break;
		case 12:
			operateNum.method12();
			break;
		case 13:
			operateNum.method13();
			break;
		default:
			break;
		}
	}
}

 

 下边我们根据不同的实验目的编写Test测试类,并对其运行结果进行分析:

实验一 两个线程同时访问一个没有同步的方法

此时Test.java的源代码如下所示:

public class Test {
	public static void main(String[] args){
		OperateNum operateNum=new OperateNum("NO1");
		new Operator(3, operateNum).start();
		new Operator(3, operateNum).start();
	}
}

 

 此时的运行结果为:


 从上述结果可以看出这两个线程交替执行被访问的方法。

实验二 两个线程访问一个被同步的方法

  这时Test.java的代码如下

public class Test {
	public static void main(String[] args){
		OperateNum operateNum=new OperateNum("NO1");
		new Operator(1, operateNum).start();
		new Operator(1, operateNum).start();
	}
}

  运行的结果如下所示


 运行的结果显示,在Thread-1执行结束后Thread-2才开始执行,也就是说被同步的方法在同一时刻只允许一个线程访问。

实验三 两个线程访问两个未同步的方法

  这时Test.java的源代码如下所示:

 

public class Test {
	public static void main(String[] args){
		OperateNum operateNum=new OperateNum("NO1");
		new Operator(3, operateNum).start();
		new Operator(4, operateNum).start();
	}
}

   运行结果如下所示:



       从上述结果可以看出,两个方法是分别被两个线程交替执行的。

实验四 两个线程访问两个同步的方法

此时Test.java的源代码如下所示:

 

public class Test {
	public static void main(String[] args){
		OperateNum operateNum=new OperateNum("NO1");
		new Operator(1, operateNum).start();
		new Operator(2, operateNum).start();
	}
}

 此时的运行结果如下所示:

 


        此时两个线程没有交叉执行,实验一和实验二对比可以告诉我们,在同一个类中所有用synchronized修饰的实例方法是一个整体,当其中一个被一个线程访问时,其余的被修饰的方法都不可被任何线程访问。

实验五 用this同步整个方法体

此时Test.java的源代码如下所示:

public class Test {
	public static void main(String[] args){
		OperateNum operateNum=new OperateNum("NO1");
		new Operator(1, operateNum).start();
		new Operator(9, operateNum).start();
	}
}

 

 此时的运行结果如下所示:



       此实验结果中并未出现两个方法交替执行的情况,与实验四对比可知OperateNum类中method1method9两个方法是等价的,即用synchronized修饰方法和用this对象锁定正方法体是等价的。

实验六 两个线程分别访问同一个类的两个不同实例的同一个同步的方法

  此时Test.java的源代码如下所示:

 

public class Test {
	public static void main(String[] args){
		OperateNum operateNum1=new OperateNum("NO1");
		OperateNum operateNum2=new OperateNum("NO2");
		new Operator(3, operateNum1).start();
		new Operator(3, operateNum2).start();
	}
}

   此时的运行结果如下所示



       上图中的结果明显出现了交叉执行的现象,参照实验五的结论并将本实验与实验二对比可以发现:用不同的实例对同一个方法加锁,其结果等同与没有加锁。也就是说只有用同一个锁来对不同的资源进行锁定时才可以实现同步。

实验七 两个线程分别访问两个同步的类方法

此时Test.java的源代码如下所示:

 

public class Test {
	public static void main(String[] args){
		OperateNum operateNum1=new OperateNum("NO1");
		OperateNum operateNum2=new OperateNum("NO2");
		new Operator(5, operateNum1).start();
		new Operator(6, operateNum2).start();
	}
}

    此时的运行结果如下所示:



        从图中可以看出两个线程没有出现交叉执行的现象,该实验与实验六对比可以发现,用synchronized修饰类方法时,省略的锁不是this指代的实例,而是OperateNum.class。它在当前虚拟机中是惟一的,因此这两个方法实现了同步。

实验八 两个线程分别访问一个同步的类方法和一个同步的实例方法

    此时Test.java的源代码如下所示:

 

public class Test {
	public static void main(String[] args){
		OperateNum operateNum1=new OperateNum("NO1");
		OperateNum operateNum2=new OperateNum("NO2");
		new Operator(1, operateNum1).start();
		new Operator(5, operateNum2).start();
	}
}

     此时的运行结果如下所示:



     结果中出现了明显的交叉执行方法,由此可以看出用synchronized修饰的类方法和实例方法并不是用的同一个锁,因此两个方法没有实现同步。

实验九 两个线程同时访问一个同步部分代码的方法

此时Test.java的源代码如下所示:

 

public class Test {
	public static void main(String[] args){
		OperateNum operateNum=new OperateNum("NO1");
		new Operator(10, operateNum).start();
		new Operator(10, operateNum).start();
	}
}

 此时的运行结果如下所示:



        上述结果很有意思,我们首先刨除输出*的语句,在剩下的语句中可以看出两个线程并没有出现交叉现象,这是因为输出method的方法是同步的。但是输出*的语句却出现了交叉现象,这是因为输出*的语句并没有实现同步。

基于OperateNumOperator两个类还可以组合出很多个实利,有兴趣的读者可以自行探索研究。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 大小: 15.6 KB
  • 大小: 18.5 KB
  • 大小: 21.5 KB
  • 大小: 16.2 KB
  • 大小: 17.7 KB
  • 大小: 21.8 KB
  • 大小: 24.6 KB
  • 大小: 11.3 KB
  • 大小: 17.6 KB
  • 大小: 56.2 KB
分享到:
评论

相关推荐

    java中synchronized用法

    可见同步方法实质是将 synchronized 作用于 object reference。 Synchronized 关键字也可以用作语句块,它锁定的对象可以是任何对象。例如,在以下代码中: public void method3(SomeObject so){ synchronized(so...

    Java中synchronized关键字引出的多种锁 问题

    Java中synchronized关键字引出的多种锁问题 Java 中的 synchronized 关键字可以在多线程环境下用来作为线程安全的同步锁。synchronized 关键字有三种用法,分别为:用在普通方法上,能够锁住当前对象;用在静态方法...

    透彻理解Java中Synchronized(对象锁)和Static Synchronized(类锁)的区别

    例如,在某个类中,如果某个方法或代码块使用了 Synchronized 关键字,那么在生成该类的实例后,该实例就拥有一个监视块,防止线程并发访问该实例的 synchronized 保护块。 Static Synchronized(类锁) Static ...

    sqlite数据库锁定问题.zip

    对于这样的问题,解决的办法就是keep single sqlite connection,保持单个SqliteOpenHelper实例,同时对所有数据库操作的方法添加synchronized关键字。完美解决sqlite的 database locked 或者是 error 5: database ...

    Android例子源码解决多线程读写sqlite数据库锁定问题

    对于这样的问题,解决的办法就是keep single sqlite connection,保持单个SqliteOpenHelper实例,同时对所有数据库操作的方法添加synchronized关键字。完美解决sqlite的 database locked 或者是 error 5: database ...

    黑马武汉校区Java面试宝典

    synchronized关键字是通过monitorenter和monitorexit指令来实现的,monitorenter指令是指获取对象的监控锁,而monitorexit指令是指释放对象的监控锁。 2.7 synchronized和volatile的区别 synchronized关键字是指...

    并发面试专题.pdf

    综上所述,Synchronized关键字是Java并发编程中的一个重要组成部分,通过对其实现机制的理解以及JVM对其优化策略的掌握,可以更有效地在多线程环境中使用Synchronized来保证数据的一致性和安全性。

    java多线程编程总结.pdf

    因此,需要合理地使用synchronized关键字或java.util.concurrent包中提供的高级并发工具,如ReentrantLock、Semaphore、CountDownLatch等,这些工具可以提供更为灵活和强大的线程同步能力。 此外,Thread类还提供了...

    Java实现线程同步方法及原理详解

    首先,同步代码块是使用synchronized关键字来实现的。它可以将某个代码块锁定,以避免多个线程同时访问该代码块。例如: ```java synchronized (锁对象) { // 这里添加受保护的数据操作 } ``` 在上面的代码中,...

    java多线程实现有序输出ABC

    通过使用synchronized关键字,我们可以在多线程环境下控制线程的执行顺序,实现线程之间的同步和通信。 在上面的代码中,我们使用volatile关键字来声明一个整型变量flag,这个变量用来标志当前线程的执行状态。然后...

    PHP面试题异常全面

    - 如果只需要简单的同步控制,则可以使用synchronized关键字。 - 使用Lock接口时需要注意释放锁的操作,通常建议使用try-finally块或者try-with-resources语句来确保锁被正确释放。 **8. EJB 规范限制** - **...

    javaduoxiancheng.rar_多线程 java

    Java提供了多种同步机制,包括synchronized关键字、Lock接口(如ReentrantLock可重入锁)以及CountDownLatch、CyclicBarrier、Semaphore等并发工具类。synchronized可以保证在同一时刻,只有一个线程能够访问特定的...

    Java线程总结.pdf

    synchronized关键字可以修饰方法或代码块,用于控制方法或代码块在同一时刻只能被一个线程访问,以保护共享资源的完整性。wait()方法使当前线程等待,直到其他线程调用notify()或notifyAll(),当前线程才会被唤醒...

    Java多线程同步具体实例讲解 .doc

    1. 同步方法`sell()`使用了`synchronized`关键字,这意味着在任何时候只能有一个线程调用这个方法。内部的同步块进一步限制了票的销售:如果票数大于0,就减去一张票并打印出购票信息。同步方法的锁是当前对象,即`...

    SCJP.basic1.rar_scjp

    12. **多线程**:学习线程的创建方式,理解同步和互斥的概念,掌握synchronized关键字的用法,以及Thread类和Runnable接口的区别。 13. **输入/输出流**:了解Java的I/O流体系,掌握File类和各种流的使用,包括字节...

    JAVA架构面试_并发面试专题.pdf

    在Java编程语言中,`synchronized`关键字是用于实现线程同步和互斥的关键机制,它确保了在多线程环境下对共享资源的访问控制。面试中,`synchronized`通常是一个必问的话题,因为它在并发编程中的核心地位至关重要。...

    Java源码房门终于被打开了(解决死锁的方法).rar

    在Java中,死锁通常与多线程同步有关,尤其是当使用synchronized关键字和wait()、notify()、notifyAll()方法时,如果没有正确管理,就可能导致死锁的发生。 死锁的四个必要条件是: 1. 互斥条件:至少有一个资源是...

    2021-2022计算机二级等级考试试题及答案No.3855.docx

    原题目中的描述“静态方法不能使用synchronized关键字来修饰”是错误的。实际上,静态方法可以使用synchronized关键字修饰。 ### 16. Access中的数据类型 **知识点描述:** 在Access数据库中“文本”数据类型的...

    JAVA并发编程实践高清版带书签PDF

    2. **同步机制**:讨论了synchronized关键字的使用,包括方法同步和代码块同步,以及其在解决数据竞争问题中的作用。此外,还涉及了死锁、活锁和饥饿现象的预防和处理。 3. **Java内存模型(JMM)**:详细解析了...

Global site tag (gtag.js) - Google Analytics