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


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

    [基础概念之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-platformsrccomchj hreadcapt01volatilesNotSafe.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/474380680]
    参考:https://blog.csdn.net/m0_37661458/article/details/90692419
    https://www.cnblogs.com/codetree/p/10188638.html
    https://blog.csdn.net/aimashi620/article/details/82017700

  • 相关阅读:
    【转】SVN与Git比较
    我遇到了Hibernate异常
    使用 Eclipse 远程调试 Java 应用程序
    linux显示桌面快捷键设置
    Ubuntu共享WiFi(AP)给Android方法
    用zd1211+Ubuntu 10.04实现的AP
    Ubuntu开机自动禁用无线网络
    戴尔大力宣传Ubuntu 对比与Windows的差异
    【SSH进阶之路】Spring的AOP逐层深入——采用注解完成AOP(七)
    【SSH进阶之路】Spring的AOP逐层深入——AOP的基本原理(六)
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11937160.html
Copyright © 2020-2023  润新知