Code, Food & Music


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

Synchronized和Reentrantlock的区别

发表于 2016-09-01

ReentrantLock和Synchronized具有相同的行为与语义, 并且有更强大的功能, 如下

  1. 支持非块结构的锁, 如下这样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    private ReentrantLock lock;

    public void foo() {
    ...
    lock.lock();
    ...
    }

    public void bar() {
    ...
    lock.unlock();
    ...
    }
  2. 支持公平与非公平锁

公平锁是线程按照FIFO的顺序来获取锁, 而非公平锁是倾向于将访问权授予等待时间最长的线程, 两者相比使用非公平锁一般具有更高的总体吞吐量

  1. 支持lock polling, 即trylock()方法, 利用该方法可以实现无锁同步, 如

    1
    2
    3
    4
    5
    while(!lock.trylock()) {
    doNothing;
    }

    doRealThing;
  2. 支持可中断锁等候(应用场景?)

  3. 支持多个条件

    1
    2
    3
    Lock lock = new ReentrantLock();
    Condition con1 = lock.newCondition();
    Condition con2 = lock.newCondition();

什么时候使用Synchronzied和ReentrantLock呢?

一般使用Synchronized就可以了, 因为Lock需要显示的去释放锁(利用finally), 这可能忘记, 而Synchronzied则由JVM来做这件事。除非要使用更高级的特性, 否则使用Synchronzied就可以了。

参考资源

  1. Why use a ReentrantLock if one can use synchronized(this)?
  2. Java theory and practice: More flexible, scalable locking in JDK 5.0

ThreadLocal原理

发表于 2016-09-01

首先要了解ThreadLocal的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main { 

private static ThreadLocal<Integer> value = new ThreadLocal<Integer>();

public static void main(String[] args) throws InterruptedException{
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0; i <= 10; i++) {
exec.execute(new Runnable() {
@Override
public void run() {
value.set(5);
System.out.println(value.get());
}
});
}
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow();
}
}

问题

  1. ThreadLocal为什么一般要声明为static的
  2. ThreadLocal是如何实现的。

1. ThreadLocal为什么一般要声明为static的

这和ThreadLocal的语义有关

ThreadLocal指的是一个变量属于一个Thread

而如果是实例成员的话, 语义则变为, 一个变量不仅仅属于一个线程, 还属于一个对象,

因为要求只属于某一个线程, 所以最好指定为static的, 但即使是static的, 也并不是只属于某一线程, 还属于某一class

2. ThreadLocal是如何实现的

线程在Java中也是一个对象, 对象的实例成员是属于对象的, 所以, 如果将一个变量保存成线程的实例成员就可以实现线程本地变量。实际上也是如此

  1. Thread有一个ThreadLocalMap成员用来保存线程本地变量
  2. ThreadLocalMap是一个Map, Key是ThreadLocal, Value是ThreadLocal中保存的值
  3. 当调用ThreadLocal的set方法时, 实际上是先得到ThreadLocalMap对象, 再以该ThreadLocal为key, 以value为值保存到ThreadLocalMap中去。

    1
    2
    3
    4
    5
    6
    7
    8
    public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
    map.set(this, value);
    else
    createMap(t, value);
    }
  4. 当调用ThreadLocal的get方法时, 实际上是先得到ThreadLocalMap对象, 再以该ThreadLocal为key, 去取值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null)
    return (T)e.value;
    }
    return setInitialValue();
    }

参考

  1. 理解Java中的ThreadLocal
  2. Why should Java ThreadLocal variables be static

finally与return执行的时机

发表于 2016-09-01

finally不一定执行的情况

  1. JVM退出, 即在try catch中有System.exit()
  2. 在执行try catch时线程中断

finally一定执行的情况(以下说法并不准确, 只是为了方便理解, 还需要去了解生成的字节码是怎么样的才行)

  1. 首先要将try catch当做一个整体, 当try catch中有return语句时, 则该整体有一个返回值, 当没有return语句时, 该整体没有返回值。这里try catch类似于一个函数 , 函数可以有返回值,也可以没有返回值
  2. finally执行的时机:finally可以看作是一个subroutine,在try catch退出前, 即如果当try catch当做一个函数的话, 就是在该函数退出前,执行的,try catch中的变量是作为finally这个subroutine的参数复制了一份,传给了finally。
  3. finally执行的行为:finally是做为一个subroutine进行执行的, 所以, 如果finally中修改了try catch中的变量, 那么修改的是一个副本(注意基本类型与引用的不同)
  4. return的时机有三种, 一是在try catch中return, 二是在finally中return, 三是在try catch finally外返回。 对于一,因为finally中修改的是副本, 所以,不影响返回值(对于引用还是影响的),且该返回值做为整个函数的返回值, 对于二, finally的返回值作为整个函数的返回值, 所以如果try catch和finally中都有return , 则finally中的会覆盖try catch的, 对于三, try catch中和finally都没有return , 如果try catch finally都修改了某一个变量, 那么因为finally是最终修改的, 所以以finally的结果为准。

例子, 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//返回4
public static int getValue() {
int i = 1;
try {
i = 4;
return i;
} finally{
i++;
}
}

//返回5
public static int getValue() {
int i = 1;
try {
i = 4;

} finally{
i++;
return i;
}
}

//返回5
public static int getValue() {
int i = 1;
try {
i = 4;
} finally{
i++;
}
return i;
}

以上用法只是为了说明finally与return的关系, finally最大的作用是用来做资源回收(C++中析构函数工作的一部分)

更多例子与解释

  • https://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
  • https://www.ibm.com/developerworks/cn/java/j-lo-finally/
  • http://www.cnblogs.com/lanxuezaipiao/p/3440471.html

后记

其实也不需要对finally对return的影响那么了解。最关键的是了解使用finally的目的是什么,要有正确的实践。而有些例子则是很不好的实践, 应该尽量避免(这使得代码很不容易理解)

使用finally的主要目的是回收,关闭资源, 并不是让其返回某个值的。

同时, 在try语句中, 尽量不要使用System.exit()这样会导致finally中的语句不被执行

澄清数据库一致性概念

发表于 2016-08-24

0 前言

最近在和同学讨论分布式事务以及分布式一致性时产生了分歧. 我的疑问是

2PC这种算法有没有保证数据的一致性呢? 如果保证了保证的是什么样的一致性呢?

而同学的回答是

2PC是一种事务提交协议, 是用来保证分布式事务的原子性的, 和数据的一致性没有关系

这样的回答并不能认我满意, 我不知道自己为什么会认为2PC与数据的一致性有关系, 我总感觉它们之间是有关系的.

而同学也说不清为什么两者之间没有关系.

冷静下来想一想, 发现, 其实两个人对数据的一致性这个概念的理解是片面和模糊的.

本文的目的是澄清这个概念, 进而开头的问题也可以做出回答了.

1 一致性是什么?

一致性这个概念在不同的上下文中含义是不同的, 这里只是说明在数据库这个上下文中的含义, 再细分一下, 就是单机数据库与分布式数据库上下文中的含义

2 单机数据库下一致性的含义

在单机数据库下, 事务有ACID的性质. 其中C就是一致性, 而这里说的一致性, 有两种含义

原子一致性: 拿最经典的转账举例, 一个事务是A向B转100块钱, 事务的原子性保证了要么全部执行, 要么全部不执行, 不存在A扣了钱, B没有加钱的情况. 这里, 谈讨原子一致性时有个前提, 即只有一个事务, 当涉及到并发时, 仅仅有原子性是无法保证一致性的.

并发一致性: 当有并发事务的情况下, 事务之间的操作可能产生互相影响, 如两个事务同时修改一份数据, 可能导致一个事务的修改覆盖了另一个事务的修改. 事务的隔离性保证了并发的一致性, 但事务有四种隔离级别, 而不同隔离级别的选择对一致性的保证程度是不同的, 如Read Uncommited下, 一个事务可能读到另一个事务还未提交的数据.

所以, 谈到一致性时, 需要明确一下, 是指的原子一致性, 还是并发一致性.

3 分布式数据库下一致性的含义

在说分布式数据库下一致性含义时, 先解释一下为什么需要分布式数据库. 主要有两点考虑

  1. 性能问题: 一台数据库服务器性能不行了, 需要多台
  2. 容灾问题: 防止一台数据库挂了之后不能提供服务了.

针对以上两个问题, 可以使用三种思想来处理

  1. 数据分区: 不同的数据放在不同的服务器上, 这样可以解决性能问题
  2. 数据镜像: 同一数据有多个备份
  3. 数据分区与数据分区结合使用

而无论是数据分区还是数据镜像, 在新的环境下, 会产生新的一致性问题

还是以A向B转账为例.

对于数据分区来说, 如果A, B的数据放在两台不同服务器上, 即一个事务操作涉及多台服务器的修改, 这时就需要有一种算法来保证要不两台服务器上的数据都正确修改了, 要不都没有修改.

对于数据镜像来说, 如果一台服务器发生了修改, 这种修改也要想办法同步到其它镜像上, 否则镜像上的数据就不一致了.

上面提出了三种思想, 而基于这三种思想可以衍生出很多很多的具体的实施方案, 由以上讨论可知, 不同的实施方案都要去解决一致性问题. 只是不同方案的难度不一样(这里忽略了性能问题, 性能也是一个很重要的权衡因素), 而以下文章中讨论了几种方案, 以及这几种方案是如何解决一致性问题的.

分布式系统的事务处理

以上面讨论知道, 单机情况下可以分为原子一致性和并发一致性, 原子一致性由原子性来解决, 并发一致性由隔离性来解决. 那么文章中解决的一致性又是哪一种一致性呢?

其实, 文章中讨论一致性时只是假设只有一个事务, 比如对于Master-slave来说, 对Master的修改如何保证其它slave上的修改与回滚, 对于2PC来说, 如何保证在多个服务器上执行的Global Transaction要么全部执行, 要么全部不执行. 这其实类似于实现多机情况下的原子一致性. 而对于分布式存储下的并发事务可能导致的一致性问题并没有涉及.(TODO)

至此也可以回答开头的那么问题了

2PC这种算法有没有保证数据的一致性呢? 如果保证了保证的是什么样的一致性呢?

2PC算法只是保证了分布式事务的原子性, 可以说是保证了原子一致性, 和并发一致性无关, 所以算是部分保证了, 当然它和一致性也是有关系了.

4 分布式数据库的方案设计

自己感觉当要使用分布式数据库时, 到底使用哪一种方案, 需要结合具体的业务要求, 考虑如下几点

  1. 对性能的要求
  2. 对可用度的要求
  3. 对一致性的要求

如银行系统可能对一致性要求很高,性能要求不是那么高, 而对于互联网业务, 可能对一致性要求低, 但性能要求比较高.

如果现有的方案无法满足, 就需要自己去创造新方案了.

5 CAP原理与分布式存储的关系

CAP原理是说明了分布式存储的一个客观性质. 这里的C指的是强一致性, 对于分布式存储P是一定有的, 所以就在CA之间二选一, 而为了可用性, 一般选择A, 但这里并不是放弃了强一致性, 可以选择最终一致性, 使得经过一个时间窗口后, 数据回到正确的状态.

参考

  1. 数据库事务原子性、一致性是怎样实现的?
  2. 分布式系统的事务处理
  3. 谈谈对CAP定理的理解

Netty Handler CTX & Pipeline UML

发表于 2016-08-16

alt

12…17

asanelder

84 日志
9 标签
GitHub Instagram
© 2019 asanelder
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4