### 一、是什么
synchronized是Java的关键字,可用于同步实例方法、类方法(静态方法)、代码块。
sychronized是非公平线程安全的,具有可见性、有序性,有原子性。
### 二、实现原理
synchronized是依赖于 JVM来实现同步的。
synchronized同步代码块是通过加monitorenter和monitorexit指令实现的。
每个对象都有个监视器锁(monitor) ,当monitor被占用的时候就代表对象处于锁定状态,而monitorenter指令的作用就是获取monitor的所有权,monitorexit的作用是释放monitor的所有权。
可见性原理:
~~~
- 内存模型和happens-before规则。
- 这跟监视器(monitor)有关,即对同一个监视器的解锁,先于(happens-before)对该监视器的加锁
~~~
JMM关于synchronized的两条语义规定了:
- 线程加锁前:需要将工作内存清空,从而保证了工作区的变量副本都是从主存中获取的最新值。
- 线程解锁前;需要将工作内存的变量副本写回到主存中。
有序性跟原子性:
~~~
synchronized是给共享变量加锁,使用阻塞的同步机制
~~~
### 三、使用场景
同步实例方法、类方法(静态方法)、代码块。
### 四、优劣
- 效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时
- 不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活
- 无法知道是否成功获得锁,相对而言,Lock可以拿到状态
1. 提高程序的安全性:当多个线程同时访问共享资源时,synchronized 可以保证同一时间只有一个线程可以访问该资源,避免了多个线程同时修改共享资源而产生的数据不一致问题。
2. 提高程序的性能:由于 synchronized 代码块只有一个线程执行,因此可以提高程序的执行效率,减少线程之间的竞争,避免了不必要的数据拷贝和等待。
3. 简化代码:使用 synchronized 可以使代码更加简洁,减少了重复代码的出现,使代码更加易于维护和理解。
### 五、刷题
#### 1、 synchronized是非公平锁
~~~
synchronized底层使用mutext锁实现的,而mutext锁并不保证公平性,Synchronized 获取锁的行为是不公平的,并非是按照申请对象锁的先后时间分配锁的,每次对象锁被释放时,每个线程都有机会获得对象锁,这样有利于提高执行性能,但是也会造成线程饥饿现象。
~~~
#### 2、 Synchronized本质上是通过什么保证线程安全的?
~~~
synchronized关键字用于保证线程安全,其本质上是通过加锁和释放锁、可重入原理以及保证可见性原理来实现的。
1、加锁和释放锁的原理:
synchronized用于修饰方法或代码块,使得同一时间只有一个线程可以进入到临界区,并且在临界区执行代码块期间,其他线程必须等待该线程执行完毕后才能进入临界区。
加锁是在需要同步的代码块前加上锁,如果线程尝试进入临界区,则会阻塞等待该锁的持有者释放锁;
释放锁则是在不需要同步的代码块后释放锁,如果有其他线程进入了临界区,则会等待该线程执行完毕后才能继续执行。
2、可重入原理:
synchronized还可以保证同一个代码块不会被多个线程同时执行,从而避免了数据不一致的问题。
3、保证可见性原理:
synchronized还可以保证共享变量的可见性。如果多个线程同时访问共享变量,并且其中一个线程修改了该变量的值,那么其他线程需要等待修改该变量的线程执行完毕后才能访问该变量。
为了保证共享变量的可见性,synchronized会在访问共享变量的代码块前后加上锁,确保同一时间只有一个线程可以访问该变量。
~~~
#### 3、 Synchronized修饰的方法在抛出异常时,会释放锁吗?
~~~
synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
~~~
#### 4、 Synchronized和Lock的对比,如何选择?
~~~
synchronized和Lock都是用于线程同步的机制,它们的主要区别在于:
1、实现原理:synchronized是基于JVM底层的数据同步,而Lock是基于Java编写一个类,主要通过硬件依赖CPU指令实现数据同步。
2、释放锁的方式:synchronized是在同步代码块执行完毕或出现异常时,JVM会让线程释放锁,而Lock必须手工释放。
3、性能:在资源竞争不是很激烈的情况下,synchronized的性能要优于Lock,但是在资源竞争很激烈的情况下,Lock的性能能维持常态,ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步等。
4、可见性:synchronized可以保证共享变量的可见性,而Lock不保证可见性。
选择synchronized还是Lock主要取决于应用场景。对于对共享资源的同步和安全性要求较高的场景,建议使用synchronized;对于对性能要求较高的场景,可以使用Lock。如果在多个线程之间共享的资源不是很关键的情况下,可以使用Lock来代替synchronized。
~~~
#### 5、 多个线程等待同一个Synchronized锁的时候,JVM如何选择下一个获取锁的线程?
~~~
在多个线程等待同一个synchronized锁的情况下,JVM会按照以下顺序选择下一个获取锁的线程:
1、首先,JVM会检查当前正在获取锁的线程是否可达(即该线程是否已经访问过共享资源)。如果当前正在获取锁的线程可达,那么JVM会继续等待该线程执行完毕,然后再选择下一个线程。
2、如果当前正在获取锁的线程不可达,那么JVM会检查当前等待在锁上的线程数(即等待获取锁的线程数)。如果当前等待在锁上的线程数等于当前线程数,那么JVM会继续等待当前线程执行完毕,然后再选择下一个线程。
3、如果当前等待在锁上的线程数小于当前线程数,那么JVM会选择当前线程作为下一个获取锁的线程。这通常发生在一个非主线程中,当多个子线程都等待在一个
synchronized代码块中时,JVM会选择当前线程作为下一个获取锁的线程,因为非主线程没有访问共享资源的需求,它们只是在等待主线程执行完毕。
需要注意的是,在多个线程等待同一个synchronized锁的情况下,JVM会尽可能地让每个线程都有机会获取锁,但是在某些情况下,JVM可能会选择等待时间最长的线程来获取锁,从而保证锁的公平性。因此,在使用synchronized时,需要注意共享资源的访问模式和锁的使用方式,以便更好地控制线程同步和并发访问。
~~~
### 6、Synchronized使得同时只有一个线程可以执行,性能比较差,有什么提升的方法
~~~
Synchronized虽然可以保证同步代码块的执行效率,但是它的性能比较差,特别是在多个线程等待同一个锁的情况下。为了提升Synchronized的性能,可以考虑以下几种方法:
1、使用读写锁(ReentrantReadWriteLock):ReentrantReadWriteLock 比 synchronized 更加灵活,可以自由地控制锁的获取和释放。它提供了读锁和写锁两种模式,可以根据需求选择适当的模式,从而提高程序的性能。
2、避免使用Synchronized:在不需要同步的情况下,应该尽量避免使用 Synchronized。同时使用多个锁可能会导致死锁等问题,因此应该尽量避免在多个线程之间共享的代码块中使用 Synchronized。
3、使用更高级别的同步机制:如果应用程序需要更高级别的同步机制,可以考虑使用Java提供的并发容器,如 Collections.synchronizedList()、Collections.synchronizedSet() 和 Collections.synchronizedMap()。这些容器提供了更高级别的同步机制,可以更好地控制并发访问。
4、使用线程池:使用线程池可以更好地控制并发访问,避免多个线程同时修改共享资源而产生的数据不一致问题。线程池可以更好地控制线程数量和线程的执行时间,从而提高程序的性能。
总的来说,Synchronized虽然是一种常用的同步机制,但是在性能方面可能存在一些问题。通过使用更高级别的同步机制、避免使用Synchronized、使用线程池等方法,可以提高Synchronized的性能,从而更好地控制并发访问。
~~~
### 7、我想更加灵活的控制锁的释放和获取(现在释放锁和获取锁的时机都被规定死了),怎么办?
~~~
要更加灵活地控制锁的释放和获取,可以考虑以下两种方法:
1、使用适当的同步级别:如果应用程序需要更高级别的同步机制,可以考虑使用Java提供的并发容器,如 Collections.synchronizedList()、Collections.synchronizedSet() 和 Collections.synchronizedMap()。这些容器提供了更高级别的同步机制,可以更好地控制并发访问。
2、使用Java中的线程池:使用线程池可以更好地控制并发访问,避免多个线程同时修改共享资源而产生的数据不一致问题。线程池可以更好地控制线程数量和线程的执行时间,从而提高程序的性能。
使用适当的同步级别可以避免死锁等问题,同时可以灵活地控制锁的释放和获取。而使用线程池则可以更好地控制并发访问,提高程序的性能。
~~~