引言

在多线程应用场景中,同步锁是一种非常重要的机制,例如:ID号的分配,多个客户端分别与服务端建立连接,客户端并发请求的情况下,为提升吞吐量,服务端一般采用多线程处理请求,若无同步锁机制,不同线程分配到相同ID号的情况将不可避免,而这种情况与预期相违背。在现实中是有许许多多的例子的,比较典型的有购票,抽奖等。


java 同步锁机制介绍

java同步锁的关键字为:synchronized,其应用主要有两种方式:[synchronized 方法]和[synchronized 块],顾名思义,其区别在于作用范围不同,以下分别对两种方式进行介绍,首先我们看一个例子类:

public class TestForSynchronized
{
	static int ID=0;
	//测试方法01-synchronized块(对象级)
	public String setID_01()
	{
		 synchronized(this)
		 {
			ID++;
			return "setID_01() ID No.:"+ID;
		 }
	}

	//测试方法02-synchronized块(类级别)
	public String setID_02()
	{
		 synchronized(TestForSynchronized.class)
		 {
			ID++;
			return "setID_02() ID No.:"+ID;
		 }
	}

	//测试方法03-synchronized 方法
	public synchronized String setID_03()
	{
		ID++;
		return "setID_03() ID No.:"+ID;
	}

	//普通方法
	public  String commonMethod()
	{
		return "commonMethod ID No."+ID;
	}
}

如上程序所示,分别体现了synchronized块(对象级别和类级别),synchronized方法,普通方法的定义方式。

synchronized 方法

通过在方法的定义中加入synchronized关键字来定义synchronized方法,例如:
public synchronized String setID(),这就是一个返回值为String类型的synchronized方法,对于一个应用了synchronized机制的类来说,以上面定义的类TestForSynchronized 为例,它的每一个实例(对象)都具有单一的锁,当通过这个(实例)对象调用它的任何synchronized方法,或者这个实例(对象)执行synchronized块的时候,这个实例(对象)就会被加锁,即:

 

  • 在多线程场景下,对于某一个类实例(对象)tempObject,如果多个线程并发通过tempObject访问其synchronized方法或者synchronized块时,任何一个时刻只有一个线程处于可执行状态,因为同一时刻只有一个线程能够获取该实例的唯一的锁,其它线程都会被阻塞,直到synchronized方法返回或者synchronized块执行完毕,锁才会被占用的线程释放,此前被阻塞的线程才有机会获得该对象的锁;
  • 在多线程场景下,对于某一个类实例(对象)tempObject,如果一个线程获得tempObject的锁,在一个synchronized方法返回或者一个synchronized块执行完毕后,便会将锁释放,而不会继续持有锁,即使该线程接下来仍需执行该实例tempObject的其它synchronized方法或者synchronized块;

  我们以具体的例子来验证一下上述介绍(类实例为上面定义的TestForSynchronized):
 验证<1>中论点:

public class MainClass
{

	public static void main(String[] args)
	{
		// 创建10个线程来调用【同一个】TestForSynchronized实例(对象)
		TestForSynchronized temp=new TestForSynchronized();
		for(int index=0;index<10;index++)
		{
			MyThread_01 thread=new MyThread_01(temp);
			thread.start();
		}
	}
}

class MyThread_01 extends Thread
{
	TestForSynchronized testObject;

	public  MyThread_01(TestForSynchronized testObject)
	{
		this.testObject=testObject;
	}

	@Override
	public void run()
	{
		try
		{
			Thread.sleep(0);
		} catch (InterruptedException e)
		{

			e.printStackTrace();
		}

		System.out.println(Thread.currentThread().getName()+"--"+testObject.commonMethod());
		System.out.println(Thread.currentThread().getName()+"--"+testObject.setID_01());
	}
}

synchronized 同步锁(Java)-LMLPHP

 验证<2>中论点:
 将上面验证<1>中的commonMethod换成synchronized方法setID_03:

public class MainClass
{

	public static void main(String[] args)
	{
		// 创建10个线程来调用【同一个】TestForSynchronized实例(对象)
		TestForSynchronized temp=new TestForSynchronized();
		for(int index=0;index<10;index++)
		{
			MyThread_01 thread=new MyThread_01(temp);
			thread.start();
		}
	}
}

class MyThread_01 extends Thread
{
	TestForSynchronized testObject;

	public  MyThread_01(TestForSynchronized testObject)
	{
		this.testObject=testObject;
	}

	@Override
	public void run()
	{
		try
		{
			Thread.sleep(0);
		} catch (InterruptedException e)
		{

			e.printStackTrace();
		}

		System.out.println(Thread.currentThread().getName()+"--"+testObject.setID_01());
		System.out.println(Thread.currentThread().getName()+"--"+testObject.setID_03());
	}
}

某次运行的输出结果:

synchronized 同步锁(Java)-LMLPHP


synchronized 块

通过上面的介绍,相信读者已经对synchronized块和synchronized方法有了一定理解,这里我们再补充介绍一下synchronized块。前已述及,synchronized块和synchronized方法,最明显的区别在于【作用域】,synchronized方法的作用域更大一些,某些场景下,作用域太大是一种缺陷,例如:对于一个很复杂,代码量比较大的方法,如果将其定义为synchronized方法,那么,由于同步锁的机制制约,多线程场景下,效率将会显得低下;如果,需要同步锁机制保障的仅仅只是一小段代码的话,完全可以采用synchronized块来解决。
 synchronized块的定义方式:synchronized(syncObject)

synchronized(syncObject){
	//允许控制的代码块
}

synchronized块具有以下特点:

  • synchronized块必须获得了syncObject的锁才能执行这块代码,具体获得锁的机制如前面分析;
  • 当多个线程并发访问某个实例syncObject的synchronized(this)块时,任何一个时刻只有一个线程能够持有该实例的锁,执行synchronized(this)块,其它线程将被阻塞,直到执行完毕释放锁;
  • 多线程场景下,当某个线程访问实例syncObject的synchronized(this)块时,其它线程可以访问实例syncObject的非synchronized(this)块和非synchronized方法;

特殊的synchronized 块(重要!!!)

如我们在例子类TestForSynchronized中定义的方法setID_02():

//测试方法02-synchronized块(类级别)
	public String setID_02()
	{
		 synchronized(TestForSynchronized.class)
		 {
			ID++;
			return "setID_02() ID No.:"+ID;
		 }
	}

在synchronized块中,并不是this(对象级),而是class(类级别),相较于对象级别,类级别具有更严格的同步约束,主要有以下几点:

  • 前已述及,采用了synchronized机制的类,其每一个实例都有一个锁(对象级别的锁),事实上,类也有唯一的锁,换言之,采用了synchronized机制的类有一个类锁;
  • 多线程场景下,当某一线程获得类锁(注意:不再是对象锁),其它线程将被阻塞,将无法调用或访问该类的所有方法和域,包括静态方法和静态变量;
  • 对于含有静态方法和静态变量的代码块的同步,类锁的严格约束在多线程场景下非常实用,应用也较多。

类锁的应用实例

基于在TestForSynchronized类中定义了synchronized块的方法setID_02,运行如下代码:

public class MainClass
{

	public static void main(String[] args)
	{
		//创建10个线程,每个线程各创建一个TestForSynchronized实例(对象)
		for(int index=0;index<10;index++)
		{
			MyThread_02 thread=new MyThread_02();
			thread.start();
		}
	}
}

class MyThread_02 extends Thread
{

	@Override
	public void run()
	{
		//每个线程均创建一个TestForSynchronized实例
		TestForSynchronized temp=new TestForSynchronized();
		System.out.println(Thread.currentThread().getName()+"--"+temp.setID_02());
	}
}

某次运行的输出结果:

synchronized 同步锁(Java)-LMLPHP


互斥锁mutex

上面介绍的“类锁”虽然可以最大限度的避免并发场景下的冲突,但是,其过于严格:多线程场景下,当某一线程获得类锁(注意:不再是对象锁),其它线程将被阻塞,将无法调用或访问该类的所有方法和域,包括静态方法和静态变量。

  事实上,对于一个类,不可能所有属性和方法都涉及并发问题,因此,类锁过于严格的限制会极大的影响性能。鉴于此,本节介绍另外一种锁:互斥锁mutex,其定义方式如下:

public class TestForSynchronized
{
    //定义一个静态对象
    private static Object mutex = new Object();
    //
    //省略其它属性的定义

    public void TestMethod()
    {
        synchronized (mutex)
        {
            //涉及并发问题的代码块
        }
    }
}

    由于mutex为静态类型,对于TestForSynchronized类的所有对象,当需要访问TestMethod的同步块时都必须获得mutex对象的锁,然而,mutex属于类,有且仅有一个锁,从而可以保证任意一个时刻只有一个线程可以访问这个同步块。

 

全文地址请点击:https://blog.csdn.net/jin_kwok/article/details/74898102?utm_source=copy

10-06 20:55