方法锁(对象锁)
修饰在实例方法上,多个线程调用同一个对象的同步方法会阻塞(不管同步方法是不是同一个,只要对象是同一个就行),调用不同对象的同步方法不会阻塞。
简单来说,锁住的仅仅是一个类中的一个方法,该方法在同一时刻只能被一个线程调用。相对于方法锁,对象锁这个名字更为合适,因为只要有线程在调用该类的同步方法,其他线程调用本类的任何同步方法都会无效。
示例:
具有方法锁的测试类
public class Sync {
public synchronized void test(String name) {
System.out.println(name+"test开始.."+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"test结束.."+System.currentTimeMillis());
}
public synchronized void test2(String name) {
System.out.println(name+"test2开始.."+System.currentTimeMillis());
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"test2结束.."+System.currentTimeMillis());
}
}
两个线程类,分别调用Asyc的两个不同的同步方法
public class MyThread1 extends Thread{
private Sync sync;
public MyThread1(Sync sync) {
this.sync = sync;
this.setName("线程1");
}
@Override
public void run() {
sync.test(currentThread().getName());
}
}
public class MyThread2 extends Thread{
private Sync sync;
public MyThread2(Sync sync) {
this.sync = sync;
this.setName("线程2");
}
@Override
public void run() {
sync.test2(currentThread().getName());
}
}
测试
public class Test {
public static void main(String[] args) {
Sync sync = new Sync();
MyThread1 thread = new MyThread1(sync);
MyThread2 thread2 = new MyThread2(sync);
thread.start();
thread2.start();
}
}
运行结果:
线程2test2开始..1578217624004
线程2test2结束..1578217624804
线程1test开始..1578217624804
线程1test结束..1578217625805
ps:调用非同步方法
正如一开始所说,我们的方法锁会让整个类的所有同步方法都锁定,那么在A线程调用同步方法的时候,B线程调用该对象的非同步方法会不会阻塞?
测试一下,我们去掉test1的synchronized关键字
public class Sync {
public void test(String name) {
System.out.println(name+"test开始.."+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"test结束.."+System.currentTimeMillis());
}
public synchronized void test2(String name) {
System.out.println(name+"test2开始.."+System.currentTimeMillis());
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"test2结束.."+System.currentTimeMillis());
}
}
执行结果
线程2test2开始..1578222537075
线程1test开始..1578222537075
线程2test2结束..1578222537875
线程1test结束..1578222538075
答案是对线程A调用同步方法对线程B调用同一对象的非同步方法没有影响。
类锁
修饰在静态方法上,多个线程调用同一个类的同步方法会阻塞(不管同步方法是不是同一个,也不管是不是同一个对象,只要是同一个类就会阻塞)。
简单来说,类锁相对于方法锁,更加严格,即使是不同对象,只要有线程调用同一个类中任意一个对象的任意一个同步方法,都会导致其他线程调用该类的实例的同步方法阻塞。
实例:
具有类锁的测试类
public class Sync {
public static synchronized void test(String name) {
System.out.println(name+"test开始.."+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"test结束.."+System.currentTimeMillis());
}
}
两个工具线程
public class MyThread1 extends Thread{
public MyThread1() {
this.setName("线程1");
}
@Override
public void run() {
Sync.test(currentThread().getName());
}
}
public class MyThread2 extends Thread{
public MyThread2() {
this.setName("线程2");
}
@Override
public void run() {
Sync.test(currentThread().getName());
}
}
测试
public class Test {
public static void main(String[] args) {
MyThread1 thread = new MyThread1();
MyThread2 thread2 = new MyThread2();
thread.start();
thread2.start();
}
}
运行结果
线程2test开始..1578226459350
线程2test结束..1578226460352
线程1test开始..1578226460352
线程1test结束..1578226461352
由于是静态方法,所以在两个线程中就不需要用实例调用了,最终结果说明类锁阻塞了试图调用被加锁的类中的同步方法。
ps再次验证非同步方法
1.我们对Asyc类增加一个非同步方法再次测试,让线程2去调非同步方法。
更改如下
public class Sync {
public static synchronized void test(String name) {
System.out.println(name+"test开始.."+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"test结束.."+System.currentTimeMillis());
}
public void test2(String name) {
System.out.println(name+"test2开始.."+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"test2结束.."+System.currentTimeMillis());
}
}
测试类
public class Test {
public static void main(String[] args) {
Sync sync2= new Sync();
MyThread1 thread = new MyThread1();
MyThread2 thread2 = new MyThread2(sync2);
thread.start();
thread2.start();
}
}
运行结果:
线程2test2开始..1578227227114
线程1test开始..1578227227114
线程2test2结束..1578227228115
线程1test结束..1578227228115
结果表明,类锁同样对非同步方法无作用。
2.如果对Asyc类增加一个方法锁呢。
更改如下:
public class Sync {
public static synchronized void test(String name) {
System.out.println(name+"test开始.."+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"test结束.."+System.currentTimeMillis());
}
public synchronized void test2(String name) {
System.out.println(name+"test2开始.."+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"test2结束.."+System.currentTimeMillis());
}
}
测试类不做更改,运行结果:
线程2test2开始..1578227470062
线程1test开始..1578227470062
线程2test2结束..1578227471062
线程1test结束..1578227471062
结果表明,类锁对方法锁无效,类锁锁住的只有静态同步方法。经过测试,同理,方法锁对类锁也无效,方法锁锁住的只有实例方法。
synchronized代码块
使用synchronized声明的方法在某些情况下是有弊端的,比如A线程调用同步的方法执行一个长时间的任务,那么B线程就必须等待比较长的时间才能执行,这种情况可以使用synchronized代码块去优化代码执行时间,只有在必须锁住对象的时候再上锁,也就是通常所说的减少锁的粒度。
ps:synchronized(Object)相当于一个对象锁/方法锁。
示例:
下面用实例说明,在方法内部有两次sleep,代表两次长时间任务,只有在第二次任务时在用同步代码块上锁。
public class Asyc {
public void doLongTimeTask(){
try {
System.out.println("当前线程开始:" + Thread.currentThread().getName() +
", 正在执行一个较长时间的业务操作,其内容不需要同步");
Thread.sleep(2000);
synchronized(this){
System.out.println("当前线程:" + Thread.currentThread().getName() +
", 执行同步代码块,对其同步变量进行操作");
Thread.sleep(1000);
}
System.out.println("当前线程结束:" + Thread.currentThread().getName() +
", 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final Asyc asyc = new Asyc();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
asyc.doLongTimeTask();
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
asyc.doLongTimeTask();
}
},"t2");
t1.start();
t2.start();
}
}
执行结果如下:
当前线程开始:t2, 正在执行一个较长时间的业务操作,其内容不需要同步
当前线程开始:t1, 正在执行一个较长时间的业务操作,其内容不需要同步
当前线程:t2, 执行同步代码块,对其同步变量进行操作
当前线程结束:t2, 执行完毕
当前线程:t1, 执行同步代码块,对其同步变量进行操作
当前线程结束:t1, 执行完毕
效果显而易见,不加说明了,synchronized(this)也就是表明锁住当前调用这个方法的实例,跟方法锁一样。
ps:本例如果不用同步代码块也可用方法锁做优化,只需要把不需要执行同步的部分抽出来当成一个独立的方法即可。不写了。
网友评论