Java中synchronized关键字作用及用法

概念

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

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


评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值