java并发安全详解
类的线程安全定义
如果多线程下使用这个类,不过多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的。
类的线程安全表现为:
- 操作的原子性
- 内存的可见性
不做正确的同步,在多个线程之间共享状态的时候,就会出现线程不安全。
怎么才能做到类的线程安全?
栈封闭
所有的变量都是在方法内部声明的,这些变量都处于栈封闭状态。
无状态
没有任何成员变量的类,就叫无状态的类
让类不可变
让状态不可变,两种方式:
1,加final关键字,对于一个类,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上final关键字,但是加上final,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能保证整个类是不可变的。
2、根本就不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值
volatile
保证类的可见性,最适合一个线程写,多个线程读的情景,
加锁和CAS
synchronized、reentrantLock和cas
安全的发布
类中持有的成员变量,特别是对象的引用,如果这个成员对象不是线程安全的,通过get等方法发布出去,会造成这个成员对象本身持有的数据在多线程下不正确的修改,从而造成整个类线程不安全的问题。
TheadLocal
死锁
死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象, 若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
资源一定是多于1个,同时小于等于竞争的线程数,资源只有一个,只会产生激烈的竞争。
死锁的根本成因:获取锁的顺序不一致导致。
解决办法:保证加锁的顺序性
简单死锁示例
public class DeadLockDemo {
public static void main(String[] args) {
// 创建两个线程
DeadLockThread dt1 = new DeadLockThread(false);
DeadLockThread dt2 = new DeadLockThread(true);
// 启动线程
new Thread(dt1).start();
new Thread(dt2).start();
}
}
class DeadLockThread implements Runnable {
// 标记变量
private boolean flag;
public DeadLockThread(boolean flag) {
super();
this.flag = flag;
}
public void run() {
// dt1线程执行该方法
if (flag) {
synchronized (ThreadLock.locka) { Thread.sleep(100);
System.out.println("if locka!");
synchronized (ThreadLock.lockb) {
System.out.println("if lockb!");
}
}
} // dt2线程执行该方法
else {
synchronized (ThreadLock.lockb) { Thread.sleep(100);
System.out.println("else lockb!");
synchronized (ThreadLock.locka) {
System.out.println("else locka!");
}
}
}
}
}
class ThreadLock {
static Object locka = new Object();
static Object lockb = new Object();
}运行结果:
if locka!
else lockb!
说白了,就是线程一等待获取被线程二占有的锁,线程二等待获取被线程一占有的锁,互相打架!!!
这种是最简单的,没什么好说的。。。
动态死锁
动态顺序死锁,在实现时按照某种顺序加锁了,但是因为外部调用的问题,导致无法保证加锁顺序而产生的。
解决:
1、 通过内在排序,保证加锁的顺序性
2、通过尝试拿锁,也可以。
账户类:
package com.ty.thread.account;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*类说明:用户账户的实体类
*/
public class UserAccount {
//private int id;
private final String name;//账户名称
private int money;//账户余额
public UserAccount(String name, int amount) {
this.name = name;
this.money = amount;
}
public String getName() {
return name;
}
public int getAmount() {
return money;
}
@Override
public String toString() {
return "UserAccount{" +
"name=‘" + name + ‘\‘‘ +
", money=" + money +
‘}‘;
}
//转入资金
public void addMoney(int amount){
money = money + amount;
}
//转出资金
public void flyMoney(int amount){
money = money - amount;
}
}转账接口:
package com.ty.thread.transfer;
import com.ty.thread.account.UserAccount;
/**
*类说明:银行转账动作接口
*/
public interface ITransfer {
/**
*
* @param from 转出账户
* @param to 转入账户
* @param amount 转账金额
* @throws InterruptedException
*/
void transfer(UserAccount from, UserAccount to, int amount) throws InterruptedException;
}转账线程类:
package com.ty.thread.worker;
import com.ty.thread.account.UserAccount;
import com.ty.thread.transfer.ITransfer;
/**
* 执行转账动作的线程
*/
public class TransferThread extends Thread {
private String name;//线程名字
private UserAccount from;
private UserAccount to;
private int amount;
private ITransfer transfer; //实际的转账动作
public TransferThread(String name, UserAccount from, UserAccount to,
int amount, ITransfer transfer) {
this.name = name;
this.from = from;
this.to = to;
this.amount = amount;
this.transfer = transfer;
}
public void run(){
Thread.currentThread().setName(name);
try {
transfer.transfer(from,to,amount);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}下面会分为几种转账场景,一一来说明:
1、不安全的锁策略
TrasnferAccount
package com.ty.thread.transfer.impl;
import com.ty.thread.account.UserAccount;
import com.ty.thread.transfer.ITransfer;
/**
*类说明:不安全的转账动作的实现
*/
public class TrasnferAccount implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
synchronized (from){//先锁转出
System.out.println(Thread.currentThread().getName() + " get"+from.getName());
Thread.sleep(100);
synchronized (to){//再锁转入
System.out.println(Thread.currentThread().getName() + " get"+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}运行主类:
/**
*类说明:模拟支付公司转账的动作
*/
public class PayCompany {
public static void main(String[] args) {
UserAccount zhangsan = new UserAccount("zhangsan",20000);
UserAccount lisi = new UserAccount("lisi",20000);
ITransfer transfer = new TrasnferAccount();
TransferThread zhangsanToLisi = new TransferThread("zhangsanToLisi",zhangsan,lisi,2000,transfer);
TransferThread lisiToZhangsan = new TransferThread("lisiToZhangsan",lisi,zhangsan,4000,transfer);
zhangsanToLisi.start();
lisiToZhangsan.start();
}
}运行结果:

会发现一直卡在这,发生了死锁。
原因:上面启动了两个线程,每个线程的from其实是不同的,对于zhangsanToLisi线程来说,from是zhangsan,to是lisi,但是对于lisiToZhangsan线程来说,则是反的,所以两个线程启动后,会产生死锁的情况。
2、安全但是不好理解的锁策略
package com.ty.thread.transfer.impl;
import com.ty.thread.account.UserAccount;
import com.ty.thread.transfer.ITransfer;
/**
*类说明:不会产生死锁的安全转账
*/
public class SafeOperate implements ITransfer {
private static Object tieLock = new Object();//加时赛锁
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
//通过对象的hash值比较,这样就不会出现from to混乱的情况
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
//先锁hash小的那个
if(fromHash<toHash) {
synchronized (from){
System.out.println(Thread.currentThread().getName()
+" get"+from.getName());
Thread.sleep(100);
synchronized (to){
System.out.println(Thread.currentThread().getName()
+" get"+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}else if(toHash<fromHash) {
synchronized (to){
System.out.println(Thread.currentThread().getName()
+" get"+to.getName());
Thread.sleep(100);
synchronized (from){
System.out.println(Thread.currentThread().getName()
+" get"+from.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}else {//解决hash冲突的方法
synchronized (tieLock) {
synchronized (from) {
synchronized (to) {
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
}
}这种是比较麻烦的方式,不过却是线程安全的。
3、安全好理解的策略
首先修改账户类,增加lock锁
package com.ty.thread.account;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*类说明:用户账户的实体类
*/
public class UserAccount {
//private int id;
private final String name;//账户名称
private int money;//账户余额
//显示锁
private final Lock lock = new ReentrantLock();
public Lock getLock() {
return lock;
}
public UserAccount(String name, int amount) {
this.name = name;
this.money = amount;
}
public String getName() {
return name;
}
public int getAmount() {
return money;
}
@Override
public String toString() {
return "UserAccount{" +
"name=‘" + name + ‘\‘‘ +
", money=" + money +
‘}‘;
}
//转入资金
public void addMoney(int amount){
money = money + amount;
}
//转出资金
public void flyMoney(int amount){
money = money - amount;
}
}package com.ty.thread.transfer.impl;
import java.util.Random;
import com.ty.thread.account.UserAccount;
import com.ty.thread.transfer.ITransfer;
/**
*类说明:不会产生死锁的安全转账第二种方法,尝试拿锁
*/
public class SafeOperateToo 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();
}
}
//这地方加休眠也是有原因的,因为代码逻辑是锁两个账户对象,假如线程一拿到了A账户的锁,线程二拿到了B账户的锁,这是线程一获取B的锁失败,释放A的锁,线程二则获取A失败,又释放了B,相互礼让。。。通过休眠降低这种竞争概率!
Thread.sleep(r.nextInt(10));
}
}
}运行方法:
public static void main(String[] args) {
UserAccount zhangsan = new UserAccount("zhangsan",20000);
UserAccount lisi = new UserAccount("lisi",20000);
ITransfer transfer = new SafeOperateToo();
TransferThread zhangsanToLisi = new TransferThread("zhangsanToLisi",zhangsan,lisi,2000,transfer);
TransferThread lisiToZhangsan = new TransferThread("lisiToZhangsan",lisi,zhangsan,4000,transfer);
zhangsanToLisi.start();
lisiToZhangsan.start();
}运行结果:

说明:通过自旋 + ReentrantLock实现线程安全。