文章目录
概念
在上篇文章介绍Volatile关键字的时候提到,synchronized 可以保障原子性和可见性。因为 synchronized 无论是同步的方法还是同步的代码块,都会先把主内存的数据拷贝到工作内存中,同步代码块结束,会把工作内存中的数据更新到主内存中,这样主内存中的数据一定是最新的。更重要的是禁用了乱序重组以及保证了值对存储器的写入,这样就可以保证可见性。
背景
现在可以多个线程对同一片存储空间进行访问,这时存储空间里面的数据叫做共享数据。线程并发给我们带来效率的同时,也带了一些数据安全性的问题,数据安全性是一个很严重的问题,多个线程同时访问同一片数据区,很有可能把里面的数据弄的混乱。 所以Java语言提供了专门机制以解决这种数据安全性问题,有效避免了同一个数据对象被多个线程同时访问,从而导致数据的错乱的问题。
synchronized关键字用法
- synchronized关键字可以作为函数的修饰符(也就是常说的同步方法)
- synchronized关键字可以作为函数内的语句(也就是常说的同步代码块)
示例
同步方法的写法
public synchronized void test(){}
同步代码块的写法
public void test(){
synchronized(this){
System.out.println("Test");
}
}
代码synchronized(this)中的this的含义会在后面详解。
synchronized关键字的作用域
- 对象实例: 可以防止多个线程同时访问这个对象的synchronized方法,如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程就不能同时访问这个对象中任何一个synchronized方法。这时,不同的对象实例的
synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法。 - 类: 可以防止多个线程同时访问这个类所创建的对象中的synchronized方法。它可以对这个类创建的所有对象实例起作用。
synchronized关键字用法及含义
synchronized 方法
它的作用域默认是当前对象,这时锁就是对象,谁拿到这个锁谁就可以运行它所控制的那段代码。如果这个对象有多个synchronized方法,其它线程就不能同时访问这个对象中任何一个synchronized方法。
示例
public class SynchronizedTest {
/**
* 同步方法1
*/
public synchronized void printA(){
System.out.println("AAAAAAAAAAAAAAAAA");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 同步方法2
*/
public synchronized void printB(){
System.out.println("BBBBBBBBBBBBBBBBB");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 创建一个对象实例
SynchronizedTest synchronizedTest = new SynchronizedTest();
/**
* 线程1,执行该实例的printA方法
*/
new Thread(new Runnable() {
@Override
public void run() {
synchronizedTest.printA();
}
}).start();
/**
* 线程2,执行该实例的printB方法
*/
new Thread(new Runnable() {
@Override
public void run() {
synchronizedTest.printB();
}
}).start();
}
}
这个示例代码很简单,在一个类里面有两个打印字符串的方法,然后在main函数里面启动两个线程去分别调用这个两个方法。
代码执行后会出现两种种打印情况
第一种:
AAAAAAAAAAAAAAAAA
(这里会等待三秒)
BBBBBBBBBBBBBBBBB
(这里会等待三秒,然后进程退出)
第二种:
BBBBBBBBBBBBBBBBB
(这里会等待三秒)
AAAAAAAAAAAAAAAAA
(这里会等待三秒,然后进程退出)
为什么会这样呢?
因为在main函数里面的两个线程都调用了start()方法后,并不是按照谁先调用start()方法,就先执行哪个线程,而是需要等待CPU的调度,那么CPU先调度谁呢?这我也不知道,因为CPU是随机的。
如果CPU先调用了线程1,因为printA()方法是synchronized修饰的,所以线程1在执行printA()方法前,先看看有没有谁把synchronizedTest对象锁住了。目前来看没有锁,那线程1就把该对象锁起来,并执行printA()方法,然后睡眠3秒钟,如果这时候,CPU又调度了线程2,那么线程2去执行的synchronized修改的printB()方法前,看到synchronizedTest对象已经被锁住了,拿不到锁,于是就只能等到synchronizedTest对象锁被释放后才能执行printB()方法了。
再假设,此时等待synchronizedTest对象锁的线程有很多,有线程1、2…10,这么多线程在等待,那么synchronizedTest对象锁被释放后,下一次对象锁会被谁拿到,也是要看CPU的心情了,不知道谁才是那个天选之子呢…
如果CPU先调用了线程2,后面的等待流程是一样的。
这个例子说明了synchronized关键字在对象实例的作用域,防止多个线程同时访问这个对象的synchronized方法,如果一个对象有多个synchronized方法,只要一个线程访问了其中的某一个synchronized方法,其它线程就不能同时访问这个对象中其他任何一个synchronized方法了。
那在对象实例的作用域概念后面还有一句话“这时,不同的对象实例的 synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法。”
现在来验证一下这句话,只需要修改上面代码中main函数的两句话,修改后如下:
public static void main(String[] args) {
/**
* 线程1,执行该实例的printA方法
*/
new Thread(new Runnable() {
@Override
public void run() {
// 创建一个对象实例
SynchronizedTest synchronizedTest1 = new SynchronizedTest();
synchronizedTest1.printA();
}
}).start();
/**
* 线程2,执行该实例的printB方法
*/
new Thread(new Runnable() {
@Override
public void run() {
// 创建一个对象实例
SynchronizedTest synchronizedTest2 = new SynchronizedTest();
synchronizedTest2.printB();
}
}).start();
}
修改的地方是把创建对象实例的地方,放在线程里面去了,此时就有两个不同的对象实例了,现在来看看执行结果呢。
也会有两种打印情况
第一种:
AAAAAAAAAAAAAAAAA
BBBBBBBBBBBBBBBBB
(等待睡眠时间结束,退出进程)
第二种:
BBBBBBBBBBBBBBBBB
AAAAAAAAAAAAAAAAA
(等待睡眠时间结束,退出进程)
两个线程并行发生,这就印证了上面这句话:不同的对象实例的 synchronized方法是不相干扰的,也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法。
思考时间?
如果我们在示例1的基础上,增加一个普通成员方法的打印方法:
/**
* 普通成员方法1
*/
public void printC(){
System.out.println("CCCCCCCCCCCCCCCCC");
}
在main函数里面增加一个线程去执行这个方法:
/**
* 线程3,执行该实例的printC方法
*/
new Thread(new Runnable() {
@Override
public void run() {
synchronizedTest.printC();
}
}).start();
看看现在的程序执行结果会是什么样的呢?可能会有6中不同的打印哦,自己试试吧,想想为什么。
synchronized 代码块
锁对象
synchronized关键字还可以用于方法中的某个代码块中,表示只对这个代码块里的资源实行互斥访问。
示例
public class SynchronizedObjTest {
/**
* 同步方法1
*/
public void printA(){
synchronized (this){
System.out.println("AAAAAAAAAAAAAAAAA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 同步方法2
*/
public void printB(){
synchronized (this){
System.out.println("BBBBBBBBBBBBBBBBB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SynchronizedObjTest synchronizedObjTest = new SynchronizedObjTest();
/**
* 线程1,执行该实例的printA方法
*/
new Thread(new Runnable() {
@Override
public void run() {
synchronizedObjTest.printA();
}
}).start();
/**
* 线程2,执行该实例的printB方法
*/
new Thread(new Runnable() {
@Override
public void run() {
synchronizedObjTest.printB();
}
}).start();
}
}
这段代码和示例1极为相似,不同的地方在于锁的写法,synchronized (this),中的this代表着当前对象,那么它的作用域就是当前对象,这时锁就是对象,谁拿到这个锁谁就可以运行它所控制的那段代码。如果这个对象有多个synchronized方法,其它线程就不能同时访问这个对象中任何一个synchronized方法。
那么synchronized 代码块这种做法对于同一个类的不同的对象实例的 synchronized 代码块会不会相互干扰呢?答案是不会的,就像synchronized方法一样。不同的对象实例是不同的锁,也就不会相互干扰。
锁class
可以防止多个线程同时访问这个类所创建的对象中的synchronized方法。它可以对这个类创建的所有对象实例起作用。
锁class只需要将上面代码中的this,换成“类名.class”就行了
示例
public class SynchronizedClassTest {
/**
* 同步方法1
*/
public void printA(){
synchronized (SynchronizedClassTest.class){
System.out.println("AAAAAAAAAAAAAAAAA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 同步方法2
*/
public void printB(){
synchronized (SynchronizedClassTest.class){
System.out.println("BBBBBBBBBBBBBBBBB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SynchronizedClassTest synchronizedClassTest = new SynchronizedClassTest();
/**
* 线程1,执行该实例的printA方法
*/
new Thread(new Runnable() {
@Override
public void run() {
synchronizedClassTest.printA();
}
}).start();
/**
* 线程2,执行该实例的printB方法
*/
new Thread(new Runnable() {
@Override
public void run() {
synchronizedClassTest.printB();
}
}).start();
}
}
程序会出现两种种打印情况
第一种:
AAAAAAAAAAAAAAAAA
(这里会等待三秒)
BBBBBBBBBBBBBBBBB
(这里会等待三秒,然后进程退出)
第二种:
BBBBBBBBBBBBBBBBB
(这里会等待三秒)
AAAAAAAAAAAAAAAAA
(这里会等待三秒,然后进程退出)
现在将对象实例放在线程中去创建,使其生成两个不同的对象实例
public static void main(String[] args) {
/**
* 线程1,执行该实例的printA方法
*/
new Thread(new Runnable() {
@Override
public void run() {
SynchronizedClassTest synchronizedClassTest1 = new SynchronizedClassTest();
synchronizedClassTest1.printA();
}
}).start();
/**
* 线程2,执行该实例的printB方法
*/
new Thread(new Runnable() {
@Override
public void run() {
SynchronizedClassTest synchronizedClassTest2 = new SynchronizedClassTest();
synchronizedClassTest2.printB();
}
}).start();
}
修改后程序会出现两种种打印情况
第一种:
AAAAAAAAAAAAAAAAA
(这里会等待三秒)
BBBBBBBBBBBBBBBBB
(这里会等待三秒,然后进程退出)
第二种:
BBBBBBBBBBBBBBBBB
(这里会等待三秒)
AAAAAAAAAAAAAAAAA
(这里会等待三秒,然后进程退出)
修改前和修改后的打印情况是一致的。这就印证了这句话:synchronized锁class可以防止多个线程同时访问这个类所创建的对象中的synchronized方法。它可以对这个类创建的所有对象实例起作用。
在Java中还有一条隐式规则
- 当修饰静态方法的时候,锁定的是当前类的Class对象。
- 当修饰非静态方法的时候,锁定的是当前实例对象this。
技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。