一、什么是Synchronized
能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果
二、Synchronized的作用
- 原子性:确保线程互斥地访问同步代码;
- 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的“对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
- 有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”;
三、Synchronized主要有三种用法
1.修饰实例方法:作用于当前实例加锁 (对象实例锁)
public synchronized void method() {
//业务代码
}
2.修饰静态方法:作用于当前类对象加锁 (类锁)
也就是给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁。因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管 new 了多少个对象,只有一份)
public static synchronized void method() {
//业务代码
}
3.修饰代码块:指定加锁对象,对给定对象加锁 (对象锁)
表示进入同步代码库前要获得给定对象的锁
synchronized (this){
}
表示进入同步代码库前要获得当前class加锁
synchronized (Demo.class){
}
4.总结
根据上述举例我们可以把锁分为两个用法
对象锁:包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)
分为方法锁住和代码块锁
类锁:修饰静态代码的方法或者指定锁为class对象
分为静态锁和class对象
四、代码块锁的使用演示
package synchronizeddemo;
/**
* @author: XXX
* @date: 2023-04-20 15:38
*/
public class demo2 implements Runnable{
static demo2 demo2=new demo2();
// Object lock=new Object
@Override
public void run() {
// 这里可以不用对象,可以自定义一个对象 lock
synchronized(this){
System.out.println("【"+Thread.currentThread().getName()+"】开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("【"+Thread.currentThread().getName()+"】结束执行");
}
}
public static void main(String[] args) {
Thread thread0=new Thread(demo2);
Thread thread1=new Thread(demo2);
thread0.start();
thread1.start();
}
}
五、方法锁的使用演示
package synchronizeddemo;
/**
* 方法锁的演示
* @author: XXX
* @date: 2023-04-20 15:59
*/
public class demo3 implements Runnable{
static demo3 demo3=new demo3();
@Override
public void run() {
method();
}
public synchronized void method(){
System.out.println("【"+Thread.currentThread().getName()+"】开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("【"+Thread.currentThread().getName()+"】结束执行");
}
public static void main(String[] args) {
Thread thread0=new Thread(demo3);
Thread thread1=new Thread(demo3);
thread0.start();
thread1.start();
}
}
六、类锁的使用演示
只有一个Class对象:Java类可能会有很多个对象,但是只有1个Class对象。
本质:所以所谓的类锁,不过是Class对象的锁而已
用法和效果: 类锁只能在同一时刻被一个对象拥有
形式1:synchronized加在static方法上
package synchronizeddemo;
/**
* @author: chengen
* @date: 2023-04-21 11:16
*/
public class nynchroizedClassStatic implements Runnable{
static nynchroizedClassStatic nynchroizedClassStatic0=new nynchroizedClassStatic();
static nynchroizedClassStatic nynchroizedClassStatic1=new nynchroizedClassStatic();
@Override
public void run() {
method();
}
public synchronized void method(){
System.out.println("【"+Thread.currentThread().getName()+"】开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("【"+Thread.currentThread().getName()+"】结束执行");
}
public static void main(String[] args) {
Thread thread0=new Thread(nynchroizedClassStatic0);
Thread thread1=new Thread(nynchroizedClassStatic1);
thread0.start();
thread1.start();
}
}
执行上面的代码会出现thread0和thread1同时执行,因为是实例了两个对象,两个对象去请求
package synchronizeddemo;
/**
* @author: chengen
* @date: 2023-04-21 11:16
*/
public class nynchroizedClassStatic implements Runnable{
static nynchroizedClassStatic nynchroizedClassStatic0=new nynchroizedClassStatic();
static nynchroizedClassStatic nynchroizedClassStatic1=new nynchroizedClassStatic();
@Override
public void run() {
method();
}
public static synchronized void method(){
System.out.println("【"+Thread.currentThread().getName()+"】开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("【"+Thread.currentThread().getName()+"】结束执行");
}
public static void main(String[] args) {
Thread thread0=new Thread(nynchroizedClassStatic0);
Thread thread1=new Thread(nynchroizedClassStatic1);
thread0.start();
thread1.start();
}
}
在method方法上加上static修饰,就会出现锁的效果,这是因为nynchroizedClassStatic0和nynchroizedClassStatic0都拥有一个唯一的类
形式2:synchronized (*.class)代码块
package synchronizeddemo;
/**
* @author: chengen
* @date: 2023-04-21 11:28
*/
public class synchroizedClassClass implements Runnable{
static synchroizedClassClass synchroizedClassClass0=new synchroizedClassClass();
static synchroizedClassClass synchroizedClassClass1=new synchroizedClassClass();
@Override
public void run() {
method();
}
public void method(){
synchronized (this){
System.out.println("【"+Thread.currentThread().getName()+"】开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("【"+Thread.currentThread().getName()+"】结束执行");
}
}
public static void main(String[] args) {
Thread thread0=new Thread(synchroizedClassClass0);
Thread thread1=new Thread(synchroizedClassClass1);
thread0.start();
thread1.start();
}
}
执行以上代码会出现thread0和thread1同时执行的情况,因为代码块中的this指的是当前的对象,也就是synchroizedClassClass0,和synchroizedClassClass1
package synchronizeddemo;
/**
* @author: chengen
* @date: 2023-04-21 11:28
*/
public class synchroizedClassClass implements Runnable{
static synchroizedClassClass synchroizedClassClass0=new synchroizedClassClass();
static synchroizedClassClass synchroizedClassClass1=new synchroizedClassClass();
@Override
public void run() {
method();
}
public void method(){
synchronized (synchroizedClassClass.class){
System.out.println("【"+Thread.currentThread().getName()+"】开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("【"+Thread.currentThread().getName()+"】结束执行");
}
}
public static void main(String[] args) {
Thread thread0=new Thread(synchroizedClassClass0);
Thread thread1=new Thread(synchroizedClassClass1);
thread0.start();
thread1.start();
}
}
把代码块中的this改成synchroizedClassClass.class这样就是对synchroizedClassClass加锁
七、多线程访问同步方法的7种情况
1.两个线程同时访问一个对象的同步方法是串行还是并行
答案:串行,因为是一个对象同步方法拿的this
2.两个线程访问的是两个对象的同步方法是串行还是并行
答案:并行,因为this代表的当前对象,除非这个同步方法中是类锁也就是class锁
3.两个线程访问的是synchronized的静态方法是串行还是并行
答案:串行,这是因为如果使用静态修饰方法就上升为类锁
4.同时访问同步方法与非同步方法是串行还是并行
答案:并行,一个线程访问同步方法另外一个线程访问同步方法,所以会同时进行
package synchronizeddemo;
/**
* @author: XXX
* @date: 2023-05-04 11:14
*/
public class demo4 implements Runnable{
static demo4 demo4=new demo4();
public static synchronized void method(){
System.out.println("【"+Thread.currentThread().getName()+"】"+"加锁的方法开始执行");
System.out.println("【"+Thread.currentThread().getName()+"】"+"加锁的方法结束执行");
}
public static void method1(){
System.out.println("【"+Thread.currentThread().getName()+"】"+"没有加锁的方法开始执行");
System.out.println("【"+Thread.currentThread().getName()+"】"+"没有加锁的方法结束执行");
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method();
}else{
method1();
}
}
public static void main(String[] args) {
Thread thread0=new Thread(demo4);
Thread thread1=new Thread(demo4);
thread0.start();
thread1.start();
}
}
5.访问同一个对象不同的普通(非static)同步方法
答案:串行,因为访问method和method1都是一个this,这个this就是 demo5
package synchronizeddemo;
/**
* @author: XXX
* @date: 2023-05-04 11:14
*/
public class demo5 implements Runnable{
static demo5 demo5=new demo5();
public synchronized void method(){
System.out.println("【"+Thread.currentThread().getName()+"】"+"method加锁的方法开始执行");
System.out.println("【"+Thread.currentThread().getName()+"】"+"method加锁的方法结束执行");
}
public synchronized void method1(){
System.out.println("【"+Thread.currentThread().getName()+"】"+"method1加锁的方法开始执行");
System.out.println("【"+Thread.currentThread().getName()+"】"+"method1加锁的方法结束执行");
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method();
}else{
method1();
}
}
public static void main(String[] args) {
Thread thread0=new Thread(demo5);
Thread thread1=new Thread(demo5);
thread0.start();
thread1.start();
}
}
6.同时访问静态synchronized和非静态synchronized方法
答案:并行,因为method是类锁拿到的class,而method1拿到的this锁当前对象的锁互不影响。
package synchronizeddemo;
/**
* @author: XXX
* @date: 2023-05-04 11:14
*/
public class demo6 implements Runnable{
static demo6 demo6=new demo6();
public synchronized static void method(){
System.out.println("【"+Thread.currentThread().getName()+"】"+"method加锁的静态方法开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("【"+Thread.currentThread().getName()+"】"+"method加锁的静态方法结束执行");
}
public synchronized void method1(){
System.out.println("【"+Thread.currentThread().getName()+"】"+"method1加锁的非静态方法开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("【"+Thread.currentThread().getName()+"】"+"method1加锁的非静态方法结束执行");
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method();
}else{
method1();
}
}
public static void main(String[] args) {
Thread thread0=new Thread(demo6);
Thread thread1=new Thread(demo6);
thread0.start();
thread1.start();
}
}
7.抛出异常后是否会自动释放锁
答案:会
八、Synchronized的性质
1.可重入
可重入指的是同一线程的外层函数获得锁以后,内层函数可以直接再次获取该锁。
好处:避免死锁、提升封装性
2.不可中断
一旦这个锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁。如果别人永远不释放锁,那么我只能永远地等下去。