Java中synchronized关键字作用及用法 您所在的位置:网站首页 两个竟字起什么作用 Java中synchronized关键字作用及用法

Java中synchronized关键字作用及用法

2024-07-08 05:43| 来源: 网络整理| 查看: 265

文章目录 概念背景synchronized关键字用法synchronized关键字的作用域synchronized关键字用法及含义synchronized 方法思考时间?synchronized 代码块锁对象锁class

概念

在上篇文章介绍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。

技 术 无 他, 唯 有 熟 尔。 知 其 然, 也 知 其 所 以 然。 踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有