1、变量的线程安全分析
1.1、成员变量和静态变量
- 如果它们没有共享,则线程安全
- 如果它们被共享了,根据状态是否改变区分
- 如果只是读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全问题
1.2、局部变量
- 局部变量是线程安全的
- 局部变量引用的对象
- 如果改对象(生命周期)处在方法的作用范围之内,线程安全
- 如果该对象逃离了方法的作用访问,需要考虑线程安全问题
2、常见线程安全类
- String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent包下的类
这里的线程安全是指,多个线程调用同一个实例的某个方法时,是线程安全的
- 它们的每个方法是原子的
- 它们多个方法的组合不是原子的
2.1、不可变类线程安全性
String、Integer等都是不可变类,因为其内部的状态不可以改变,因此他们的方法都是线程安全的。
以String类为例,replace,substring等方法不是都可以改变字符串的值吗,怎么说它是不可变的呢?
以substring为例,看下源码:
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
substring返回的要么是它自己要么是新的字符串,并不会改变字符串,其他方法同理。
3、小案例
转账,代码如下
public class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() { return this.money;}
public void setMoney(int money) { this.money = money;}
public void transfer(Account account, int amount) {
if (this.money >= amount) {
this.setMoney(this.getMoney() - amount);
account.setMoney(account.getMoney() + amount);
}
}
}
@Slf4j(topic = "security.syn.TestTransfer")
public class TestTransfer {
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 500; i++) {
a.transfer(b, randomInt());
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 500; i++) {
a.transfer(b, randomInt());
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("总金额:{}", (a.getMoney() + b.getMoney()));
}
那么总金额是不是2000呢?多次测试并不是2000,2个不同的对象,如何保证线程安全呢?
首先我们想到的是在transfer方法上加上synchronized,结果测试并不行,为什么呢?
因为方法上加synchronized,锁对象为this,它只锁住this对象,而我们的是2个对象之间并不互斥,既会存在线程安全问题。
这里不管是2个账户还是多个账户都是同一个类的对象,可以吧锁加载类对象上,改造如下:
public class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() { return this.money;}
public void setMoney(int money) { this.money = money;}
public void transfer(Account account, int amount) {
synchronized (Account.class) {
if (this.money >= amount) {
this.setMoney(this.getMoney() - amount);
account.setMoney(account.getMoney() + amount);
}
}
}
}
测试类不变。