标签归档:java

Java 类成员执行顺序

  1. 如果类还没有被加载:
    1. 先执行父类的静态代码块和静态变量初始化,并且静态代码块和静态变量的执行顺序只跟代码中出现的顺序有关
    2. 执行子类的静态代码块和静态变量初始化
    3. 执行父类的实例变量初始化(例如:int a; 初始化就是0,引用类型就是null)
    4. 执行父类的构造函数
    5. 执行子类的实例变量初始化(例如:int a; 初始化就是0,引用类型就是null)
    6. 执行子类的构造函数
  2. 如果类已经被加载:
    1. 则静态代码块和静态变量就不用重复执行,再创建类对象时,只执行与实例相关的变量初始化和构造方法

参考 java 中类初始化,构造方法,静态成员变量,静态块的加载顺序

用 ThreadLocal 实现静态变量线程同步

代码中有一种使用场景:
子类需要调用父类的构造方法来进行实例化,而传给父类构造方法的参数中却有参数需要在子类中先进行加工处理。比如下面的例子:

父类构造方法的第三个参数需要调用 signUrl 这个方法进行加工。因为

Cannot reference ‘signUrl’ before supertype constructor has been called

所以,一种解决方法就是将 signUrl 声明为 static 类型的。

可以看到成员变量 mRequestParamMap 在该方法中进行了引用。所以 mRequestParamMap 也需要声明为 static。(或者可以把 mRequestParamMap 声明为一个 signUrl 内的局部变量,但此处该变量还在别的方法里进行了引用,所以不能改为局部变量)

这同时就导致了多线程调用时 mRequestParamMap 的同步问题。因为不同线程访问的是同一个 mRequestParamMap,一旦同时进行了操作就会导致不同步的问题发生。

这里将介绍一种方法来规避这种问题,即使用 ThreadLocal 来保存该变量。

ThreadLocal 的作用是每个线程都会获得一个只属于线程自己的变量,其他线程无法访问。
如此这般,就实现了静态变量在线程间的同步。

FindBugs 检出的三种问题小记

FindBugs™ – Find Bugs in Java Programs

本文记录了三种 FindBugs 检出来的问题。

一、Boxing/unboxing to parse a primitive

A boxed primitive is created from a String, just to extract the unboxed primitive value. It is more efficient to just call the static parseXXX method.

该问题发生场景如下图所示:

Integer 的 valueOf 方法可以优化为 parseInt 方法。mDefaultSubId 是一个 int 类型的变量,而 defAccount.getId 返回的是一个 String 类型。这两个方法的作用都是把 String 类型转换为 int 类型,区别是调用 Integer 的 valueOf 方法会返回一个 Integer 类型的结果,再经 Java 的自动拆箱机制而转换成 int 类型,而 parseInt 方法会直接返回一个 int 类型的结果。

从 Integer 的源码中也可以看到:

valueOf 方法内部也是先调用了 parseInt 转成 int 类型,再转成 Integer 类型,在该场景下属于冗余操作了。

因此,此处应该进行方法的替换优化。

二、May expose internal representation by returning reference to mutable object

Returning a reference to a mutable object value stored in one of the object’s fields exposes the internal representation of the object. If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Returning a new copy of the object is better approach in many situations.

该问题发生在如下场景:

getCatIds 方法返回一个 String[] 类型的对象,该对象是一个可变对象。由于该对象可以自行更改内部的值而不通过相应的 set 方法,就可能产生安全问题或错误。更好的做法是在 getCatIds 方法中返回一个 catIds 的拷贝。

三、May expose internal representation by incorporating reference to mutable object

This code stores a reference to an externally mutable object into the internal representation of the object. If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Storing a copy of the object is better approach in many situations.

该问题发生在如下场景:

setCatIds 方法直接保存了一个 String[] 类型的可变对象,如果在 setCatIds 方法调用之后 catIds 进行了更改,那么这些更改也会影响到调用 setCatIds 的那个对象。因此,和上面的问题类似,更好的做法还是在 setCatIds 方法中保存一个参数对象的拷贝。

当然,这些问题都是一点性能问题或者安全问题,可能并不会在使用中出错,但是了解它对于提升自己的能力还是有帮助的。😜

Java 中的同步

From: [Android教程] 浅谈Java同步锁(Android中的同步)

多线程应用中,我们往往会对同一对象或类进行操作,这时我们需要应用同步锁,以保证程序的正常运行。本文将从Synchronized, wait, notify这些Java常见的关键字/函数作为出发点,总结同步与锁的问题,适合Java初级者阅读解惑。

synchronized 关键字

在实际编程中,我们有两种方式实现同步,分别是同步方法(synchronized methods)或同步块(synchronized block/synchronized statement)。

同步方法是在方法前加synchronized, 如果该方法是static的,则认为锁是相对于Class的,其他线程操作该类的任何对象时,遇到static同步方法或者方法内同步该Class时,需要等待;若该方法不是static的,则认为锁是相对于自身对象(this)的,其他线程操作此对象时,遇到同步方法(非static),需要等待。

[注意点]

  1. 可以认为锁是属于引用类型的, 同步的操作需要获取锁之后才进行,否则一直等待。编程时需注意锁(synchronized)的对象。
  2. 线程在wait后会释放持有的锁。有关wait详细说明参见本文第二部分。
  3. 各线程同步时遵守先触发,先得锁原则(happens-before relationship)。
  4. 构造函数无法被synchronized。

wait && notify

wait, notify, notifyAll方法均定义在基类Object中,它们的职责是为了在多线程中,可以有效的进行线程间交互,或者控制转移。一个线程执行了wait, 需要其他线程notify,或者notifyAll, 才可以继续执行。举个例子,消费者和生产者,当没有Message被生产者生产时,消费者则一直处于wait状态,直到生产者生产了一条Message,然后notify消费者进行消费。

[注意点]

  1. wait, notify, notifyAll必须在synchronized代码内。即该线程持有了某引用的锁时,wait, notify, notifyAll才可以被执行,否则,会报IllegalMonitorStateException.

    如以下代码(针对wait的,notify, notifyAll同理):

    // 会抛IllegalMonitorStateException异常, 因为没持有this的锁。
    this.wait();
    
    synchronized(this) {
    
       // 正确写法
       this.wait();
    
       // 会抛IllegalMonitorStateException异常, 因为没有持有a对象的锁。
       this.a.wait();
    }
    
  2. 执行wait()后会释放锁持有的锁,其他等待同步中的线程这时会持有该锁,并执行。

  3. wait()执行后,线程状态会变为disabled, 想继续执行除非以下事件中的一个发生:

  • 其他线程在此同步的引用上执行了notify()或者notifyAll(),注意是和wait相同引用上执行的notify()。
  • 线程被其他线程中断,会报InterruptedException。
  • wait可以指定timeout, 当timeout时间过去时。
  1. wait继续执行需要重新获得该引用的锁,若有其他线程占有着此锁,则仍然无法恢复。

  2. notify是随机唤醒一个wait中的线程,notifyAll是把所有wait中的线程全部唤醒。notify的对象仍旧是其持有的锁的引用。

  3. 如果没有线程wait, notify将会被忽略。