笔记:多线程并发编程(1)锁、ThreadLocal、同步机制使用

死锁

是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

产生死锁的条件:

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

  • 自己总结(人话):
    1. 争夺者数目大于争夺资源
    2. 争夺资源顺序不对
    3. 拿到资源不放手
    4. 有另外一个等待使用资源的线程
解决死锁:

只要打破四个必要条件之一就能有效预防死锁的发生。
打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。
打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。
打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。
打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。

  • 自己总结(人话):
    1. 确定每个线程的拿锁顺序
    2. 采用尝试拿锁的方式
活锁

频繁申请锁两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一个线程总是拿到同一把锁,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。

解决办法:每个线程休眠随机数,错开拿锁的时间。

线程饥饿

低优先级的线程,总是拿不到执行时间

Thread Local

Thread Local 线程本地变量 为每个线程提供一个变量副本。实现线程隔离
eg: 创建三个线程分别对变量count+线程id

  • 未使用ThreadLocal:
public class NoThreadLocal {
    static Integer count = new Integer(1);
    /**
     * 运行3个线程
     */
    public void startTArray(){
        Thread[] runs = new Thread[3];
        for(int i=0;i

并没有每个线程按照 t0->0 、t1->1 、t2->2

  • 使用ThreadLocal:
public class UseThreadLocal {

    //TODO
   private static ThreadLocal intThreadLocal = new ThreadLocal(){
        int count = 0;
        @Override
        protected Integer initialValue() {
            return count;
        }
    };
    /**
     * 运行3个线程
     */
    public void StartThreadArray(){
        Thread[] runs = new Thread[3];
        for(int i=0;i
  • ThreadLocal简析:
    前面说到ThreadLocal是本地变量副本。那么他是怎么实现的呢。由ThreadLocal的set()入手
ThreadLocal.java
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//通过线程获得ThreadLocalMap
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
拿到 ThreadLocal.ThreadLocalMap threadLocals
可以看到ThreadLocal.ThreadLocalMap 里面有个Entity[]
Entity的具体模型:
 static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;
            //以threadLocal为key value 为值存入
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
因为可能存在多个类型的threadlocal 所以需要使用数组
threadLocal解析.png
不同线程内部ThreadLocal解析.png

volatile的使用 (最轻量的同步机制)

适合一写多读的场景

  • 优点:保证可见性与快速更新
  • 缺点:无法保证线程安全与操作原子性
    使用 :
  • 可见性例子:
public class VolatileCase {
    private  static boolean ready;
    private static int number;

    private static class PrintThread extends Thread{
        @Override
        public void run() {
            System.out.println("PrintThread is running.......");
            while(!ready);
            System.out.println("number = "+number);
        }
    }

    public static void main(String[] args) {
        new PrintThread().start();
        SleepTools.second(1);
        number = 51;
        ready = true;
        SleepTools.second(5);
        System.out.println("main is ended!");
    }
}
打印结果:
PrintThread is running.......
main is ended!
PrintThread并不知晓ready已经变化

加入 volatile关键字
private  volatile  static boolean ready;
打印结果:
PrintThread is running.......
number = 51
main is ended!

线程不安全例子:

public class NotSafe {
    private volatile long count =0;

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

    //count进行累加
    public void incCount(){
        count++;
    }

    //线程
    private static class Count extends Thread{

        private NotSafe simplOper;

        public Count(NotSafe simplOper) {
            this.simplOper = simplOper;
        }

        @Override
        public void run() {
            for(int i=0;i<10000;i++){
                simplOper.incCount();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NotSafe simplOper = new NotSafe();
        //启动两个线程
        Count count1 = new Count(simplOper);
        Count count2 = new Count(simplOper);
        count1.start();
        count2.start();
        Thread.sleep(50);
        System.out.println(simplOper.count);
    }
}
理想结果:20000
打印结果:
20000
13671
13529
因为线程的执行是需要有cpu执行权的 所以导致了结果的不确定性

synchronized的使用

synchronized 一定是作用在某个对象上 当所在static 的方法 或者静态块 时 锁住的是 X.class的对象
注:锁只有作用在同个对象上才会起作用

public class SynTest {

    private long count =0;

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

    /*用在同步块上*/
    public void incCount(){
            count++;
    }

    

    //线程
    private static class Count extends Thread{

        private SynTest simplOper;

        public Count(SynTest simplOper) {
            this.simplOper = simplOper;
        }

        @Override
        public void run() {
            for(int i=0;i<10000;i++){
                simplOper.incCount();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynTest simplOper = new SynTest();
        //启动两个线程
        Count count1 = new Count(simplOper);
        Count count2 = new Count(simplOper);
        count1.start();
        count2.start();
        Thread.sleep(50);
        System.out.println(simplOper.count);//20000
    }
}
打印结果:
18748 20000 16735 18307 
加锁:
public void incCount(){
        synchronized (obj){
            count++;
        }
    }
打印结果:
20000 20000 20000 20000

锁的作用对象:

public class SynTest {

    private long count =0;
    private Object obj = new Object();//作为一个锁

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

        /*锁的是SyncTest.class的类对象*/
    public void incCount(){
        synchronized (SyncTest.class){
            count++;
        }
    }

    /*用在方法上 锁的是obj对象*/
    public void incCount(){
        synchronized (obj){
            count++;
        }
    }

      /*用在方法上 锁的也是SynTest.class对象*/
    public static synchronized void incCount2(){
            count++;
    }

    /*用在方法上 锁的也是当前对象实例*/
    public synchronized void incCount2(){
            count++;
    }

    /*用在同步块上,但是锁的是当前类的对象实例*/
    public void incCount3(){
        synchronized (this){
            count++;
        }
    }
}

你可能感兴趣的:(笔记:多线程并发编程(1)锁、ThreadLocal、同步机制使用)