线程安全

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.循环上述操作。

活锁和死锁最大的区别是,是否释放资源,死锁会一直等待,而活锁会释放掉。

线程饥饿:由于线程优先级太低导致一直无法执行该线程
性能:
posted @ 2018-06-14 16:08  爪哇岛求生记  阅读(134)  评论(0编辑  收藏  举报