Java中 synchronized 关键字学习

简介: 版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/80167995 前言     线程安全是我们并发编程中很重要的,为什么会造成线程安全,主要原因有两点:1、共享数据(即临界资源)2、多条线程共同操作同一数据。
版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/80167995

前言

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

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(){
    }
    @Override
    public 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:4

Process 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:4

Process 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();
            }
        }
    }

    @Override
    public 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:9

Process 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(){
    }
    @Override
    public 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:4

Process 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(){
    }
    @Override
    public 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用于修饰一个类时,是给这个类的所有对象加锁,所有对象都是同同一把锁。

总结

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




相关文章
|
10天前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
43 5
|
12天前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
Java并发编程实战:使用synchronized关键字实现线程安全
27 0
|
1月前
|
存储 Oracle 安全
揭秘Java并发核心:深入Hotspot源码腹地,彻底剖析Synchronized关键字的锁机制与实现奥秘!
【8月更文挑战第4天】在Java并发世界里,`Synchronized`如同导航明灯,确保多线程环境下的代码安全执行。它通过修饰方法或代码块实现独占访问。在Hotspot JVM中,`Synchronized`依靠对象监视器(Object Monitor)机制实现,利用对象头的Mark Word管理锁状态。
33 1
|
4天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
26 6
【Java学习】多线程&JUC万字超详解
|
23天前
|
前端开发 Java 编译器
【前端学java】如何从前端视角快速学习Maven
【8月更文挑战第12天】如何从前端视角快速学习Maven
36 2
【前端学java】如何从前端视角快速学习Maven
|
10天前
|
Java
不懂synchronized?那你可能错过了Java的“半壁江山”!
不懂synchronized?那你可能错过了Java的“半壁江山”!
37 6
|
10天前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
71 5
|
9天前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
11 2
|
10天前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
Java多线程同步实战:从synchronized到Lock的进化之路!
72 1
|
7天前
|
传感器 C# 监控
硬件交互新体验:WPF与传感器的完美结合——从初始化串行端口到读取温度数据,一步步教你打造实时监控的智能应用
【8月更文挑战第31天】本文通过详细教程,指导Windows Presentation Foundation (WPF) 开发者如何读取并处理温度传感器数据,增强应用程序的功能性和用户体验。首先,通过`.NET Framework`的`Serial Port`类实现与传感器的串行通信;接着,创建WPF界面显示实时数据;最后,提供示例代码说明如何初始化串行端口及读取数据。无论哪种传感器,只要支持串行通信,均可采用类似方法集成到WPF应用中。适合希望掌握硬件交互技术的WPF开发者参考。
23 0
下一篇
DDNS