如何做到类的线程安全
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何做到类的线程安全相关的知识,希望对你有一定的参考价值。
参考技术A 1.栈封闭 (所有的变量都是在方法内部声明的,这些变量都是处于栈封闭状态(也就是局部变量都是线程安全的))2,无状态 (没有任何成员变量的类)
3.让类不可变 (String 基本类型包装类 都是不可变的类)
1. 加final 关键字。 对于一个类,所有的成员变量应该都是私有的,同样的只要有可能,变量应该加上 final关键字
2. 根本就不提供修改成员变量的地方,同时成员变量也不作为方法的返回值
4.volatile 保证类的可见性,最适合一个线程写,多个线程读的情景
5.加锁 和 CAS
6.ThreadLocal
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实现线程安全。
以上是关于如何做到类的线程安全的主要内容,如果未能解决你的问题,请参考以下文章