引言
在多线程应用场景中,同步锁是一种非常重要的机制,例如: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());
}
}
验证<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 块
通过上面的介绍,相信读者已经对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());
}
}
某次运行的输出结果:
互斥锁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