Java并发编程中如何实现线程之间的共享和协作

发布时间:2022-02-28 10:42:31 作者:iii
来源:亿速云 阅读:106

本篇内容介绍了“Java并发编程中如何实现线程之间的共享和协作”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

一、线程间的共享

1.1 ynchronized内置锁

用处

对象锁和类锁

1.2 volatile关键字

1.3 ThreadLocal

1.4 Spring的事务借助ThreadLocal类

Spring会从数据库连接池中获得一个connection,然会把connection放进ThreadLocal中,也就和线程绑定了,事务需要提交或者回滚,只要从ThreadLocal中拿到connection进行操作。

1.4.1 为何Spring的事务要借助ThreadLocal类?
以JDBC为例,正常的事务代码可能如下:
dbc = new DataBaseConnection();//第1行
Connection con = dbc.getConnection();//第2行
con.setAutoCommit(false);// //第3行
con.executeUpdate(...);//第4行
con.executeUpdate(...);//第5行
con.executeUpdate(...);//第6行
con.commit();第7行
上述代码,可以分成三个部分:
事务准备阶段:第1~3行
业务处理阶段:第4~6行
事务提交阶段:第7行
Connection conn = getConnection();
Dao1 dao1 = new Dao1(conn);
dao1.exec();
Dao2 dao2 = new Dao2(conn);
dao2.exec();
Dao3 dao3 = new Dao3(conn);
dao3.exec();
conn.commit();
1.4.2 ThreadLocal的使用

void set(Object value)

public Object get()

public void remove()

protected Object initialValue()

public final static ThreadLocal RESOURCE = new ThreadLocal()

1.4.3 ThreadLocal实现解析
public class ThreadLocal<T> {
    //get方法,其实就是拿到每个线程独有的ThreadLocalMap
    //然后再用ThreadLocal的当前实例,拿到Map中的相应的Entry,然后就可以拿到相应的值返回出去。
    //如果Map为空,还会先进行map的创建,初始化等工作。
    public T get() {
        //先取到当前线程,然后调用getMap方法获取对应线程的ThreadLocalMap
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    // Thread类中有一个 ThreadLocalMap 类型成员,所以getMap是直接返回Thread的成员
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    // ThreadLocalMap是ThreadLocal的静态内部类
    static class ThreadLocalMap {
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            // 用数组保存 Entry , 因为可能有多个变量需要线程隔离访问,即声明多个 ThreadLocal 变量
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // Entry 类似于 map 的 key-value 结构
            // key 就是 ThreadLocal, value 就是需要隔离访问的变量
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        ...
    }
    
    //Entry内部静态类,它继承了WeakReference,
    //总之它记录了两个信息,一个是ThreadLocal<?>类型,一个是Object类型的值
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    
    //getEntry方法则是获取某个ThreadLocal对应的值
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }
    
    //set方法就是更新或赋值相应的ThreadLocal对应的值
    private void set(ThreadLocal<?> key, Object value) {
        ...
    }
    ...
}

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    ...
}

1.5引用基础知识

1.5.1 引用
1.5.2 强引用
1.5.3 软引用
1.5.4 弱引用
1.5.5 虚引用

1.6 使用 ThreadLocal 引发内存泄漏

1.6.1 准备

将堆内存大小设置为-Xmx256m

启用一个线程池,大小固定为5个线程

//5M大小的数组
private static class LocalVariable {
    private byte[] value = new byte[1024*1024*5];
}

// 创建线程池,固定为5个线程
private static ThreadPoolExecutor poolExecutor
        = new ThreadPoolExecutor(5,5,1, TimeUnit.MINUTES,new LinkedBlockingQueue<>());
        
//ThreadLocal共享变量
private ThreadLocal<LocalVariable> data;

@Override
public void run() {
    //场景1:不执行任何有意义的代码,当所有的任务提交执行完成后,查看内存占用情况,占用 25M 左右
    //System.out.println("hello ThreadLocal...");

    //场景2:创建 数据对象,执行完成后,查看内存占用情况,与场景1相同
    //new LocalVariable();

    //场景3:启用 ThreadLocal,执行完成后,查看内存占用情况,占用 100M 左右
    ThreadLocalOOM obj = new ThreadLocalOOM();
    obj.data = new ThreadLocal<>();
    obj.data.set(new LocalVariable());
    System.out.println("update ThreadLocal data value..........");

    //场景4: 加入 remove(),执行完成后,查看内存占用情况,与场景1相同
    //obj.data.remove();

    //分析:在场景3中,当启用了ThreadLocal以后确实发生了内存泄漏
}

场景1:

场景2:

场景3:

场景4:

场景分析:

1.6.2 内存泄漏分析

Java并发编程中如何实现线程之间的共享和协作

场景3分析:

从表面上看内存泄漏的根源在于使用了弱引用。为什么使用弱引用而不是强引用?下面我们分两种情况讨论:

因此,ThreadLocal内存泄漏的根源是:

总结:

错误使用ThreadLocal导致线程不安全:

二、线程间的协作

存在如下问题:

2.1等待和通知机制

是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。

上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

notify():

通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入WAITING状态。

notifyAll():

通知所有等待在该对象上的线程。

wait():

调用该方法的线程进入 WAITING状态,只有等待另外线程的通知或被中断才会返回.需要注意,调用wait()方法后,会释放对象的锁。

wait(long):

超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回;

wait (long,int):

对于超时时间更细粒度的控制,可以达到纳秒;

2.2等待和通知的标准范式

等待方遵循如下原则:

synchronized(对象){
    while(条件不满足){
        对象.wait();
    }
    对应的逻辑
}

通知方遵循如下原则:

synchronized(对象){
    改变条件
    对象.notifyAll();
}

在调用wait()、notify()系列方法之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait() 方法、notify()系列方法;

notify() 和 notifyAll() 应该用谁?

2.3等待超时模式实现一个连接池

调用场景:

调用yield() 、sleep()、wait()、notify()等方法对锁有何影响?

“Java并发编程中如何实现线程之间的共享和协作”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

推荐阅读:
  1. Python线程协作threading.Condition如何实现
  2. vue项目中如何实现组件之间的数据共享和修改

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:html+css怎样制作圣诞树

下一篇:怎么利用html与css制作5星好评页面

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》