Java中 synchronized 关键字学习

2024-08-29 14:58

本文主要是介绍Java中 synchronized 关键字学习,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

     线程安全是我们并发编程中很重要的,为什么会造成线程安全,主要原因有两点:

1、共享数据(即临界资源)
2、多条线程共同操作同一数据。
为了解决这种问题,所以引入了锁机制的概念。这种锁很好理解,我们可以理解为,“为了确保临界资源的安全,在同一时刻只能有一个线程调用该临界资源,当某个线程调用临界资源的时候,给它加上锁,不让其他线程调用,只有当前线程调用完临界资源后,开锁,临界资源可以被其他线程调用”。synchronized就是java中的一种锁。
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种: 
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

修饰一个代码块

1、当一个线程访问一个对象中的synchronized(this)中的代码块时,其他想要访问该代码块的线程将被阻塞。我们先来看个demo
/*** 同步线程* Created by leiqi on 2018/4/27.*/public class SynThread implements Runnable {public SynThread(){}@Overridepublic void run() {synchronized (this){for (int i= 0; i< 5;i++){try {System.out.println(Thread.currentThread().getName()+":"+i);Thread.sleep(100);}catch (InterruptedException e){e.printStackTrace();}}}}
}
在main方法中调用下面方法:
    /*** 访问同一对象的synchronized()this代码块*/private static void FirstSynMethond() {SynThread synThread = new SynThread();Thread thread1 = new Thread(synThread,"SynThread1");Thread thread2 = new Thread(synThread,"SynThread2");thread1.start();thread2.start();}
我们看到的结果如下:
SynThread1:0
SynThread1:1
SynThread1:2
SynThread1:3
SynThread1:4
SynThread2:0
SynThread2:1
SynThread2:2
SynThread2:3
SynThread2:4Process finished with exit code 0
我们可以看到同时调用两个并发线程对象,同时访问同一对象(SynThread)的synchronized(this)代码块时,只有一个线程能够执行,另一个线程受到阻塞,必须等前面的线程执行完之后后面的线程才能执行。这就相当于给对象加了把锁,当一个线程访问该对象时就把所关闭,不让其他线程访问,当前面线程执行完成之后,再把锁打开。这就解决了我们的并发时的安全问题。
我们再来通过下面的方法调用SynThread对象:
  /*** 访问不同对象的synchronized()this代码块*/private static void FirstSynMethond2() {SynThread synThread1 = new SynThread();SynThread synThread2 = new SynThread();Thread thread1 = new Thread(synThread1,"SynThread1");Thread thread2 = new Thread(synThread2,"SynThread2");thread1.start();thread2.start();}
得到结果如下:
SynThread1:0
SynThread2:0
SynThread1:1
SynThread2:1
SynThread1:2
SynThread2:2
SynThread2:3
SynThread1:3
SynThread2:4
SynThread1:4Process finished with exit code 0
我们发现,这次得到的结果和上次完全不一样,不是说同时只能一个又一个线程访问对象吗?我们仔细看发现我们刚才调用时。创建了两个SynThread对象,两个线程分别调用了它们自己的对象,而第一次运行的结果是两个线程调用的是同一个对象。为啥两个对象count还是一直在加,那是因为所以,在这里我们要注意一点:“ synchronized锁定的是对象,而不是类,第二次运行结果就是,因为有两个对象,所以生成了两把锁,这两把锁不干扰。
2、 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。 我们看下面demo:
/*** 访问同一对象的synchronized(this)代码块和非synchronized(this)代码块* Created by leiqi on 2018/4/28.*/
public class SynthreadDemo2 implements Runnable {private int count;public SynthreadDemo2(){count = 0;}public void countAdd(){synchronized (this){for (int i=0;i<5;i++){try {System.out.println("Add     count"+":"+(count++));Thread.sleep(100);}catch (InterruptedException e){e.printStackTrace();}}}}public void printcount(){for (int i =0; i<5;i++){try{System.out.println("print   count"+":"+(count++));Thread.sleep(100);}catch (InterruptedException e){e.printStackTrace();}}}@Overridepublic void run() {String threadName = Thread.currentThread().getName();if (threadName.equals("SynThreadDemo1")){countAdd();}else if (threadName.equals("SynThreadDemo2")){printcount();}}
}
调用方法如下:
 /*** 同时访问同一对象的synchronized(this)代码块和非synchronized(this)代码块*/private static void SecondSynDemoMehond(){SynthreadDemo2 synthreadDemo2 = new SynthreadDemo2();Thread thread1= new Thread(synthreadDemo2,"SynThreadDemo1");Thread thread2= new Thread(synthreadDemo2,"SynThreadDemo2");thread1.start();thread2.start();}
我们得到结果如下所示:
print   count:1
Add     count:0
print   count:2
Add     count:3
print   count:4
Add     count:5
print   count:6
Add     count:7
print   count:8
Add     count:9Process finished with exit code 0
上面的两个代码printcount是非synchronized(this)代码块,countAdd是synchronized(this)代码块,根据运行结果我们可以看到当一个线程访问一个对象的synchronized的代码块时其他线程可以访问该对象的非synchronized代码块。

修饰一个方法

synchhronized修饰一个方法比较简单,有以下两种方法:
    /*** 写法一*/public synchronized void method(){//todo}/*** 写法二*/public void method(){synchronized (this){//todo}}
这两种写法,写法一修饰一个方法 写法二修饰后整个方法的代码块,两者都是修饰了整个方法的代码,所以两种写法是等价的。在用synchronized修饰一个方法要注意以下几点:
1、synchronized关键字不能继承
我们可以使用synchronized关键字来修饰方法,但是synchronized是不属于方法的一部分,所以synchronized是不能被继承的。当我们用synchronized修饰一个方法后,在子类覆盖来这个方法,在子类中这个方法并不是同步的,必须显示的在子类调用方法上加上synchronized关键字才可以。也可以在子类调用父类的方法,两种写法demo如下:
在子类方法加上synchronized关键字:
class Parent {public synchronized void method() { }}class Child extends Parent {public synchronized void method() { }}

在子类方法中调用父类方法:

  class Parent {public synchronized void method() {   }}class Child extends Parent {public void method() { super.method();   }}

2、定义接口方法时不能使用synchronized关键字

3、构造方法不能使用synchronized关键字,但是可以使用synchronized (this)来同步代码块。

修饰一个静态方法

     synchronized修饰一个静态方法写法如下:

public synchronized static void method() {// todo
}

   由于静态方法是属于类的而不是属于对象的,所以synchronized修饰静态方法是给这个类的对象加上锁,我们将第一个demo稍加改动:

/*** 同步线程* Created by leiqi on 2018/4/27.*/public class SynThread implements Runnable {public SynThread(){}@Overridepublic void run() {method();}private synchronized static void method(){for (int i= 0; i< 5;i++){try {System.out.println(Thread.currentThread().getName()+":"+i);Thread.sleep(100);}catch (InterruptedException e){e.printStackTrace();}}}
}
调用方法根修饰代码块的第二种调用方法一样:
   /*** 访问不同对象的synchronized()this代码块*/private static void FirstSynMethond2() {SynThread synThread1 = new SynThread();SynThread synThread2 = new SynThread();Thread thread1 = new Thread(synThread1,"SynThread1");Thread thread2 = new Thread(synThread2,"SynThread2");thread1.start();thread2.start();}

而这次我们得到的结果如下:

SynThread1:0
SynThread1:1
SynThread1:2
SynThread1:3
SynThread1:4
SynThread2:0
SynThread2:1
SynThread2:2
SynThread2:3
SynThread2:4Process finished with exit code 0
    为啥这次的结果和上面执行的结果不一样昵?这是因为 synchronized这次修饰的是静态方法,静态方法属于类,所以这次synchronized是给累的对象加锁,故而synThread1和synThread2是同一把锁。

修饰一个类

   synchronized还可以用来修饰一个类,写法如下:

class ClassName{public void method(){synchronized (ClassName.class){//todo}}
}
    我们把上面的demo再做个修改
/*** 同步线程* Created by leiqi on 2018/4/27.*/public class SynThread implements Runnable {public SynThread(){}@Overridepublic void run() {method();}private  void method(){synchronized (SynThread.class){for (int i= 0; i< 5;i++){try {System.out.println(Thread.currentThread().getName()+":"+i);Thread.sleep(100);}catch (InterruptedException e){e.printStackTrace();}}}}
}

  它的运行效果和上面的结果是一样的,最终效果都是给类的对象加锁,synchronized用于修饰一个类时,是给这个类的所有对象加锁,所有对象都是同同一把锁。

总结

    通过上面的学习我们简单总结一下:




这篇关于Java中 synchronized 关键字学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1118186

相关文章

Spring Boot @RestControllerAdvice全局异常处理最佳实践

《SpringBoot@RestControllerAdvice全局异常处理最佳实践》本文详解SpringBoot中通过@RestControllerAdvice实现全局异常处理,强调代码复用、统... 目录前言一、为什么要使用全局异常处理?二、核心注解解析1. @RestControllerAdvice2

Spring IoC 容器的使用详解(最新整理)

《SpringIoC容器的使用详解(最新整理)》文章介绍了Spring框架中的应用分层思想与IoC容器原理,通过分层解耦业务逻辑、数据访问等模块,IoC容器利用@Component注解管理Bean... 目录1. 应用分层2. IoC 的介绍3. IoC 容器的使用3.1. bean 的存储3.2. 方法注

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

java中新生代和老生代的关系说明

《java中新生代和老生代的关系说明》:本文主要介绍java中新生代和老生代的关系说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、内存区域划分新生代老年代二、对象生命周期与晋升流程三、新生代与老年代的协作机制1. 跨代引用处理2. 动态年龄判定3. 空间分

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

深度解析Java DTO(最新推荐)

《深度解析JavaDTO(最新推荐)》DTO(DataTransferObject)是一种用于在不同层(如Controller层、Service层)之间传输数据的对象设计模式,其核心目的是封装数据,... 目录一、什么是DTO?DTO的核心特点:二、为什么需要DTO?(对比Entity)三、实际应用场景解析

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操