多线程安全
类的线程安全定义
类在多线程环境下运行,始终保持正确的行为,就是线程安全的。
怎么才能做到类的线程安全?
- 栈封闭
简单的说,就是把变量定义在方法里面,因为方法是线程私有的。
- 无状态类
线程不安全主要是因为资源竞争,如果类没有属性,就不会有线程安全的问题。
- 不发布修改属性的方法
定义类属性的时候私有化,并且不发布修改属性的方法,在类外部就不能修改属性,就可以避免线程安全的问题
- 加锁
synchronized内置锁,或者lock。
- cas或者线程安全的容器
用cas类,例如AtomicInteger;或者concurrent包的容器。
- ThreadLocal
threadlocal是保存线程变量副本,每个线程都会保存变量副本。
死锁
资源数量大于1,请求资源的线程大于等于资源数,核心原因就是:请求资源的顺序不一致。
- 检测死锁的方法
- jvm命令
jps -l : 查看运行的应用id
jstack #ID: 查看运行应用的线程以及子线程信息,会打印线程持有的资源信息以及请求的资源信息,如果有死锁会检测到。
2.jconsole监控工具或者jprofile
- 简单的
同时创建两个线程,一个调用a方法,一个调用b方法,就会发生死锁。
public void a(){
Synchronized(A){
Synchronized(B){
}
}
}
public void b(){
Synchronized(B){
Synchronized(A){
}
}
}
- 动态的
转账问题
- 线程不安全的代码示例
/**
* @Auther: allen
* @Date: 2019/8/22 21:00
* @Description: 用户类
*/
public class UserAccount {
//姓名
private String name;
//账户余额
private int account;
/**
* 账户锁
*/
private final Lock lock = new ReentrantLock();
public UserAccount(String name, int account) {
this.name = name;
this.account = account;
}
/**
* 增加账户月
* @param money
*/
public void addAccount(int money){
account = account + money;
}
/**
* 减少账户余额
* @param money
* @throws Exception
*/
public void subAccount(int money) throws Exception{
if( money > account ){
throw new Exception("账户余额不足!");
}
account = account - money;
}
public String getName() {
return name;
}
public int getAccount() {
return account;
}
public Lock getLock() {
return lock;
}
}
/**
* @Auther: allen
* @Date: 2019/8/22 21:04
* @Description: 银行转账动作接口
*/
public interface ITransfer {
/**
*
* @param from 转出账户
* @param to 转入账户
* @param account 转账金额
* @throws InterruptedException
*/
void transfer(UserAccount from, UserAccount to, int account) throws InterruptedException;
}
/**
* @Auther: allen
* @Date: 2019/8/22 21:07
* @Description: 银行转账动作接口实现类
*/
public class TransferAccount implements ITransfer{
public void transfer(UserAccount from, UserAccount to, int account) throws InterruptedException {
try {
from.getLock().lock();
System.out.println(Thread.currentThread().getName() + " get " + from.getName());
try {
to.getLock().lock();
System.out.println(Thread.currentThread().getName() + " get " + to.getName());
//处理转账业务
from.subAccount(account);
to.addAccount(account);
} catch (Exception e) {
e.printStackTrace();
} finally {
to.getLock().unlock();
}
} finally {
from.getLock().unlock();
}
}
}
/**
* @Auther: allen
* @Date: 2019/8/22 21:16
* @Description: 测试类:new出两个线程,一个张三转给李四2000,一个张三转给李四2000
*/
public class PayAccountTask {
private static class PayAccountThread extends Thread{
//线程名称
private String name;
//转出账户
private UserAccount from;
//转入账户
private UserAccount to;
//转账金额
private int account;
//交易操作实现类
private ITransfer iTransfer;
public PayAccountThread(String name, UserAccount from, UserAccount to, int account, ITransfer iTransfer) {
this.name = name;
this.from = from;
this.to = to;
this.account = account;
this.iTransfer = iTransfer;
}
@Override
public void run() {
//设置线程名称
Thread.currentThread().setName(name);
try {
System.out.println(from + " start transfer " + account + " $ to " + to);
//执行转账操作
iTransfer.transfer(from, to, account);
System.out.println(from + " transfer finish.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
UserAccount zhangsan = new UserAccount("zhangsan", 5000);
UserAccount lisi = new UserAccount("lisi",5000);
ITransfer transfer = new TransferAccount();
PayAccountThread zhangsanToLisi = new PayAccountThread("zhangsanToLisi",
zhangsan,lisi,2000,transfer);
PayAccountThread lisiToZhangsan = new PayAccountThread("lisiToZhangsan",
lisi,zhangsan,2000,transfer);
zhangsanToLisi.start();
lisiToZhangsan.start();
}
}
运行结果:
- 线程安全的实现
- 比较对象hash值,先获取hash值大的,保证锁获取顺序
/**
* @Auther: allen
* @Date: 2019/8/22 21:36
* @Description:
*/
public class SafeTransferOperate implements ITransfer {
//加时锁
private final Lock tieLock = new ReentrantLock();
public void transfer(UserAccount from, UserAccount to, int account) throws InterruptedException {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
//先锁hash值大的,保证获取锁的顺序始终一致
if(fromHash > toHash){
try {
from.getLock().lock();
System.out.println(Thread.currentThread().getName() + " get " + from.getName());
try {
to.getLock().lock();
System.out.println(Thread.currentThread().getName() + " get " + to.getName());
//处理转账业务
from.subAccount(account);
to.addAccount(account);
} catch (Exception e) {
e.printStackTrace();
} finally {
to.getLock().unlock();
}
} finally {
from.getLock().unlock();
}
} else if( fromHash < toHash ){
try {
to.getLock().lock();
System.out.println(Thread.currentThread().getName() + " get " + to.getName());
try {
from.getLock().lock();
System.out.println(Thread.currentThread().getName() + " get " + from.getName());
//处理转账业务
from.subAccount(account);
to.addAccount(account);
} catch (Exception e) {
e.printStackTrace();
} finally {
to.getLock().unlock();
}
} finally {
from.getLock().unlock();
}
}else {
try {
tieLock.lock();
try {
from.getLock().lock();
try {
to.getLock().lock();
from.subAccount(account);
to.addAccount(account);
} catch (Exception e) {
e.printStackTrace();
} finally {
to.getLock().unlock();
}
} finally {
from.getLock().unlock();
}
} finally {
tieLock.unlock();
}
}
}
}
执行结果:
[外链图片转存失败(img-02orSVOD-1566482813596)(https://i.loli.net/2019/08/22/ZyimVCjhDwPa78e.png)]
- 显示锁,一直tryLock
public class SafeOperateLock implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
Random r = new Random();
while(true) {
if(from.getLock().tryLock()) {
try {
System.out.println(Thread.currentThread().getName()
+" get "+from.getName());
if(to.getLock().tryLock()) {
try {
System.out.println(Thread.currentThread().getName()
+" get "+to.getName());
//两把锁都拿到了
from.flyMoney(amount);
to.addMoney(amount);
break;
}finally {
to.getLock().unlock();
}
}
}finally {
from.getLock().unlock();
}
}
SleepTools.ms(r.nextInt(10));
}
}
}
其他安全问题
-
活锁
尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生拿锁,释放锁的过程。
解决办法:每个线程休眠随机数,错开拿锁的时间。 -
线程饥饿
低优先级的线程,总是拿不到执行时间