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

更多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-platform\src\com\chj\thread\capt01\volatiles\NotSafe.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

原文地址:https://blog.51cto.com/14591958/2447755

时间: 2024-11-09 13:37:22

并发编程之线程共享和协作(一)的相关文章

Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

Java并发编程系列[未完]: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) 一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线程创建完成时为新

19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权.因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去.因此,一般情况下,当队列满时,会让生产者交出对

Java并发编程:线程的同步

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

JAVA并发编程3_线程同步之synchronized关键字

在上一篇博客里讲解了JAVA的线程的内存模型,见:JAVA并发编程2_线程安全&内存模型,接着上一篇提到的问题解决多线程共享资源的情况下的线程安全问题. 不安全线程分析 public class Test implements Runnable { private int i = 0; private int getNext() { return i++; } @Override public void run() { // synchronized while (true) { synchro

并发编程 13—— 线程池 之 整体架构

Java并发编程实践 目录 并发编程 01—— ConcurrentHashMap 并发编程 02—— 阻塞队列和生产者-消费者模式 并发编程 03—— 闭锁CountDownLatch 与 栅栏CyclicBarrier 并发编程 04—— Callable和Future 并发编程 05—— CompletionService : Executor 和 BlockingQueue 并发编程 06—— 任务取消 并发编程 07—— 任务取消 之 中断 并发编程 08—— 任务取消 之 停止基于线

第十五章、并发编程之线程

目录 第十五章.并发编程之线程 1.什么是线程 2. 进程和线程的区别 3. 开启线程的两种方式 函数开启 类开启 4.子线程与子进程创建速度 5.子线程共享数据的证明 6.线程的join方法 单个子线程 多个子线程 思考 7.了解进程的join 8. 线程的其他相关用法 第十五章.并发编程之线程 1.什么是线程 纠正概念:进程其实不是个执行单位,进程是一个资源单位,每个进程内自带一个线程,线程才是cpu上的执行单位 抽象理解: 进程是指在系统中正在运行的一个应用程序:线程是系统分配处理器时间资

Java并发编程:线程的创建

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

【转】Java并发编程:线程池的使用

Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果.今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPool

68:Scala并发编程原生线程Actor、Cass Class下的消息传递和偏函数实战解析及其在Spark中的应用源码解析

今天给大家带来的是王家林老师的scala编程讲座的第68讲:Scala并发编程原生线程Actor.Cass Class下的消息传递和偏函数实战解析 昨天讲了Actor的匿名Actor及消息传递,那么我们今天来看一下原生线程Actor及CassClass下的消息传递,让我们从代码出发: case class Person(name:String,age:Int)//定义cass Class class HelloActor extends Actor{//预定义一个Actor  def act()