并发编程之线程共享和协作一

44次阅读

共计 4278 个字符,预计需要花费 11 分钟才能阅读完成。

更多 Android 架构进阶视频学习请点击:https://space.bilibili.com/47…
本篇文章将从以下几个内容来阐述线程共享和协作:

[基础概念之 CPU 核心数、线程数,时间片轮转机制解读]
[线程之间的共享]
[线程间的协作]

一、基础概念

CPU 核心数、线程数
两者的关系:cpu 的核心数与线程数是 1:1 的关系,例如一个 8 核的 cpu,支持 8 个线程同时运行。但在 intel 引入超线程技术以后,cpu 与线程数的关系就变成了 1:2。此外在开发过程中并没感觉到线程的限制,那是因为 cpu 时间片轮转机制(RR 调度)的算法的作用。什么是 cpu 时间片轮转机制看下面 1.2.

CPU 时间片轮转机制
含义就是:cpu 给每个进程分配一个“时间段”,这个时间段就叫做这个进程的“时间片”,这个时间片就是这个进程允许运行的时间,如果当这个进程的时间片段结束,操作系统就会把分配给这个进程的 cpu 剥夺,分配给另外一个进程。如果进程在时间片还没结束的情况下阻塞了,或者说进程跑完了,cpu 就会进行切换。cpu 在两个进程之间的切换称为“上下文切换”,上下文切换是需要时间的,大约需要花费 5000~20000(5 毫秒到 20 毫秒,这个花费的时间是由操作系统决定)个时钟周期,尽管我们平时感觉不到。所以在开发过程中要注意上下文切换(两个进程之间的切换)对我们程序性能的影响。

二、线程之间的共享

synchronized 内置锁
线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一步一步地执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立地运行,那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作,包括数据之间的共享,协同处理事情。这将会带来巨大的价值。

Java 支持多个线程同时访问一个对象或者对象的成员变量,关键字 synchronized 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。
volatile 关键字
volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

private volatile static boolean ready;
private static int number;
不加 volatile 时,子线程无法感知主线程修改了 ready 的值,从而不会退出循环,而加了 volatile 后,子线程可以感知主线程修改了 ready 的值,迅速退出循环。但是 volatile 不能保证数据在多个线程下同时写时的线程安全,参见代码:
thread-platformsrccomchjthreadcapt01volatilesNotSafe.java
volatile 最适用的场景:一个线程写,多个线程读。
线程私有变量 ThreadLocal

+ get() 获取每个线程自己的 threadLocals 中的本地变量副本。+ set() 设置每个线程自己的 threadLocals 中的线程本地变量副本。ThreadLocal 有一个内部类 ThreadLocalMap:

                    public T get() {Thread t = Thread.currentThread();
                    // 根据当前的线程返回一个 ThreadLocalMap. 点进去 getMap
                    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();}
                
                 // 点击去 getMap(t)方法发现其实返回的是当前线程 t 的一个内部变量 ThreadLocal.ThreadLocalMap
                    ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
                // 由此可以知道,当调用 ThreadLocal 的 get 方法是,其实返回的是当前线程的 threadLocals(类型是 ThreadLocal.ThreadLocalMap)中的变量。调用 set 方法也类似。// 举例一个使用场景
                /**
             * ThreadLocal 使用场景:把数据库连接对象存放在 ThreadLocal 当中.
             * 优点:减少了每次获取 Connection 需要创建 Connection
             * 缺点:因为每个线程本地会存放一份变量,需要考虑内存的消耗问题。* @author luke Lin
             *
             */
            public class ConnectionThreadLocal {
                private final static String DB_URL = "jdbc:mysql://localhost:3306:test";
                private static ThreadLocal<Connection> connectionHolder  = new ThreadLocal<Connection>(){protected Connection initialValue() {
                        try {return DriverManager.getConnection(DB_URL);
                        } catch (SQLException e) {e.printStackTrace();
                        }
                        return null;
                    };
                };
                
                /**
                 * 获取连接
                 * @return
                 */
                public Connection getConnection(){return connectionHolder.get();
                }
                
                /**
                 * 释放连接
                 */
                public void releaseConnection(){connectionHolder.remove();
                }
            }
     
                // 解决 ThreadLocal 中弱引用导致内存泄露的问题的建议
                + 声明 ThreadLoal 时,使用 private static 修饰
                + 线程中如果本地变量不再使用,即使使用 remove()

三、线程间的协作

wait() notify() notifyAll()

        //1.3.1 通知等候唤醒模式
            //1)等候方
                获取对象的锁
                在循环中判断是否满足条件,如果不满足条件,执行 wait,阻塞等待。如果满足条件跳出循环,执行自己的业务代码
            
            //2)通知方
                获取对象的锁
                更改条件
                执行 notifyAll 通知等等待方
        //1.3.2 
            //wait notify notifyAll 都是对象内置的方法
            //wait notify notifyAll 都需要加 synchronized 内被执行,否则会抱错。// 执行 wait 方法是,会让出对象持有的锁,直到以下 2 个情况发生:1。被 notify/notifyAll 唤醒。2。wait 超时
        //1.3.3 举例使用 wait(int millis),notifyAll 实现一个简单的线城池超时连接
/*
 * 连接池,支持连接超时。* 当连接超过一定时间后,做超时处理。*/
public class DBPool2 {
    LinkedList<Connection> pools;
    // 初始化一个指定大小的新城池
    public DBPool2 (int poolSize) {if(poolSize > 0){pools =  new LinkedList<Connection>(); 
            for(int i=0;i < poolSize; i++){pools.addLast(SqlConnectImpl.fetchConnection());
            }
        }
    }
    
    
    /**
     * 获取连接
     * @param remain 等待超时时间
     * @return
     * @throws InterruptedException 
     */
    public Connection fetchConn(long millis) throws InterruptedException {
        // 超时时间必须大于 0,否则抛一场
        synchronized (pools) {if (millis<0) {while(pools.isEmpty()) {pools.wait();
                }
                return pools.removeFirst();}else {
                // 超时时间
                long timeout = System.currentTimeMillis() + millis;
                long remain = millis;
                // 如果当前 pools 的连接为空,则等待 timeout,如果 timeout 时间还没有返回,则返回 null。while (pools.isEmpty() && remain > 0) {
                    try {pools.wait(remain);
                        remain = timeout - System.currentTimeMillis();} catch (InterruptedException e) {e.printStackTrace();
                    }
                }
                Connection result = null;
                if (!pools.isEmpty()) {result = pools.removeFirst();
                }
                return result;
            }
        }

    }
    
    /**
     * 释放连接
     */
    public void releaseConn(Connection con){if(null != con){synchronized (pools) {pools.addLast(con);
                pools.notifyAll();}
        }
    }
}

sleep() yield()
join()
面试点:线程 A 执行了县城 B 的 join 方法,那么线程 A 必须等到线程 B 执行以后,线程 A 才会继续自己的工作。
wait() notify() yield() sleep()对锁的影响
面试点:
线程执行 yield(), 线程让出 cpu 执行时间,和其他线程同时竞争 cup 执行机会,但如果持有的锁不释放。
线程执行 sleep(), 线程让出 cpu 执行时间,在 sleep()醒来前都不竞争 cpu 执行时间,但如果持有的锁不释放。
notify 调用前必须持有锁,调用 notify 方法本身不会释放锁。
wait()方法调用前必须持有锁,调用了 wait 方法之后,锁就会被释放。当 wait 方法返回的时候,线程会重新持有锁。
更多 Android 架构进阶视频学习请点击:[https://space.bilibili.com/47…]
参考:https://blog.csdn.net/m0_3766…
https://www.cnblogs.com/codet…
https://blog.csdn.net/aimashi…

正文完
 0