STM

来源:互联网 发布:卡巴斯基软件是什么 编辑:程序博客网 时间:2024/04/24 21:00

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;">INN0 first1 first2 first3 first4 first5 first6 first7 first10087</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 first5 first4 first1 first0 first7 first2 first3 first7 retry 14 retry 11 retry 15 retry 12 retry 16 retry 13 retry 11008182</span>
最然最终的结果也是正确的,但是总过消耗了182ms,与之前的7ms已经不在一个数量级了。日志中也明显可以看到基本上每一个线程都有重试。

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

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

<span style="font-size:14px;">INN0 first36 first32 first28 first24 first20 first16 first12 first8 first37 first33 first29 first25 first21 first17 first13 first9 first39 first35 first31 first27 first23 first19 first15 first11 first38 first34 first30 first26 first22 first18 first14 first10 first6 first7 first5 first4 first2 first3 first1 first10401836</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 first34 first22 first16 first14 first2 first25 first3 first26 first32 first36 first5 first20 first10 first23 first33 first6 first0 first7 first37 first4 first8 first1 first19 first28 first15 first12 first31 first21 first18 first27 first24 first9 first39 first35 first30 first17 first11 first13 first29 first33 retry 114 retry 124 retry 120 retry 116 retry 10 retry 132 retry 130 retry 110 retry 14 retry 18 retry 113 retry 129 retry 136 retry 16 retry 113 retry 225 retry 122 retry 11 retry 130 retry 237 retry 18 retry 236 retry 232 retry 26 retry 228 retry 14 retry 25 retry 17 retry 122 retry 212 retry 127 retry 121 retry 130 retry 39 retry 135 retry 13 retry 117 retry 129 retry 226 retry 131 retry 118 retry 139 retry 133 retry 223 retry 12 retry 137 retry 238 retry 122 retry 325 retry 219 retry 115 retry 11 retry 211 retry 136 retry 330 retry 432 retry 314 retry 25 retry 224 retry 220 retry 221 retry 216 retry 29 retry 24 retry 317 retry 28 retry 30 retry 229 retry 318 retry 228 retry 22 retry 238 retry 227 retry 27 retry 233 retry 312 retry 222 retry 435 retry 23 retry 230 retry 514 retry 331 retry 225 retry 339 retry 21 retry 323 retry 237 retry 32 retry 338 retry 319 retry 222 retry 515 retry 224 retry 35 retry 320 retry 311 retry 216 retry 321 retry 39 retry 317 retry 30 retry 330 retry 636 retry 414 retry 429 retry 428 retry 332 retry 47 retry 312 retry 333 retry 438 retry 44 retry 430 retry 725 retry 48 retry 414 retry 527 retry 322 retry 61 retry 435 retry 33 retry 331 retry 339 retry 35 retry 423 retry 322 retry 737 retry 430 retry 821 retry 414 retry 69 retry 417 retry 432 retry 529 retry 528 retry 419 retry 336 retry 515 retry 328 retry 525 retry 511 retry 317 retry 51 retry 517 retry 63 retry 421 retry 55 retry 514 retry 724 retry 422 retry 820 retry 433 retry 516 retry 44 retry 58 retry 50 retry 436 retry 612 retry 437 retry 517 retry 727 retry 49 retry 535 retry 425 retry 622 retry 97 retry 414 retry 816 retry 531 retry 420 retry 539 retry 423 retry 45 retry 617 retry 80 retry 533 retry 612 retry 54 retry 614 retry 932 retry 68 retry 636 retry 716 retry 619 retry 437 retry 615 retry 421 retry 69 retry 611 retry 425 retry 717 retry 916 retry 73 retry 517 retry 1020 retry 65 retry 74 retry 78 retry 70 retry 633 retry 717 retry 1121 retry 712 retry 69 retry 736 retry 837 retry 725 retry 827 retry 520 retry 733 retry 87 retry 55 retry 80 retry 79 retry 837 retry 831 retry 512 retry 739 retry 54 retry 85 retry 923 retry 58 retry 837 retry 935 retry 521 retry 837 retry 1027 retry 632 retry 719 retry 515 retry 527 retry 711 retry 531 retry 620 retry 83 retry 631 retry 74 retry 98 retry 90 retry 831 retry 821 retry 912 retry 819 retry 635 retry 67 retry 619 retry 739 retry 611 retry 619 retry 823 retry 620 retry 932 retry 819 retry 915 retry 60 retry 939 retry 739 retry 815 retry 712 retry 97 retry 74 retry 1023 retry 78 retry 1032 retry 915 retry 87 retry 821 retry 107 retry 923 retry 832 retry 1032 retry 1123 retry 920 retry 1021 retry 114 retry 1123 retry 108 retry 110 retry 1012 retry 1021 retry 1223 retry 114 retry 1221 retry 1320 retry 110 retry 1112 retry 114 retry 1321 retry 140 retry 1221 retry 1520 retry 1212 retry 120 retry 1320 retry 1312 retry 1320 retry 1412 retry 1412 retry 1510408708</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;">INN0 first7 first6 first5 first4 first3 first2 first1 first1001366</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 first3 first2 first4 first6 first1 first0 first7 first1001336</span>

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

<span style="font-size:14px;">INN1 first36 first32 first28 first24 first20 first16 first12 first8 first4 first0 first37 first33 first29 first25 first27 first21 first17 first13 first9 first38 first39 first34 first30 first35 first31 first26 first22 first23 first5 first18 first19 first14 first10 first6 first15 first2 first11 first7 first3 first10011835</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 first21 first34 first14 first25 first30 first38 first0 first4 first28 first5 first33 first3 first37 first27 first29 first20 first19 first7 first39 first1 first23 first31 first8 first35 first11 first36 first32 first15 first16 first2 first12 first17 first13 first9 first6 first18 first26 first10 first22 first10011147</span>

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

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

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


参考资料:

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

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

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


未完待续。

0 0
原创粉丝点击