java并发编程之美学习

基础

线程的创建与运行

  1. 继承Thread类,重写run方法。调用类的start()方法启用。
  2. 实现Runnable接口,重写run方法。new Thread(Runnable的实现类).start()方法启动。
  3. 实现Callable接口,从写call方法。
    在这里插入图片描述

通知等待系列函数

wait()函数

  当一个线程调用一个共享变量的wait()方法的时候,该调用线程会被阻塞挂起。
(1)等到其他线程调用共享变量的notify() 或者 notifyAll() 方法,调用阻塞线程才会返回。
(2)其他线程调用了该线程的interrupt()方法,调用线程抛出InterruptedException异常返回。

  需要注意的是,调用共享变量wait()方法的线程,要首先获取到该对象上的监视器锁。如果没有获取该对象上的监视器锁,直接调用共享对象的wait()方法,会抛出ILLegalMonitorStateException异常。

  获取对象上的监视器锁,我们只需要通过synchronized关键字实现。

//1使用共享变量作为参数
synchronized(共享变量) {
   
}
//2方法上使用synchronized修饰
synchronized void add() {
   
//doSomething
}

  虚假唤醒:没有中断,没有调用notify,notifyAll,没有超时的情况下,被唤醒。虽然虚假唤醒在生产环境很少出现,但是为了防患于未然,需要下一个死循环不停的去测试该线程被唤醒的条件是否满足。不满足继续等待。
  场景:通过一个queue对象进行加锁synchronized。当queue队列里面为空,线程阻塞。当条件满足队列不为空,退出while循环。

synchronized (obj) {
   
   while (条件不满足) {
   
      obj.wait();
   }
}

wait(long timeout)函数

  该方法相比较wait()方法多了一个超时时间参数。它的不同之处在于,如果一个线程调用共享变量挂起后,没有在指定的时间内被其他线程调用notify()或者notifyAll()方法唤醒。那么该函数还是会因为超时而返回。wait(0)和wait()方法效果一样。

notify()与notifyAll()函数

  一个线程调用共享对象上的notify()方法后,会唤醒一个在共享对象上通过wait系列函数被挂起的线程,具体唤醒哪一个等待的线程是随机的。
  一个线程调用共享对象上的notifyAll()方法后,会唤醒共享对象上所有调用wait系列函数挂起的线程。

  线程被唤醒后,不能立马从wait()方法处继续执行,它必须再获取了共享对象上的监视器锁后,才可以继续执行。

  notify(),notifyAll()函数也是必须先获取到共享对象上的监视器锁后,才可以调用共享对象的notify(),notifyAll()方法。

join方法

  项目中有这么一种场景,就是需要等某几件事情都完成后才继续往下执行。Thread类中有一个join方法,就可以做这个事情。主线程调用Thread类的join()方法会被阻塞,等线程执完返回后,主线程才会继续执行。在JUC并发包中有一个CountDownLatch,功能更加强大。

sleep方法

  Thread类中有一个静态的sleep方法,当一个执行中的线程调用了Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是这期间不参与cpu调度。sleep睡眠时间内不释放获取的锁
在这里插入图片描述

yield方法

  yield方法也是Thread类中的一个静态方法。当一个线程调用yield方法时,实际就是在暗示线程调度器当前线程请求让出自己的cpu使用,让线程调度器进行下一轮的线程调度。
  当一个线程调用yield方法时,当前线程会让出cpu使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能获取到的就是刚刚调用yield方法让出时间片的线程。
在这里插入图片描述

线程中断

  1. void interrupt()方法
      中断线程。比如:当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。如果线程A调用了wait系列函数,join方法,sleep方法被阻塞挂起。这时候若线程B调用线程A的interrupt()方法,线程A会再调用这些方法的地方抛出InterruptedException异常而返回。
  2. boolean isinterrupted()方法
      检测当前线程是否被中断,如果是返回true,否则返回false。
  3. boolean interrupted()方法
      该方法是静态方法。检测当前线程是否被中断,如果是返回true,否则返回false。与isInterrupted()不同的是。该方法如果发现当前线程被中断,则会清除中断标志。

线程死锁

  死锁是这两个或两个以上线程在执行过程中因争夺资源而造成的互相等待的现象。死锁产生必须具备的四个条件:

  1. 互斥条件
      资源具有排他性,同一时刻只有一个线程占用。如果此时还有其他线程想要获取资源只能等待,直至占有资源的线程释放该资源。
  2. 持有并等待
      指一个线程已经持有了至少一个资源,但又提出了新的资源请求,而新的资源已被其他线程占有。
  3. 不可剥夺
      线程拿到资源后,在自己释放前,不能被其他线程抢占。
  4. 环路等待
      发生死锁时,必然存在两个或两个以上线上相互等待的情况。

如何避免死锁:
  避免死锁,只需要破坏产生死锁其中一个条件就行了。但是学过操作系统的知道,目前只有持有并等待环路等待是可以破坏的。
  所以。我们可以指定获取锁的顺序,使用资源申请的有序性原则就可以避免死锁。

守护线程和用户线程

  Java中的线程分为两类,分别为daemon线程(守护线程) 和 user线程(用户线程)。在JVM启动时,会调用main函数,main函数所在的线程就是一个用户线程。JVM内部同时还启动了好多守护线程,比如垃圾回收线程。

  守护线程和用户线程的区别。
(1)当最后一个非守护线程结束时,JVM退出。
(2)只要有一个用户线程没有结束,JVM就不会退出。

  可以通过Thread类的setDaemon方法设置当前线程为守护线程。

ThreadLocal

  多线程访问同一个共享变量特别容易出现并发问题。特别是多线程对同一个变量进行并发写入的时候。为了保证线程安全,一般使用者在访问共享变量的时候需要进行适当的同步。ThreadLocal相当于一个工具类,里面的方法操作都是对应当前线程的操作。

  ThreadLocal可以设置线程对象中的本地变量进行赋值。这样每个线程操作的都是自己的变量,就不存在共享变量的线程安全问题了。
  创建一个ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存。
在这里插入图片描述

ThreadLocal的使用

  本例开启了两个线程,在每个线程内部都设置了本地变的值,然后调用print 函数打印当前本地变量的值。如果打印后调用了本变量的remove 方法, 删除地内中的该变量。

package com.example.demo.entryone;

public class ThreadLocalTest {
   
    static ThreadLocal<String> localVariable = new ThreadLocal<>() ;

    static void print(String str) {
   
        //1.1打印当前线程本地内存中 localVariable变量的值
         System.out.println(str + ":" +localVariable .get());
        //1.2清除当前线程本地内存中的localVariable变量
        //localVariable.remove() ;
    }

    public static void main(String[] args) {
   
        //创建线程ThreadOne
        Thread threadOne = new Thread(new Runnable() {
   

            @Override
            public void run() {
   
                //设置线程One中的本地变量
                localVariable.set("threadOne local variable");
                print("threadOne");
                System.out.println("threadOne remove after" + ":" + localVariable.get());
            }
        });

        //创建线程ThreadTwo
        Thread threadTwo = new Thread(new Runnable() {
   

            @Override
            public void run() {
   
                //设置线程Two中的本地变量
                localVariable.set("threadTwo local variable");
                print("threadTwo");
                System.out.println("threadTwo remove after" + ":" + localVariable.get());
            }
        });

        threadOne.start();
        threadTwo.start();
    }

}

ThreadLocal的实现原理

在这里插入图片描述
  由该图可知,Thread类中有一个threadLocals和一个inheritableThreadLocals。它们都是ThreadLocalMap类型的变量,ThreadLocalMap是一个定制化的Hashmap

  每个线程中的这两个变量初始化默认都为null。当线程第一次调用ThreadLocal的set或者get方法时才会创建它们。线程的本地变量就是存储在threadLocals里面。

  ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程threadLocals里面并存放起来。如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadLocals变量里面,所以当不需要使用本地变量时可以通过调用TreadLocal的remove方法,从当前线程的threadlocals里面删除该本地变量。

  ThreadLocal的set,get及remove方法的实现逻辑如下:

  ThreadLocalMap getMap(Thread t) {
   
        return t.threadLocals;
    }
  void createMap(Thread t, T firstValue) {
   
        t.threadLocals = new ThreadLocalMap(this, firstValue);
   }
   public void set(T value) {
   
       //获取当前线程
        Thread t = Thread.currentThread();
        //将当前线程作为key,去查询对应的线程变量,找到则设置
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //将Threadlocal作为key传入ThreadLocalMap中
            map.set(this, value);
        else
           //第一次调用就创建当前线程对应的HashMap
            createMap(t, value);
    }
    
    public T get() {
   
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadlocals变量
        ThreadLocalMap map = getMap(t);
        if (map != null) {
   
            //根据key:threadlocal对象,获取value值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
   
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
     public void remove() {
   
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             //移出当前key为:threadlocal对象的entry
             m.remove(this);
     }

InheritableThreadLocal类

  InheritableThreadLocal继承ThreadLocal,提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
   
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
   
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
   
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值