写在前面
有时候当我们回头看看自己做的几款应用,总会有些感叹,原来一步一步走来,我们竟然也完成了以前觉得做不到或者做不好的事情,甚至会有些沾沾自喜,但是当我们回头深究来路的时候却发现原来我们同样也边捡边丢了好多的东西,我们的大脑总是会不听使唤的忘记很多事情,有时候我们如果不做好充足的准备甚至这种选择权都不能够拥有,这种“TOT”状态让人有种喷嚏打不出来的难受感,到了某个阶段窒碍难行的阻碍总会发生,每天用过的东西总有一天也会忘记,而帮助我们最好记住它们的方式就是笔记,于是才会有这个系列,用于更加深刻的去帮我记住我可能会忘记的东西,同时也把它送给刚好需要的人。
什么是内置锁
在Java的多线程当中总是离不开锁机制,而在Java中锁的机制分为两种,一种是语言特性的内置锁(本质就是给某块内存空间的访问权设限,因为Java中无法直接对某块内存进行操作,而解决这个的办法便是让某个对象承担锁的功能),比如synchronized 关键字,synchronized 方法或者 synchronized 代码块 。还有另外一种是 java.util.concurrent.locks 包中的锁 (这里不做过多讨论)。
用法及作用范围
synchronized在多线程环境下用来作为线程安全的同步锁。主要分为对象锁和类锁,从概念上来讲它们和上文提到的内置锁没什么大的区别,但是它们自己本身区别还是挺大的,对象锁主要作用于对象实例化的方法或者说对象实例上,而类锁是作用于静态方法(static修饰)或者某个类的class对象上。
类的对象实例可有多个,但是每个类只有一个class对象,所以多个对象锁之间互不干扰,但是只能有一个类锁。(其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的)
对象锁
(非静态):修饰实例方法,锁定对象
private synchronized void function() {
//TODO execute something
}
(代码块)该对象可以是当前对象(object == this),也可以是当前类的Class对象(object == MyClass.class)
private void function() {
synchronized (object) {
//TODO execute something
}
}
下面通过网友提供的一个例子来说明对象锁:
定义一个类,方法如下,将 count 自减,从 5 到 0:
public class TestSynchronized {
public synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
测试调用方法如下:
public class Run {
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
test.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
test.minus();
}
});
thread1.start();
thread2.start();
}
两个线程 thread1 和 thread2,同时访问对象的方法,由于该方法是 synchronized 关键字修饰的,那么这两个线程都需要获得该对象锁,一个获得后另一个线程必须等待。所以我们可以猜测运行结果应该是,一个线程执行完毕后,另一个线程才开始执行,运行例子,输出打印结果如下:
Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0
(另:thread1 和 thread2 谁先执行并不一定)
本例对于对象锁进行了基础的解释。但是对象锁的范围是怎样的,对象的某个同步方法被一个线程访问后,其他线程能不能访问该对象的其他同步方法,以及是否可以访问对象的其他非同步方法呢,下面对两种进行验证:
对两个同步方法两个线程的验证:
修改类如下,加入 minus2() 方法,和 minus() 方法一样:
package com.test.run;
public class TestSynchronized {
public synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
public synchronized void minus2() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
测试调用如下,两个线程访问不同的方法:
public class Run {
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
test.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
test.minus2();
}
});
thread1.start();
thread2.start();
}
输出结果如下:
Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0
可以看到,某个线程得到了对象锁之后,该对象的其他同步方法是锁定的,其他线程是无法访问的。
下面看是否能访问非同步方法:
修改类代码如下,将 minus2() 的 synchronized 修饰去掉,代码如下:
public class TestSynchronized {
public synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
public void minus2() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
测试调用的类不变,如下:
public class Run2 {
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
test.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
test.minus2();
}
});
thread1.start();
thread2.start();
}
可以看到,结果是交替的,说明线程是交替执行的,说明如果某个线程得到了对象锁,但是另一个线程还是可以访问没有进行同步的方法或者代码。进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了同步方法,得到了对象锁,其他线程还是可以访问那些没有同步的方法(普通方法)。当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,当某个线程获得对象的锁之后,只能阻止其他线程获得同一个锁。
类锁
private static synchronized void function() {
//TODO execute something
}
举例说明类锁的作用:
public class TestSynchronized {
public static synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
测试调用类如下:
public class Run {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
TestSynchronized.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
TestSynchronized.minus();
}
});
thread1.start();
thread2.start();
}
输出结果如下:
Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0
可以看到,类锁和对象锁其实是一样的,由于静态方法是类所有对象共用的,所以进行同步后,该静态方法的锁也是所有对象唯一的。每次只能有一个线程来访问对象的该非静态同步方法。
类锁的作用和对象锁类似,但是作用范围是否和对象锁一致呢,下面看对象锁和类锁是否等同:
修改类,两个同步方法,其中一个是静态的:
public class TestSynchronized {
public static synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
public synchronized void minus2() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
测试调用类如下,静态方法直接用类调用,实例方法由对象来调用:
public class Run {
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
TestSynchronized.minus();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
test.minus2();
}
});
thread1.start();
thread2.start();
}
运行结果:
Thread-1 - 4
Thread-0 - 4
Thread-0 - 3
Thread-1 - 3
Thread-0 - 2
Thread-1 - 2
Thread-0 - 1
Thread-1 - 1
Thread-1 - 0
Thread-0 - 0
以上证明类锁和对象锁相互独立。
(避免重复造轮子,秉着方便使用,便于记忆,文中用到的代码段摘自:https://blog.csdn.net/yx0628/article/details/79086511, 谢谢。)