阅读 40

Java synchronized同步块和同步函数的区别

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

详细介绍了Java线程同步的概念以及常用方法,重点介绍了synchronized同步块和同步函数。

1 什么是线程同步

线程同步:即当有一个线程在对一块内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,这种线程访问数据的模式就叫做线程同步。

2 同步的前提

  1. 多个线程执行的时候访问共享数据时需要同步,如果是单线程则不需要同步。
  2. 多个线程在执行的过程中是不是使用同一把锁。如果是,就是同步。否则不是同步。

3 Java实现线程同步的方法

线程同步的方法有很多,这里介绍常用的6种方法。

  1. 同步方法
    1. 即由synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
    2. 注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
  2. 同步代码块
    1. 即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
  3. 使用特殊域变量(volatile)实现线程同步
    1. volatile关键字为共享变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值。volatile禁止重排序,但是不会提供任何原子操作,它也不能用来修饰final类型的变量。
    2. 因此volatile可以用在对原性操作的共享变量进行数据同步,但是对于非原子性操作无效。
  4. 使用JUC提供的各种锁实现线程同步
    1. JUC中的锁相比如synchronized更加灵活。
    2. ReentrantLock; 可重入锁.互斥锁
    3. ReentrantReadWriteLock 可重入锁.读写锁
  5. 使用JUC中的阻塞队列实现线程同步
    1. 每次只能有一条线程改变队列的数据,保证了队列的安全,底层使用还是使用的lock锁。
  6. 使用JUC中的原子变量实现线程同步
    1. java.util.concurrent.atomic包下的是类的小工具包,支持在单个变量上解除锁的线程安全编程。底层使用的是cas无锁机制操作变量+volatile修饰变量,保证操作的可见性和原子性并禁止指令重排序。

3.1 同步块

使用同步块解决数据安全问题:

  synchronized(obj){     //obj叫做同步监视器,也叫同步锁
  	//操作的共享数据
  }
复制代码

上面代码的含义为:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时候只能有一条线程可以获得同步监视器的锁定,并且sleep的线程不会释放同步锁。当同步代码块执行完之后,该线程会释放对该同步监视器的锁定。

注意多条线程并须使用同一把锁,否则“锁”便没有作用!

3.2 同步函数

使用同步函数(方法)解决数据安全问题:

 Public (staticsynchronized void xx(){
    //语句一;
   // 语句二;
 }
复制代码

3.3 同步块和同步函数的优缺点

共同的优缺点:

  1. 优点:线程同步,解决了数据的安全问题。
  2. 缺点:当线程较多时,每一条线程每次运行都会去判断获取同步锁,这是很消耗资源的,会降低运行效率。

分别的优缺点:

  1. 同步块优点:
    1. 锁范围自定义。
    2. 锁对象可以是任意对象,除了null,但必须是同一把锁对象。
    3. 如果在线程同步当中需要使用多个锁对象,只能使用同步块。
  2. 同步块缺点:
    1. 增加了代码的层级。
  3. 同步函数优点:
    1. 使用方便,减少了代码的层级。
  4. 同步函数缺点:
    1. 锁对象是默认的:普通方法锁对象是this锁;静态方法的是字节码文件对象Class。
    2. 锁的范围是整个方法,不灵活。

如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

文章分类
后端
文章标签