美文网首页
笔记:多线程并发编程(1)锁、ThreadLocal、同步机制使

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

作者: 盐海里的鱼 | 来源:发表于2021-03-09 14:35 被阅读0次

死锁

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

产生死锁的条件:

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<runs.length;i++){
            runs[i]=new Thread(new TestTask(i));
        }
        for(int i=0;i<runs.length;i++){
            runs[i].start();
        }
    }
    /**
     *类说明:
     */
    public static class TestTask implements Runnable{
        int id;
        public TestTask(int id){
            this.id = id;
        }
        public void run() {
            System.out.println(Thread.currentThread().getName()+":start");
            count = count+id;
            System.out.println(Thread.currentThread().getName()+":"
                    +count);
        }
    }

    public static void main(String[] args){
        NoThreadLocal test = new NoThreadLocal();
        test.startTArray();
    }
}

打印结果:
Thread-1:start
Thread-1:2
Thread-0:start
Thread-0:2
Thread-2:start
Thread-2:4

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

  • 使用ThreadLocal:
public class UseThreadLocal {

    //TODO
   private static ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>(){
        int count = 0;
        @Override
        protected Integer initialValue() {
            return count;
        }
    };
    /**
     * 运行3个线程
     */
    public void StartThreadArray(){
        Thread[] runs = new Thread[3];
        for(int i=0;i<runs.length;i++){
            runs[i]=new Thread(new TestThread(i));
        }
        for(int i=0;i<runs.length;i++){
            runs[i].start();
        }
    }
    
    /**
     *类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
     */
    public static class TestThread implements Runnable{
        int id;
        public TestThread(int id){
            this.id = id;
        }
        public void run() {
            System.out.println(Thread.currentThread().getName()+":start");
            //TODO
            int value = intThreadLocal.get()+id;
            intThreadLocal.set(value);
            System.out.println(Thread.currentThread().getName()+" : "+intThreadLocal.get());
        }
    }

    public static void main(String[] args){
        UseThreadLocal test = new UseThreadLocal();
        test.StartThreadArray();
    }
}
打印结果:
Thread-0:start
Thread-0 : 0
Thread-1:start
Thread-1 : 1
Thread-2:start
Thread-2 : 2
  • 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<ThreadLocal<?>> {
            /** 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、同步机制使

    死锁 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们...

  • Java多线程(十五)---锁的内存语义

    移步java多线程系列文章锁是Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向...

  • ThreadLocal源码分析

    ThreadLocal和线程同步机制相比:都是为了解决多线程中相同变量的访问冲突问题。在同步机制中,通过对象的锁机...

  • ZooKeeper实现分布式锁

    1.什么是分布式锁 一般的锁:一般我们说的锁是但进程多线程的锁,在多线程并发编程中,用于线程之间的数据同步,保护共...

  • java并发

    1.并发编程中的锁 并发编程中的各种锁java高并发锁的3种实现Java并发机制及锁的实现原理 2.线程池核心线程...

  • 基本的线程机制—Java编程思想

    基本的线程机制—Java编程思想 并发编程使我们可以将程序分为多个分离的、独立运行的任务。通过使用多线程机制,这些...

  • 并发编程03-Java内存模型03(锁的内存语义)

    锁的内存语义 锁的释放-获取建立的happens-before关系 锁是Java并发编程中最重要的同步机制.锁除了...

  • 多线程之_Synchronized关键字

    Synchronized线程同步机制 synchronized是非公平锁1、在多线程编程、一些敏感数据不允许被多个...

  • Java多线程编程——锁优化

    并发环境下进行编程时,需要使用锁机制来同步多线程间的操作,保证共享资源的互斥访问。加锁会带来性能上的损坏,似乎是众...

  • Java线程相关类

    A.ThreadLocal类(泛型支持)-代表线程局部变量 使用ThreadLocal类可以简化多线程编程时的并发...

网友评论

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

      本文链接:https://www.haomeiwen.com/subject/unevqltx.html