博客主页

Java什么时候才能用synchronized

编写多线程的代码时,使用synchronized关键字能提供JVM级的线程同步。因为synchronized本身性能不高,于是Doug Lea编写了JUC模块,提供了AQS这样强大的接口,以及ReentrantLock这样方便的类。

也许你会听到有人跟你讲,jdk6后synchronized得到了优化,性能已经不低了。但实际上,在多线程竞争时,synchronized效率依然很低,线程竞争激烈时,还是不可以用synchronized,为什么呢?

上面的图片出自openjdk HotSpot对synchronized的描述,右边是jdk6以前的加锁流程,左边是jdk6后的优化,即加入了偏向锁。简单来说,当第一个线程进入synchronized代码块时,JVM会利用对象头的mark word存下当前线程ID,下次同一线程再进入,只需要对比线程ID即可,不需要经过右边那样的加锁过程。

因此我们可以看到,jdk6优化的是同一线程的进入,但在竞争激烈的情况下,还是要走普通的加锁流程。右边的流程,即是是同一线程进入,也要经过一次CAS操作判断锁的归属,CAS操作变多,带来的是很大的性能损耗。

那么ReentrantLock为什么性能更高呢?我们可以看看它的加锁过程。

final boolean nonfairTryAcquire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    if (compareAndSetState(0, acquires)) {
      setExclusiveOwnerThread(current);
      return true;
    }
  }
  else if (current == getExclusiveOwnerThread()) {
    int nextc = c + acquires;
    if (nextc < 0) // overflow
      throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
  }
  return false;
}

上面代码,只有第一次获得锁时才会执行CAS操作,加锁失败会执行park进入队列等待唤醒。else if 判断的是线程重进入,只是对比线程,不需要执行CAS操作。因此,ReentrantLock在资源竞争激烈时,也能保持较高的性能。

回到原本的问题,什么情况下才能用synchronized呢? 很简单,在肯定资源几乎不会有竞争的情况下,才可以使用,比如spring的代码。

	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			......
		}
	}

还有一点需要注意,使用synchronized最好创建一个Object作为Lock,不要尝试去锁定某个对象本身。因为一旦访问了对象的hash code,偏向锁会失效。

Object lock = new Object();
synchronized(lock) {
  //do something...
}

//不要这样做
//synchronized(this) {
//}

想要知道这些的所有原因,就必须去读一下openjdk这篇文章了。Synchronization and Object Locking

Read more

Java泛型几个常见的术语

泛型有几个专业术语: Generic Type、Parameterized Type、Type Parameter、Type Arguments。这几个东西我也不知道怎么翻译,直接照搬外网的解释了。不想翻译内容,就当个人笔记。文章内容全部来自,这里有很多泛型的解释,GenericsFAQ 强推一波。

Generic type

A generic type is a type with formal type parameters. A parameterized type is an instantiation of a generic type with actual type arguments.

A generic type is a reference type that has one or more type parameters. These type parameters are later replaced by type arguments when the generic type is instantiated (or declared ).

Example (of a generic type):

interface Collection<E> {
  public void add (E x);
  public Iterator iterator();
}

Read more

谈谈Java的变异(不变 协变 逆变)

Subtyping

要想了解变异先要理解Subtyping的概念。Subtyping是面向对象里”类型多态(Type Polymorphism)”的其中一种表现形式,它主要描述”is a”这样的关系。比如ST的子类型,那么他们的关系可以表达为 S is a subtype of T。维基百科有一段对Subtyping的描述。

In programming language theory, subtyping (also subtype polymorphism or inclusion polymorphism) is a form of type polymorphism in which a subtype is a datatype that is related to another datatype (the supertype) by some notion of substitutability.

If S is a subtype of T, the subtyping relation is often written S <: T, to mean that any term of type S can be safely used in a context where a term of type T is expected

Subtyping和变异有什么联系呢?变异其实就是指Subtyping在更复杂的场景下,比如If S is a subtype of T, Generic<S> is subtype of Generic<T>这种关系是否还能成立。

Read more

从生物学角度看面向对象编程的多态

我们都知道面向对象编程是尽量模拟现实世界对象之间的关系,”多态”是OOP中非常重要的一个概念,它是指一个对象的多种形态。不过这个概念不是很好理解,或者说我们有时候理解的不是很透彻。最近刚好有空翻了一下Wikipedia,发现从生物学角度更好理解多态。

//已知Tiger是Cat的派生类,有没有想过,为什么这样就称为多态?
Cat cat = new Tiger();

Read more

Lambda表达式和匿名类的区别,不只是语法糖

Java8中引入了Lambda表达式,可以简化我们的编码,使用起来非常方便。

Java8之前用Runnable通常会new一个匿名类

public class LambdaDemo {
    public static void main(String[] args) {
        //jdk 1.7
        Runnable r = new Runnable() {
            @Override
            public void run() {
                //do something...
            }
        };

        //jdk 1.8
        Runnable r2 = () -> {
            //do something...
        };
    }
}

有些人觉得Lambda仅仅是个语法糖,实际上并不是这样的。这里可以先抛出结论:普通匿名类方式的调用,编译时会生成XX$YY这类匿名类(在本例中是LambdaDemo$1),而Lambda不会生成类。在字节码层面,匿名类是通过invokespecial调用。 lambda是通过invokedymanic调用。下面我们来看看。

Read more

DAO还是Repository,傻傻的分不清?

DAO vs Repository

在Java开发中,我们经常会接触到DAO,有时,我们也能看到Repository。从代码上看,这两者似乎区别不是很大,很容易让人混淆。究竟这两个该在什么场景使用,我看网上讨论的不是很多。要想知道它们该怎么用,还是要先区分清楚它们的概念。

本文大部分内容都来自于参考资料中的文章,建议英文阅读能力好的朋友直接去看英文原文。

Read more

谈谈王者荣耀的elo匹配系统

elo原本是一套用于国际象棋的评分系统。在游戏领域,普遍用于竞技游戏的实时匹配算法。如dota、lol、王者荣耀等。

elo算法

elo_ea_impl

elo_eb_impl

上面是对弈双方胜率的计算公示,其中

RA = A玩家的积分 (在竞技游戏中,这通常对玩家不可见)
RB = B玩家的积分 (同上)

当一场游戏结束后,最多会出现三种情况。胜(1分)、平(0.5分),负(0分)。我们把胜、平、负用S表示。
RA’和RB’分别表示A、B玩家比赛结束新的积分。

Read more

Ueditor修改源码增加前端直传oss功能

本文主要的目的是为了让ueditor拥有前端直接oss上传图片的能力,不需要经过后端中转,减轻后端服务器的压力。同时添加springboot的支持。

注: 为了代码统一性和兼容性,本文所有编码方式均采用ES5。如果你觉得这没必要,可以使用ES6,甚至ES7的语法。

准备工作

  1. ueditor source code

  2. 阿里云oss服务

  3. 一丢丢Node.js和grunt的知识

Read more