多线程系列-synchronized

多线程系列-synchronized

synchronized

什么是synchronized

synchronized的应用场景是在多线程的环境下实现方法的同步或者代码块的同步,就是说,如果你的一段代码被synchronized修饰,那么你这一段代码就只能串行

package com.luban.syn;

import java.util.concurrent.TimeUnit;

/**
 * synchronized 演示
 */
public class SynExample {

    public synchronized void test1(){
        try {
            //休眠一秒钟来实现演示效果
            TimeUnit.SECONDS.sleep(1L);
            System.out.println(Thread.currentThread().getName()+"线程被执行,打印时间为:"+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SynExample synExample = new SynExample();
        //定义需要执行的10个线程
        Thread tf[] = new Thread[10];

        for (int i = 0; i < 10 ; i++) {
            tf[i] = new Thread(){
                @Override
                public void run() {
                    synExample.test1();
                }
            };
            tf[i].start();
            try {
                //使用join可以保证线程的有序执行
                tf[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }



    }
}

外面来看上面 SynExample 代码块,将synchronized使用在方法上,是使用synchronized的其中一种方式,其他还可以使用在代码块。

执行以上的代码块我们可以得到以下的结果

Thread-0线程被执行,打印时间为:1580560098784
Thread-1线程被执行,打印时间为:1580560099785
Thread-2线程被执行,打印时间为:1580560100785
Thread-3线程被执行,打印时间为:1580560101786
Thread-4线程被执行,打印时间为:1580560102786
Thread-5线程被执行,打印时间为:1580560103790
Thread-6线程被执行,打印时间为:1580560104792
Thread-7线程被执行,打印时间为:1580560105792
Thread-8线程被执行,打印时间为:1580560106793
Thread-9线程被执行,打印时间为:1580560107795

synchronized的使用

在非静态方法中使用

在方法中使用,我们在第一个例子中就是一个典型的在方法中使用的例子

在静态方法中使用
public static synchronized void test2() {
        try {
            TimeUnit.SECONDS.sleep(1L);
            System.out.println(Thread.currentThread().getName() + "线程被执行,打印时间为:" + System.currentTimeMillis());
        } catch (InterruptedException var2) {
            var2.printStackTrace();
        }

    }

在静态代码中使用和非静态代码中使用,显性的在java代码中不存在区别,具体区别在哪里,可以继续往下看原理

在代码块中使用

在代码块中使用,先上代码
对当前对象加锁

    public void test3(){
        synchronized (this){
            try {
                TimeUnit.SECONDS.sleep(1L);
                System.out.println(Thread.currentThread().getName() + "线程被执行,打印时间为:" + sdf.format(new Date()));
            } catch (InterruptedException var2) {
                var2.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        SynExample synExample = new SynExample();
        //定义需要执行的10个线程
        Thread tf[] = new Thread[10];

        for (int i = 0; i < 10 ; i++) {
            tf[i] = new Thread(){
                @Override
                public void run() {
                    synExample.test3();
                }
            };
            tf[i].start();
        }
    }

如果我们对我们的对象加锁,请保证我们的对象是同一个,上面的例子中,我们只实例化了一个对象,因此不会出现问题,演示的结果为:

Thread-0线程被执行,打印时间为:46:40
Thread-9线程被执行,打印时间为:46:41
Thread-8线程被执行,打印时间为:46:42
Thread-7线程被执行,打印时间为:46:43
Thread-6线程被执行,打印时间为:46:44
Thread-5线程被执行,打印时间为:46:45
Thread-4线程被执行,打印时间为:46:46
Thread-3线程被执行,打印时间为:46:47
Thread-2线程被执行,打印时间为:46:48
Thread-1线程被执行,打印时间为:46:49

下面来演示一个错误的例子

public static void main(String[] args) {
        //定义需要执行的10个线程
        Thread tf[] = new Thread[10];

        for (int i = 0; i < 10 ; i++) {
            tf[i] = new Thread(){
                @Override
                public void run() {
                    SynExample synExample = new SynExample();
                    synExample.test3();
                }
            };
            tf[i].start();
        }

上面我们的对象不同,因此,我们的同步块也不起作用,因为我们是对我们的对象加的锁,熟悉jvm的人一定知道,在外面的堆区中不同的对象没有任何关系,因此,以上外面的代码就会导致我们的同步块失效

对当前类加锁
当出现上面代码的问题,有可能是写代码的时候出现的失误,为了避免出现上述的问题,我们可以对我们的类对象加锁

public void test4(){
        synchronized (SynExample.class){
            try {
                TimeUnit.SECONDS.sleep(1L);
                System.out.println(Thread.currentThread().getName() + "线程被执行,打印时间为:" + sdf.format(new Date()));
            } catch (InterruptedException var2) {
                var2.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        //定义需要执行的10个线程
        Thread tf[] = new Thread[10];

        for (int i = 0; i < 10 ; i++) {
            tf[i] = new Thread(){
                @Override
                public void run() {
                    SynExample synExample = new SynExample();
                    synExample.test4();
                }
            };
            tf[i].start();
        }
    }

以上就是所有对sychronized的用法

synchronized 原理

在分析synchronized的原理之前,我们先了解以下我们怎么去查看class源码,因为我们要了解synchronized的原理,我们就需要去了解以下我们的java源文件被编译之后生成的文件是什么样子的。
我们可以通过

javap -v class文件

我们通过反编译可以得到相应的编译后的文件信息

 public synchronized void test1();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=4, locals=2, args_size=1
         0: getstatic     #6                  // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;
         3: lconst_1
         4: invokevirtual #7                  // Method java/util/concurrent/TimeUnit.sleep:(J)V
         7: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: new           #9                  // class java/lang/StringBuilder
        13: dup
        14: invokespecial #10                 // Method java/lang/StringBuilder."<init>":()V
        17: invokestatic  #11                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        20: invokevirtual #12                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        23: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        26: ldc           #14                 // String 线程被执行,打印时间为:
        28: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        31: invokestatic  #15                 // Method java/lang/System.currentTimeMillis:()J
        34: invokevirtual #16                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
        37: invokevirtual #17                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        40: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        43: goto          51
        46: astore_1
        47: aload_1
        48: invokevirtual #20                 // Method java/lang/InterruptedException.printStackTrace:()V
        51: return
      Exception table:
         from    to  target type
             0    43    46   Class java/lang/InterruptedException
      LineNumberTable:
        line 17: 0
        line 18: 7
        line 21: 43
        line 19: 46
        line 20: 47
        line 22: 51
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           47       4     1     e   Ljava/lang/InterruptedException;
            0      52     0  this   Lcom/luban/syn/SynExample;
      StackMapTable: number_of_entries = 2
        frame_type = 110 /* same_locals_1_stack_item */
          stack = [ class java/lang/InterruptedException ]
        frame_type = 4 /* same */

从上面这一段代码中我们可以发现,如果我们使用在方法中加synchronized的方式的话,我们的源码就会在指令中添加 ACC_SYNCHRONIZED
这个指令的意思就是说,当我们的java虚拟机读到这里的时候,会去看当前方法是不是可以执行,如果已经有别的方法在执行了,就阻塞,如果还没有,就执行

 public void test3();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=5, locals=4, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #6                  // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;
         7: lconst_1
         8: invokevirtual #7                  // Method java/util/concurrent/TimeUnit.sleep:(J)V
        11: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        14: new           #9                  // class java/lang/StringBuilder
        17: dup
        18: invokespecial #10                 // Method java/lang/StringBuilder."<init>":()V
        21: invokestatic  #11                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        24: invokevirtual #12                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        27: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        30: ldc           #14                 // String 线程被执行,打印时间为:
        32: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        35: aload_0
        36: getfield      #5                  // Field sdf:Ljava/text/SimpleDateFormat;
        39: new           #21                 // class java/util/Date
        42: dup
        43: invokespecial #22                 // Method java/util/Date."<init>":()V
        46: invokevirtual #23                 // Method java/text/SimpleDateFormat.format:(Ljava/util/Date;)Ljava/lang/String;
        49: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        52: invokevirtual #17                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        55: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        58: goto          66
        61: astore_2
        62: aload_2
        63: invokevirtual #20                 // Method java/lang/InterruptedException.printStackTrace:()V
        66: aload_1
        67: monitorexit
        68: goto          76
        71: astore_3
        72: aload_1
        73: monitorexit
        74: aload_3
        75: athrow
        76: return
      Exception table:
         from    to  target type
             4    58    61   Class java/lang/InterruptedException
             4    68    71   any
            71    74    71   any
      LineNumberTable:
        line 35: 0
        line 37: 4
        line 38: 11
        line 41: 58
        line 39: 61
        line 40: 62
        line 42: 66
        line 44: 76
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           62       4     2  var2   Ljava/lang/InterruptedException;
            0      77     0  this   Lcom/luban/syn/SynExample;
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 61
          locals = [ class com/luban/syn/SynExample, class java/lang/Object ]
          stack = [ class java/lang/InterruptedException ]
        frame_type = 4 /* same */
        frame_type = 68 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

在上面这一段就是指在代码块中添加synchronized
这里有几个关键指令
monitorenter:进入同步代码块
monitorexit:退出同步代码块
我们可以在上面的代码中发现,当前的指令集中存在了两个monitorexit
第一个是正常执行的退出
第二个是异常执行的退出

对monitorenter的解释

每个对象都拥有一个monitor(监视器)
某一个线程想要去占有这个对象的时候,需要先看一下,monitor计数器是不是等于0,
如果不是0,
如果不是已经进入的线程,说明还有其他线程占有着,不可进入
如果是已经进入过的线程,则可以进入,monitor+1(可重入性)
如果等于0,当前线程占有这个对象,并且对这个对象的monitor+1

对monitorexit的解释

当线程走到monitorexit的时候,对monitor-1

对ACC_SYNCHRONIZED的解释

如果我们的synchronized加在了方法上,那么很明显,在我们的指令集中不存在monitorexit和monitorenter,但是会有一个ACC_SYNCHRONIZED,当程序要进入方法的时候,会检查是不是存在ACC_SYNCHRONIZED,如果存在,那么会去看一下monitor计数器是不是等于0,
如果不是0,
如果不是已经进入的线程,说明还有其他线程占有着,不可进入
如果是已经进入过的线程,则可以进入,monitor+1(可重入性)
如果等于0,当前线程占有这个对象,并且对这个对象的monitor+1

monitor在对象中的位置


如上图所示,我们的对象在内存中的存储分为三个部分,我们的monitor存储在对象头当中

synchronized的优化

在jdk1.6之前,synchronized是属于重量级锁,在1.6之后,为了减少获取锁和释放锁产生的消耗,引入了偏向锁和轻量级锁,关于具体优化细节将在下一篇博客说明

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值