线程安全
1.什么是线程安全
一个实例或这一个方法在多线程使用中不会出现任何问题。
2.产生线程不安全的原因
多个线程访问同一相同资源,并且有线程执行了写操作,可能会出现线程安全问题。
2.怎样做到类线程安全
无状态 :没有成员变量的类,也就不存在共享同一资源了。
让类不可变:所有成员变量定义为final
volatile :最轻量,保证类的可见性 但是不能保证原子性。(当这个共享资源改变时会通知其他 读取最新的)
CAS :乐观锁
加锁:悲观锁
ThreadLocal 让每个线程工作内存里都有一个变量拷贝
3.线程安全问题
死锁:多个线程(m) 竞争多个资源(n) n<=m 互不相让
如:线程1 线程2 同时需要资源A B, 1->A , 2->B。现在1需要拿B,但是B被2拿到并锁定,所以1等待2释放B,同理2和A也是这样,互相等待,谁也不放,导致死锁
a.简单顺序锁:
两个线程代码层级互相拿对方需要的不同资源
简单顺序锁事例
public void getFirst() {
try {
synchronized (first) {
System.out.println(Thread.currentThread().getName()+"getFirst first");
Thread.sleep(1000);
synchronized (second) {
System.out.println(Thread.currentThread().getName()+"getFirst second");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void getSecond() {
try {
synchronized (second) {
System.out.println(Thread.currentThread().getName()+"getFirst second");
Thread.sleep(1000);
synchronized (first) {
System.out.println(Thread.currentThread().getName()+"getFirst first");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
b.动态顺序锁:
参数传递 导致死锁
动态顺序锁 由于参数传递顺序不同导致死锁
public void transfer(UserAccounts from, UserAccounts to, int amount) {
try {
String threadName = Thread.currentThread().getName();
synchronized (from) {
System.out.println(threadName + " get " + from.getName());
Thread.sleep(100);
synchronized (to) {
System.out.println(threadName + " get " + to.getName());
from.flyMoney(amount);
to.flyMoney(amount);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
解决死锁的办法:
1.对于简单顺序死锁,可以改变拿锁顺序让他们一致即可。
2.对于动态锁,通过一种方式给他们指定顺序,如判断hashcode来设定拿锁顺序。
private Object tieLock = new Object();//加时锁
public void transfer(UserAccounts from, UserAccounts to, int amount) {
int fromHash = System.identityHashCode(from);//调用object hash因为哈希方法能被改写
int toHash = System.identityHashCode(to);
//始终让小的先加锁 如果相同 则再定义一个锁变量,看谁先获取到这个变量 谁先执行操作
if (fromHash < toHash) {
synchronizedSafe(from, to, from, to, amount);
} else if (toHash < fromHash) {
synchronizedSafe(to, from, from, to, amount);
} else {
synchronized (tieLock) {
synchronizedSafe(from, to, from, to, amount);
}
}
}
public void synchronizedSafe(UserAccounts first, UserAccounts sencond, UserAccounts from, UserAccounts to, int amount) {
try {
String threadName = Thread.currentThread().getName();
synchronized (first) {
System.out.println(threadName + " get " + first.getName());
Thread.sleep(100);
synchronized (sencond) {
System.out.println(threadName + " get " + sencond.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
3.采用尝试拿锁方式。 在共享对象中建立一个锁,程序中判断拿到锁再拿下一把锁,如果没有拿到就释放当前锁
//在共享变量的UserAccounts 类中定义一个锁
private final Lock lock = new ReentrantLock();
//加锁逻辑
public void transfer(UserAccounts from, UserAccounts to, int amount) {
String threadName = Thread.currentThread().getName();
Random r = new Random();
while (true) {
if (from.getLock().tryLock()) {//先拿from的锁 如果拿到尝试拿to的锁
System.out.println(threadName + " get " + from.getName());
try {
if (to.getLock().tryLock()) {
//拿到to的锁后执行操作
try {
System.out.println(threadName + " get " + to.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
break;
} finally {
//发生异常释放锁
to.getLock().unlock();
}
}
} finally {
from.getLock().unlock(); //to的锁拿不到就释放掉from的锁
}
}
}
try {
//随机休眠 拿锁时间过短可能造成活锁
Thread.sleep(r.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
活锁: 上述例子不加sleep可能造成活锁,线程1 拿A 线程2 拿B ,1尝试拿B,拿不到,释放掉A,2拿A拿不到,释放掉B.循环上述操作。
活锁和死锁最大的区别是,是否释放资源,死锁会一直等待,而活锁会释放掉。
线程饥饿:由于线程优先级太低导致一直无法执行该线程
性能: