JAVA并发编程梳理与学习二(线程之间的共享和协作)

一、线程间的共享
synchronized (底层原理分析jvm时会说到)内置锁
Java 支持多个线程同时访问一个对象或者对象的成员变量,关键字 synchronized 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制
对象锁和类锁
对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态 方法。我们知道,类的对象实例可以有很多个,但 是每个类只有一个 class 对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。 但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。类锁和对象锁之间也是互不干扰的。
一个问题:执行下面代码synchronized为什么没用


    private Integer i;

    public TestIntegerThread(Integer i){
        this.i=i;
    }

    @Override
    public void run() {

        synchronized (i){
            i++;
            System.out.println(i);
        }
    }

    public static void main(String[] args){
        TestIntegerThread testIntegerThread=new TestIntegerThread(0);
        for(int i=0;i<5;i++){
            new   Thread(testIntegerThread).start();
        }

    }

这是执行结果,我们期望的是1,2,3,4,5
在这里插入图片描述
注意:synchronized锁的是对象,一定要保证所锁对象的不变性
二、volatile,最轻量的同步机制(底层原理后面分析)
volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某 个变量的值,这新值对其他线程来说是立即可见的
volatile 最适用的场景:一个线程写,多个线程读。
三、ThreadLocal
1.ThreadLocal定义:从名字看ThreadLocal叫线程变量,填充的变量属于当前线程,每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。
2.使用场景:
1》在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2》线程间数据隔离
3》进行事务操作,用于存储线程事务信息。
4》数据库连接,Session会话管理。
spring事务管理应用了ThreadLocal
我们代码一般都采用三层结构,我们会在service层调用dao方法,在dao对象的每个方法当中去打开事务和关闭事务。如何让多个 dao 使用同一个数据源连接呢?我们就必须为每个 dao传递同一个数据库连接,要么就是在 dao实例化的时候作为构造方法的参数传递,要么在每个dao的实例方法中作为方法的参数传递。这两种方式无疑对我们的 Spring 框架或者开发人员来说都不合适。为了让这个数据 库连接可以跨阶段传递,又不显示的进行参数传递,就可以用ThreadLocal。
Web 容器中,每个完整的请求周期会由一个线程来处理。因此,如果我们能将一些参数绑定到线程的话,就可以实现在软件架构中跨层次的参数共享(是隐式的共享)。
ThreadLocal 的使用
ThreadLocal 有 4 个方法:
• void set(Object value) 设置当前线程的线程局部变量的值。
• public Object get() 该方法返回当前线程所对应的线程局部变量。
• public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK 5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动 被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
• protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为 了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个 null。
public final static ThreadLocal threadLocal= new ThreadLocal();
threadLocal代表一个能够存放String类型的ThreadLocal对象。
ThreadLocal分析
1.看set方法源码
在这里插入图片描述
原码可以看出,get方法先获取当前线程,然后获取getMap方法获取ThreadLocalMap,如果map不为null,则将当前线程对象t作为key,要存储对象为value存到map里面去。如果该Map不存在,则初始化一个。
ThreadLocalMap解读
在这里插入图片描述
ThreadLocalMap其实就是ThreadLocal的一个静态内部类,里面定义了一个Entry来保存数据,继承的弱引用。在Entry内部使用ThreadLocal作为key,我们设置的value作为value。 Thread 类中有一个这样类型成员,所以 getMap 是直接返回 Thread 的成员。并且Entry是一个数组,可以存储多个。
getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
调用当期线程t,返回当前线程t中的成员变量threadLocals,其实就是ThreadLocalMap。
2.看get源码
在这里插入图片描述
我们也是先获取当前线程,然后通过getMap获取ThreadLocalMap,如果不为null,那就使用当前线程作为ThreadLocalMap的Entry的键值,去取对应的Entry,如果没有那就设置一个初始值。ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
3.设置初始值
protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为 了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个null。

private static ThreadLocal<Integer> intLocal
            = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

使用ThreadLocal引发的内存泄漏问题
上面我们说了ThreadLocal是一个弱引用,我们先说说强引用、软引用、弱引用、虚引用
**强引用:**是指在程序代码之中普遍存在的,类似“Object obj=new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象实例。
**软引用:**在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。用来描述一些还有用但并非必需的对象。 在系统将要发生内存溢出异常之前,将会把这些对象实例列进回收范围之中进行第二次回收。
**弱引用:**在 JDK 1.2 之 后,提供了 WeakReference 类来实现弱引用。用来描述非必需对象的,被弱引用关联的对象实例只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。
**虚引用:**在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用。也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象实例被收集器回收时收到一个系统通知。
发生内存泄漏原因:
在这里插入图片描述
threadlocal 作为弱引用将会被 gc 回收。ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,但是下面的一条线当前线程并未结束,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:CurrentThread Ref -> CurrentThread -> ThreaLocalMap -> Entry -> value,而这块 value 永 远不会被访问到了,所以存在着内存泄露。
**解决办法:**不在需要使用 ThreadLocal 变量后,都调remove()方法,清除数据,而remove也是通过expungeStaleEntry来把entry对象置为null的。 但是我们仔细观察ThreadLocal的get()、set()方法,里面也有调用了 expungeStaleEntry 方法用来清除 Entry 中 Key 为 null 的 Value,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。
四、等待/通知机制
可以用wait/notify()、notifyAll()实现
**wait:**调用该方法的线程会进入等待状态,只有等待另外线程的通知或被中断才会返回.需要注意,调用 wait()方法后,会释放对象的锁
notify(): 通知一个在对象上等待的线程,使其从 wait 方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入等待状态。 notifyAll(): 通知所有等待在该对象上的线程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章