STM

STM全称是soft transaction memory,是并发编成的模型之一。与传统的基于锁的并发模型不同的地方是,STM各个线程都是相互独立的,它假设各个线程不受其他线程的影响,也就是各个线程在访问并发区域的时候互不干扰,可以实现高效的并发。问题来了,在java中各个线程都有自己的一份独立副本,在线程中进行操作的时候首先访问副本数据,当线程结束时将副本重新写入到主存中,为了保证线程之间并发安全与可见性,往往需要使用内置锁来保证(happens before)。如果使用STM如何保证线程安全呢?STM将线程的任务封装到transaction(事务)中,当事务结束的时候,会进行commit(操作),在这一步的时候,STM会检测其访问的并发区域是否已经被其他线程所修改,如果已经修改了,则该事务回滚,重新执行该事务。需要注意的是,在commit之前的所有写操作都是对并发区域的一个副本的操作,也就是说实际上写的数据并不是原始的数据,只有当commit成功的时候,才会将数据写入。

下面拿java中的内置锁,和STM进行比较说明。

1)对于读操作较多的并发应用,STM的优势较为明显。因为STM不需要进行互斥操作,保证了并发度,同时由于写操作较少,事务回滚的概率也较低,所以保证了STM的效率要高于内置锁的效率。

2)对于写操作较多的并发应用,STM的优势就没有那么明显,在一些场景下STM的效率反而远远不如内置锁。因为频繁的回滚重试,将导致大量的CPU时间被消耗,所以总的时间也就上去了。

3)对于编程难度来说,STM也在一定程度上降低了并发的难度。维基百科上面的一段话:

In contrast, the concept of a memory transaction is much simpler, because each transaction can be viewed in isolation as a single-threaded computation. Deadlock and livelock are either prevented entirely or handled by an external transaction manager; the programmer needs hardly worry about it. Priority inversion can still be an issue, but high-priority transactions can abort conflicting lower priority transactions that have not already committed.

大致意思就是说,STM避免了死锁和活锁的出现,即使出现了也会被二外的事务管理器处理掉,开发者可以完全不用担心这个情况。


下面通过几个例子来实践一下STM

准备工作,建立MAVEN工程,加入如下的包依赖,

<span style="font-size:14px;">		<dependency>
			<groupId>org.multiverse</groupId>
			<artifactId>multiverse-core</artifactId>
			<version>0.7.0</version>
		</dependency></span>

1)写操作为主

内置锁实现,

<span style="font-size:14px;">import java.util.Date;

public class InnerLockTest {

    private Long balance;

    private Object lock = new Object();
    public InnerLockTest(Long balance) {
        this.balance = balance;
    }

    public void incBalance(final int amount, final Date date, final int j) {
        new Thread(new Runnable() {
            int count = 0;
            int idx = j;
            public void run() {
                synchronized (lock) { // 内置锁
                    if (count == 0) {
                        System.out.println(idx + " first"); 
                    } else {
                        System.out.println(idx + " retry " + count);
                    }
                    count++;
                    
//                    long i;
//                    for (i = 0; i < Integer.MAX_VALUE  / 20; i++) {
//                    }
                    
                    balance += amount;
                }
            }
        }).start();
    }

    public void print() {
        System.out.println(balance);
    }

    public static void main(String[] args) {
        System.out.println("INN");
        final long start = System.currentTimeMillis();
        int process = Runtime.getRuntime().availableProcessors();// 获取cpu数量
        final InnerLockTest innerLockTest = new InnerLockTest((long) 1000); //初始化账户
        for (int i = 0; i < process * 2; i++) {//启动一定数量的线程对账户进行操作哦
            innerLockTest.incBalance(1, new Date(), i);
        }
        
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { // 增加shutdown hook,在所有线程结束后,主线程结束前将账户值打印
            public void run() {
                innerLockTest.print();
                System.out.println(System.currentTimeMillis() - start);
            }
        }));
        
    }
}
</span>

运行后可以看到如下效果,

<span style="font-size:14px;">INN
0 first
1 first
2 first
3 first
4 first
5 first
6 first
7 first
1008
7</span>
可以看到最终的结果为1008,是正确的,同时整个运行过程只有7ms,运行十分的快。那么下面来看看STM的表现,


<span style="font-size:14px;">import java.util.Date;

import org.multiverse.api.TxnFactory;
import org.multiverse.api.TxnFactoryBuilder;
import org.multiverse.api.references.*;

import static org.multiverse.api.StmUtils.*;

public class MultiverseTest {

    private final TxnLong balance;//需要注意的是在事务中,java原生的类型都有其包装类型。这些类型只能在事务中进行访问。

    public MultiverseTest(int balance) {
        this.balance = newTxnLong(balance);
    }

    public void incBalance(final int amount, final Date date, final int j) {
        new Thread(new Runnable() {
            public void run() {
                atomic(new Runnable() { //atomic将该操作原子化,事务开始执行
                    int count = 0;
                    int idx = j;
                    public void run() {
                        if (count == 0) {
                            System.out.println(idx + " first"); 
                        } else {
                            System.out.println(idx + " retry " + count); // 记录重试的次数
                        }
                        count++;
                        
                        if (balance.get() < 0) {
                            throw new IllegalStateException("Not enough money");
                        }
                        
//                        long i;
//                        for (i = 0; i < Integer.MAX_VALUE / 20; i++) {
//                        }
                        
                        balance.increment(amount);
                        
                    }
                });                
            }
        }).start();

    }
    
    public void print() {
        atomic(new Runnable() {
            
            public void run() {
                System.out.println(balance.get());
            }
        });
    }
    
    public static void main(String[] args) {
        System.out.println("MUL");
        final long start = System.currentTimeMillis();
        int process = Runtime.getRuntime().availableProcessors();
        final MultiverseTest multiverseTest = new MultiverseTest(1000);
        for(int i = 0; i < process * 2; i++) {
            multiverseTest.incBalance(1, new Date(), i);
        }
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            
            public void run() {
                multiverseTest.print();
                System.out.println(System.currentTimeMillis() - start);
            }
        }));
    }
}
</span>

运行后,可以看到如下效果,

<span style="font-size:14px;">MUL
三月 23, 2015 11:20:18 下午 org.multiverse.api.GlobalStmInstance <clinit>
信息: Initializing GlobalStmInstance using factoryMethod 'org.multiverse.stms.gamma.GammaStm.createFast'.
三月 23, 2015 11:20:18 下午 org.multiverse.api.GlobalStmInstance <clinit>
信息: Successfully initialized GlobalStmInstance using factoryMethod 'org.multiverse.stms.gamma.GammaStm.createFast'.
6 first
5 first
4 first
1 first
0 first
7 first
2 first
3 first
7 retry 1
4 retry 1
1 retry 1
5 retry 1
2 retry 1
6 retry 1
3 retry 1
1008
182
</span>
最然最终的结果也是正确的,但是总过消耗了182ms,与之前的7ms已经不在一个数量级了。日志中也明显可以看到基本上每一个线程都有重试。

如果这个效果不够明显,那么将上面两段代码的线程数继续扩大到process * 10,

前者和后者运行结果分别是,

<span style="font-size:14px;">INN
0 first
36 first
32 first
28 first
24 first
20 first
16 first
12 first
8 first
37 first
33 first
29 first
25 first
21 first
17 first
13 first
9 first
39 first
35 first
31 first
27 first
23 first
19 first
15 first
11 first
38 first
34 first
30 first
26 first
22 first
18 first
14 first
10 first
6 first
7 first
5 first
4 first
2 first
3 first
1 first
1040
1836
</span>


<span style="font-size:14px;">MUL
三月 23, 2015 11:27:14 下午 org.multiverse.api.GlobalStmInstance <clinit>
信息: Initializing GlobalStmInstance using factoryMethod 'org.multiverse.stms.gamma.GammaStm.createFast'.
三月 23, 2015 11:27:14 下午 org.multiverse.api.GlobalStmInstance <clinit>
信息: Successfully initialized GlobalStmInstance using factoryMethod 'org.multiverse.stms.gamma.GammaStm.createFast'.
38 first
34 first
22 first
16 first
14 first
2 first
25 first
3 first
26 first
32 first
36 first
5 first
20 first
10 first
23 first
33 first
6 first
0 first
7 first
37 first
4 first
8 first
1 first
19 first
28 first
15 first
12 first
31 first
21 first
18 first
27 first
24 first
9 first
39 first
35 first
30 first
17 first
11 first
13 first
29 first
33 retry 1
14 retry 1
24 retry 1
20 retry 1
16 retry 1
0 retry 1
32 retry 1
30 retry 1
10 retry 1
4 retry 1
8 retry 1
13 retry 1
29 retry 1
36 retry 1
6 retry 1
13 retry 2
25 retry 1
22 retry 1
1 retry 1
30 retry 2
37 retry 1
8 retry 2
36 retry 2
32 retry 2
6 retry 2
28 retry 1
4 retry 2
5 retry 1
7 retry 1
22 retry 2
12 retry 1
27 retry 1
21 retry 1
30 retry 3
9 retry 1
35 retry 1
3 retry 1
17 retry 1
29 retry 2
26 retry 1
31 retry 1
18 retry 1
39 retry 1
33 retry 2
23 retry 1
2 retry 1
37 retry 2
38 retry 1
22 retry 3
25 retry 2
19 retry 1
15 retry 1
1 retry 2
11 retry 1
36 retry 3
30 retry 4
32 retry 3
14 retry 2
5 retry 2
24 retry 2
20 retry 2
21 retry 2
16 retry 2
9 retry 2
4 retry 3
17 retry 2
8 retry 3
0 retry 2
29 retry 3
18 retry 2
28 retry 2
2 retry 2
38 retry 2
27 retry 2
7 retry 2
33 retry 3
12 retry 2
22 retry 4
35 retry 2
3 retry 2
30 retry 5
14 retry 3
31 retry 2
25 retry 3
39 retry 2
1 retry 3
23 retry 2
37 retry 3
2 retry 3
38 retry 3
19 retry 2
22 retry 5
15 retry 2
24 retry 3
5 retry 3
20 retry 3
11 retry 2
16 retry 3
21 retry 3
9 retry 3
17 retry 3
0 retry 3
30 retry 6
36 retry 4
14 retry 4
29 retry 4
28 retry 3
32 retry 4
7 retry 3
12 retry 3
33 retry 4
38 retry 4
4 retry 4
30 retry 7
25 retry 4
8 retry 4
14 retry 5
27 retry 3
22 retry 6
1 retry 4
35 retry 3
3 retry 3
31 retry 3
39 retry 3
5 retry 4
23 retry 3
22 retry 7
37 retry 4
30 retry 8
21 retry 4
14 retry 6
9 retry 4
17 retry 4
32 retry 5
29 retry 5
28 retry 4
19 retry 3
36 retry 5
15 retry 3
28 retry 5
25 retry 5
11 retry 3
17 retry 5
1 retry 5
17 retry 6
3 retry 4
21 retry 5
5 retry 5
14 retry 7
24 retry 4
22 retry 8
20 retry 4
33 retry 5
16 retry 4
4 retry 5
8 retry 5
0 retry 4
36 retry 6
12 retry 4
37 retry 5
17 retry 7
27 retry 4
9 retry 5
35 retry 4
25 retry 6
22 retry 9
7 retry 4
14 retry 8
16 retry 5
31 retry 4
20 retry 5
39 retry 4
23 retry 4
5 retry 6
17 retry 8
0 retry 5
33 retry 6
12 retry 5
4 retry 6
14 retry 9
32 retry 6
8 retry 6
36 retry 7
16 retry 6
19 retry 4
37 retry 6
15 retry 4
21 retry 6
9 retry 6
11 retry 4
25 retry 7
17 retry 9
16 retry 7
3 retry 5
17 retry 10
20 retry 6
5 retry 7
4 retry 7
8 retry 7
0 retry 6
33 retry 7
17 retry 11
21 retry 7
12 retry 6
9 retry 7
36 retry 8
37 retry 7
25 retry 8
27 retry 5
20 retry 7
33 retry 8
7 retry 5
5 retry 8
0 retry 7
9 retry 8
37 retry 8
31 retry 5
12 retry 7
39 retry 5
4 retry 8
5 retry 9
23 retry 5
8 retry 8
37 retry 9
35 retry 5
21 retry 8
37 retry 10
27 retry 6
32 retry 7
19 retry 5
15 retry 5
27 retry 7
11 retry 5
31 retry 6
20 retry 8
3 retry 6
31 retry 7
4 retry 9
8 retry 9
0 retry 8
31 retry 8
21 retry 9
12 retry 8
19 retry 6
35 retry 6
7 retry 6
19 retry 7
39 retry 6
11 retry 6
19 retry 8
23 retry 6
20 retry 9
32 retry 8
19 retry 9
15 retry 6
0 retry 9
39 retry 7
39 retry 8
15 retry 7
12 retry 9
7 retry 7
4 retry 10
23 retry 7
8 retry 10
32 retry 9
15 retry 8
7 retry 8
21 retry 10
7 retry 9
23 retry 8
32 retry 10
32 retry 11
23 retry 9
20 retry 10
21 retry 11
4 retry 11
23 retry 10
8 retry 11
0 retry 10
12 retry 10
21 retry 12
23 retry 11
4 retry 12
21 retry 13
20 retry 11
0 retry 11
12 retry 11
4 retry 13
21 retry 14
0 retry 12
21 retry 15
20 retry 12
12 retry 12
0 retry 13
20 retry 13
12 retry 13
20 retry 14
12 retry 14
12 retry 15
1040
8708
</span>

可以看到STM对于某些事务重试的次数甚至达到了15次,虽然最终的结果也是正确的。


#在写竞争较多的情况下,STM的性能似乎不如内置锁。


2)读操作为主

内置锁,

<span style="font-size:14px;">import java.util.Date;

public class InnerLockTest {

    private Long balance;

    private Object lock = new Object();
    public InnerLockTest(Long balance) {
        this.balance = balance;
    }

    public void incBalance(final int amount, final Date date, final int j) {
        new Thread(new Runnable() {
            int count = 0;
            int idx = j;
            public void run() {
                synchronized (lock) {
                    if (count == 0) {
                        System.out.println(idx + " first"); 
                    } else {
                        System.out.println(idx + " retry " + count);
                    }
                    count++;
                    
                    long i;
                    for (i = 0; i < Integer.MAX_VALUE  / 20; i++) {
                    }
                    
                    if (j == 4) {
                        balance += amount;
                    }
                }
            }
        }).start();
    }

    public void print() {
        System.out.println(balance);
    }

    public static void main(String[] args) {
        System.out.println("INN");
        final long start = System.currentTimeMillis();
        int process = Runtime.getRuntime().availableProcessors();
        final InnerLockTest innerLockTest = new InnerLockTest((long) 1000);
        for (int i = 0; i < process * 2; i++) {
            innerLockTest.incBalance(1, new Date(), i);
        }
        
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                innerLockTest.print();
                System.out.println(System.currentTimeMillis() - start);
            }
        }));
        
    }
}
</span>


STM,

<span style="font-size:14px;">import java.util.Date;

import org.multiverse.api.TxnFactory;
import org.multiverse.api.TxnFactoryBuilder;
import org.multiverse.api.references.*;

import static org.multiverse.api.StmUtils.*;

public class MultiverseTest {

    private final TxnLong balance;

    public MultiverseTest(int balance) {
        this.balance = newTxnLong(balance);
    }

    public void incBalance(final int amount, final Date date, final int j) {
        new Thread(new Runnable() {
            public void run() {
                atomic(new Runnable() {
                    int count = 0;
                    int idx = j;
                    public void run() {
                        if (count == 0) {
                            System.out.println(idx + " first"); 
                        } else {
                            System.out.println(idx + " retry " + count);
                        }
                        count++;
                        
                        if (balance.get() < 0) {
                            throw new IllegalStateException("Not enough money");
                        }
                        
                        long i;
                        for (i = 0; i < Integer.MAX_VALUE / 20; i++) {
                        }
                        
                        if (j == 4) {
                            balance.increment(amount);
                        }
                    }
                });                
            }
        }).start();

    }
    
    public void print() {
        atomic(new Runnable() {
            
            public void run() {
                System.out.println(balance.get());
            }
        });
    }
    
    public static void main(String[] args) {
        System.out.println("MUL");
        final long start = System.currentTimeMillis();
        int process = Runtime.getRuntime().availableProcessors();
        final MultiverseTest multiverseTest = new MultiverseTest(1000);
        for(int i = 0; i < process * 2; i++) {
            multiverseTest.incBalance(1, new Date(), i);
        }
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            
            public void run() {
                multiverseTest.print();
                System.out.println(System.currentTimeMillis() - start);
            }
        }));
    }
}
</span>
分别运行后,结果如下所示,

<span style="font-size:14px;">INN
0 first
7 first
6 first
5 first
4 first
3 first
2 first
1 first
1001
366
</span>

<span style="font-size:14px;">MUL
三月 23, 2015 11:35:07 下午 org.multiverse.api.GlobalStmInstance <clinit>
信息: Initializing GlobalStmInstance using factoryMethod 'org.multiverse.stms.gamma.GammaStm.createFast'.
三月 23, 2015 11:35:07 下午 org.multiverse.api.GlobalStmInstance <clinit>
信息: Successfully initialized GlobalStmInstance using factoryMethod 'org.multiverse.stms.gamma.GammaStm.createFast'.
5 first
3 first
2 first
4 first
6 first
1 first
0 first
7 first
1001
336
</span>

对于线程数较少的情况下,二者性能持平,那么我们增加线程会出现什么情况呢?将线程数提升至上面的process * 10,

<span style="font-size:14px;">INN
1 first
36 first
32 first
28 first
24 first
20 first
16 first
12 first
8 first
4 first
0 first
37 first
33 first
29 first
25 first
27 first
21 first
17 first
13 first
9 first
38 first
39 first
34 first
30 first
35 first
31 first
26 first
22 first
23 first
5 first
18 first
19 first
14 first
10 first
6 first
15 first
2 first
11 first
7 first
3 first
1001
1835
</span>

<span style="font-size:14px;">MUL
三月 23, 2015 11:36:45 下午 org.multiverse.api.GlobalStmInstance <clinit>
信息: Initializing GlobalStmInstance using factoryMethod 'org.multiverse.stms.gamma.GammaStm.createFast'.
三月 23, 2015 11:36:45 下午 org.multiverse.api.GlobalStmInstance <clinit>
信息: Successfully initialized GlobalStmInstance using factoryMethod 'org.multiverse.stms.gamma.GammaStm.createFast'.
24 first
21 first
34 first
14 first
25 first
30 first
38 first
0 first
4 first
28 first
5 first
33 first
3 first
37 first
27 first
29 first
20 first
19 first
7 first
39 first
1 first
23 first
31 first
8 first
35 first
11 first
36 first
32 first
15 first
16 first
2 first
12 first
17 first
13 first
9 first
6 first
18 first
26 first
10 first
22 first
1001
1147
</span>

可见相对于线程较少的情况,当线程数较多的时候,STM对于性能的提升更为明显。

 process * 10process *20process *50process*100
内置锁18353612907920038
STM11472125527910358

当然如何在你的应用中如何选择并发模型,还是需要根据实际的业务场景来进行选择,有时必要的性能测试也是必须的。


参考资料:

http://en.wikipedia.org/wiki/Software_transactional_memory

java虚拟机并发编程(programming concurrency on the jvm)

http://multiverse.codehaus.org/overview.html


未完待续。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值