多线程系列八:线程安全

一、线程安全

1.  怎样让多线程下的类安全起来

无状态、加锁、让类不可变、栈封闭、安全的发布对象

2. 死锁

2.1 死锁概念及解决死锁的原则

一定发生在多个线程争夺多个资源里的情况下,发生的原因是每个线程拿到了某个(某些)资源不释放,同时等待着其他线程所持有的资源。

解决死锁的原则就是确保正确的获取资源的顺序,或者获取资源时使用定时尝试机制。

2.2 常见的死锁:

简单顺序死锁:

package com.study.deadlock.bank;

/**
 * 简单顺序死锁
 * 解决办法:保证拿锁的顺序一致
 * @author THINKPAD
 *
 */
public class SimpleDeadLock {

    //左锁
    private static Object left = new Object();
    //右锁
    private static Object right = new Object();

    private static void leftToRight() throws InterruptedException {
        synchronized (left){
            System.out.println(Thread.currentThread().getName()+" get left");
            Thread.sleep(100);
            synchronized (right){
                System.out.println(Thread.currentThread().getName()+" get right");
            }
        }
    }

    private static void rightToLeft() throws InterruptedException {
        synchronized (left){
            System.out.println(Thread.currentThread().getName()+" get right-left");
            Thread.sleep(100);
            synchronized (right){
                System.out.println(Thread.currentThread().getName()+" get left-right");
            }
        }
    }

    private static class TestThread extends Thread{
        private String name;

        public TestThread(String name) {
            this.name = name;
        }

        @Override
        public void run(){
            try {
                rightToLeft();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread.currentThread().setName("Main");
        TestThread  testThread = new TestThread("testThread");
        testThread.start();
        try {
            leftToRight();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

动态顺序死锁:

A. 通过特殊手段保证拿锁的顺序一致

package com.study.deadlock.bank.serivice;

import com.study.deadlock.bank.Account;

/**
 * 动态顺序死锁
 * @author THINKPAD
 * 解决办法:通过特殊手段保证拿锁的顺序一致,如获取要锁定对象的hash值,然后比较大小,先锁小的再锁大的
 */
public class SafeTransfer implements ITransfer {

    private static Object tieLock = new Object();

    @Override
    public void transfer(Account from, Account to, int amount)
            throws InterruptedException {

        //获取要锁定对象的hash值,然后比较大小,先锁小的再锁大的
        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);

        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);
                    System.out.println(from);
                    System.out.println(to);
                }
            }
        }
        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);
                    System.out.println(from);
                    System.out.println(to);
                }
            }
        }
        else{
            //hash值相等时在前面再加一把锁
            synchronized (tieLock){
                synchronized (to){
                    System.out.println(Thread.currentThread().getName()+" get "+from.getName());
                    Thread.sleep(100);
                    synchronized (from){
                        System.out.println(Thread.currentThread().getName()
                                +" get "+to.getName());
                        from.flyMoney(amount);
                        to.addMoney(amount);
                    }
                }
            }
        }
    }
}

B. 定时轮询获取锁即定时尝试获取锁

package com.study.deadlock.bank;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 账户信息
 * @author THINKPAD
 *
 */
public class Account {
    private long number;
    private final String name;
    private int money;
    private final Lock lock = new ReentrantLock();

    public Lock getLock() {
        return lock;
    }

    public Account(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 "Account{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }

    public void addMoney(int amount){
        money = money + amount;
    }

    public void flyMoney(int amount){
        money = money - amount;
    }
}
package com.study.deadlock.bank.serivice;

import com.study.deadlock.bank.Account;

import java.util.Random;

/**
 * 动态顺序死锁
 * @author THINKPAD
 * 
 * 解决办法:
 * 定时轮询获取锁即定时尝试获取锁
 */
public class TryLockTransfer implements ITransfer {
    @Override
    public void transfer(Account from, Account to, int amount)
            throws InterruptedException {
        Random r = new Random();
        while(true){
            if(from.getLock().tryLock()){
                try{
                    System.out.println(Thread.currentThread().getName()
                            +" get from "+from.getName());

                    if(to.getLock().tryLock()){
                        try{
                            System.out.println(Thread.currentThread().getName()
                                    +" get to "+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();
                }
            }
            Thread.sleep(r.nextInt(5));//防止产生活锁
        }
    }
}

3.  活锁

概念:多个线程同时获取锁,当去拿另外的一把锁时发现被其他线程持有,觉得其他线程可能需要自己手中的锁就释放自己持有的锁,这样不断的循环就产生了活锁

4. 对性能的思考

4.1、 程序的安全性优于性能的提升

4.2、 使用多线程会带来额外的性能开销,滥用线程,有可能导致得不偿失。

4.3、 所谓性能,包含多个指标。例如“多快”:服务时间、等待时间、延迟时间;例如“多少”:吞吐量,例如可伸缩性等等。

4.4、 性能的各个指标方面,是完全独立的,有时候甚至是相互矛盾。

4.5、 所以性能的提升是个包括成本在内多方面权衡和妥协的结果。

性能优化的黄金原则:

首先保证程序正确,然后再提高运行速度(如果有确切的证据表明程序确实慢)。

5. Amdahl定律

F :程序中的串行部分,是个百分比(100%-1%)

Ncpu的个数

Speedup:指在增加cpu的情况下,程序的加速比

 注意:任何程序都会有串行部分

6.  线程引入的开销

上下文的切换

内存同步

阻塞

7. 减少锁的竞争

快进快出,缩小锁的范围,将与锁无关的,有大量计算或者阻塞操作的代码移出同步范围。

 

减小锁的粒度,多个相互独立的状态变量可以使用多个锁来保护,每个锁只保护一个变量。

 

锁的分段,例如ConcurrentHashMap中的实现。

减少独占锁的使用,例如读多写少的情况下,用读写锁替换排他锁。

8. 安全的单例模式 

 懒汉式单例

package com.study.dcl;

/**
 * 懒汉式单例-双重检查
 * @author THINKPAD
 *
 */
public class SingleDcl {

    //双重检查不能保证线程安全,原因是第一个线程可能还没有初始化完,
    //第二个线程就进来获取单例对象使用了,所以加一个volatile修饰保证可见性
    private volatile static SingleDcl single;
    private SingleDcl(){}

    public static SingleDcl getInstance(){
        if(null==single){
            synchronized (SingleDcl.class){
                if(single==null){
                    single = new SingleDcl();
                }
            }
        }
        return single;
    }


}

 饿汉式单例

package com.study.dcl;

/**
 * 饿汉式单例-线程安全
 * @author THINKPAD
 *
 */
public class SingleEHan {
    public static SingleEHan singleEHan = new SingleEHan();
    private SingleEHan(){};
}

 延迟类占位符单例

package com.study.dcl;

/**
 * 延迟类占位符单例,利用JVM的类加载的时候会自动给加载的类加上锁的机制
 * @author THINKPAD
 *
 */
public class SingleClassInit {
    private SingleClassInit(){}

    private static class InstanceHolder{
        public static SingleClassInit instance = new SingleClassInit();
    }

    public static SingleClassInit getInstance(){
        return InstanceHolder.instance;
    }
}

 总结:单例建议使用延迟类占位符单例和枚举类型的单例

猜你喜欢

转载自www.cnblogs.com/leeSmall/p/8976004.html