线程安全

线程安全

线程并发

如果没有你(synchronized)

@Test
public void testUnsafeAccount() {

    // 小明拥有个不安全银行帐号(当然事先他不知道不安全),存入3000
    final UnsafeAccount account = new UnsafeAccount("小明", 3000);
    // 确认余额
    assertEquals(3000, account.getBalance());
    // 中足彩150元
    assertEquals(3150, account.deposit(150));
    // 理发20元(高上大,理发直接刷卡)
    assertEquals(3130, account.withdraw(20));
    // 再次确认余额
    assertEquals(3130, account.getBalance());

    // RP爆发,自家网店同一时刻10000个订单确认收货,每单1元(包邮)
    int threadSize = 10000;
    final long each = 1;
    Thread[] ts = new Thread[threadSize];
    for (int i = 0; i < threadSize; i++) {
        ts[i] = new Thread() {
            public void run() {
                account.deposit(each);
            }
        };
    }
    for (Thread t : ts) {
        t.start();
    }
    for (Thread t : ts) {
        try {
            t.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 确认余额
    // 余额偶尔正确,偶尔不正确,小本经营啊
    assertEquals(3130 + threadSize * each, account.getBalance());

}

UnsafeAccount.java

public class UnsafeAccount {

    private String name;
    private long balance;

    public UnsafeAccount(String name, long balance) {
        super();
        this.name = name;
        this.balance = balance;
    }

    public long deposit(long someMoney) {
        balance += someMoney;
        return balance;

    }

    public long withdraw(long someMoney) {
        balance -= someMoney;
        return balance;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @return the balance
     */
    public long getBalance() {
        return balance;
    }

}

后来,小明终于学会如何去找一间安全的银行。

@Test
public void testSafeAccount() {
    // 小明拥有个安全的银行帐号,存入3000
    final SafeAccount account = new SafeAccount("小明", 3000);
    //一样的配方,一样的流程
    ……
    //余额永远正确
}

SafeAccount.java

public class SafeAccount {
    ……
    public synchronized long deposit(long someMoney) {
        balance += someMoney;`
        return balance;

    }

    public synchronized long withdraw(long someMoney) {
        balance -= someMoney;
        return balance;
    }
    ……
}

Tell Me Why

关键字synchronized让一个线程不安全的对象变得线程安全。这里有两个疑问:

  1. 为什么原来的对象线程不安全?

    “balance += someMoney”和“balance -= someMoney”中,“balance"为共享变量,且对于java说,“+=”和“-=”并非原子操作,实际是三个独立操作,分别是A:“从主存读取balance到本地副本”、B:“修改本地副本balance”、C:“将本地副本写入主存balance”,而你永远不知道每个线程在何时运行,运行哪个操作,故原来的对象线程不安全。(详细可参考下文中的Java内存模型)

    例如:某时刻,存在线程T1,线程T2,balance=3300;

    T1、T2执行A(localT1=3300,localT2=3300),T1执行B(localT1=localT1+1),T1执行C(balance=3301),T2执行B(localT2=localT2-1),T2执行C(balance=3299),T2的本地副本已经过时。

  2. synchronized如何让原来线程不安全的对象变得线程安全?

    简单来理解,“balance += someMoney”或者“balance -= someMoney”是一个临界区(指的是一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问的特性),而synchronized的作用是序列化访问临界区的多个线程,即在同一个时刻,只能有一个线程访问临界区,其他访问临界区的线程阻塞,直到该线程离开临界区。

同步机制

Java同步块

  • Java中的同步块用synchronized标记。

  • Java同步块用来标记方法或者代码块是同步的。所有同步在一个对象上的同步块同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。

  • 同步块在Java中是同步在某个对象上。

    1. synchronized methodName1() 同步在methodName1()方法所属的实例对象
    2. synchronized (Object obj){} 同步在obj的实例对象,obj为非Class对象的实例对象,JVM中一个类只能对应一个Class对象
    3. static synchronized methodName2() 同步在methodName2所属的Class对象
    4. synchronized (ClassName.class){} 同步在ClassName对应的Class对象

Java内存模型(JMM:Java Memeory Model)(待续)

参考:

[1]http://ifeve.com/synchronized-blocks/

[2]http://www.blogjava.net/xylz/category/45607.html

你可能感兴趣的:(java,java并发,java内存模型,java线程安全,Java同步机制)