多线程总结 线程池 线程通信

线程上下文

https://www.cnblogs.com/szlbm/p/5505707.html

《Java并发编程的艺术》
即使是单核CPU也支持多线程执行代码,CPU通过给给个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间非常短,CPU通过不停的切换线程执行,让我们感觉多个线程同时执行的。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换。

如何减少上下文切换
既然上下文切换会导致额外的开销,因此减少上下文切换次数便可以提高多线程程序的运行效率。减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。
1、无锁并发编程。多线程竞争时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不同的线程处理不同段的数据
2、CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁
3、使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
4、协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换

Java线程之间如何通信

volatile关键字和Synchronized关键字
  • 标志量boolean on = true,另一个线程对其执行关闭动作on = false,这里涉及多个线程对变量的访问,因此需要将其定义成volatile,这样其他线程对它进行写入时,所有线程能够感知到变化。
  • Synchronized保证多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,保证线程对变量访问的可见性和排他性。Synchronized保证多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,保证线程对变量访问的可见性和排他性。
join() 在线程B中调用线程A.join()方法
  • 可以实现线程A执行完,线程B再执行,见demo2
Object类中的wait(),notify()方法
  • wait()和notify()方法见demo3
package com.company.多线程与并发.线程通信;

import java.util.concurrent.TimeUnit;

public class ForArticle {
    public static void main(String[] args) {
        //demo1();
        //demo2();
        demo3();

    }

    /**
     * random output
     */
    private static void demo1() {
        Thread A = new Thread(()->{
            printNumber("A");
        });

        Thread B = new Thread(()->{
            printNumber("B");
        });
        A.start();
        B.start();
    }

    /**
     * A 1, A 2, A 3, B 1, B 2, B 3
     */
    private static void demo2() {
        Thread A = new Thread(()-> {
                printNumber("A");
        });

        Thread B = new Thread(()-> {
            System.out.println("B 开始等待 A");
            try {
                A.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            printNumber("B");
        });
        A.start();
        B.start();
    }

    /**
     * A 1, B 1, B 2, B 3, A 2, A 3
     */
    private static void demo3() {
        Object lock = new Object();

        Thread A = new Thread(()->{
            System.out.println("INFO: A 竞争锁");
            synchronized (lock) {
                System.out.println("INFO: A 得到了锁 lock");
                System.out.println("A 1");
                try {
                    System.out.println("INFO: A 准备进入等待状态,调用 lock.wait() 放弃锁 lock 的控制权");
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("INFO: 有人唤醒了 A, A 重新获得锁 lock");
                System.out.println("A 2");
                System.out.println("A 3");
            }
        });

        Thread B = new Thread(()-> {
            System.out.println("INFO: B 先等待");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock) {
                System.out.println("INFO: B 得到了锁 lock");
                System.out.println("B 1");
                System.out.println("B 2");
                System.out.println("B 3");
                System.out.println("INFO: B 打印完毕,调用 lock.notify() 方法");
                lock.notify();
            }
        });
        B.start();
        A.start();


    }

    private static void printNumber(String threadName) {
        int j=0;
        while (j++ < 3) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + " print: " + j);
        }
    }

}

管道输入/输出流
  • 等待/通知的经典范式
    • 等待方(消费者)遵循如下原则:
    1. 获取对象的锁
    2. 如果条件不满足,调用对象的wait()方法,被通知后仍要检查条件
    3. 条件满足则执行对应的逻辑

对应伪代码如下:

synchronized(对象) {
       while(条件不满足) {
              对象.wait();
       }
       对应的处理逻辑
}
  • 通知方(生产者)遵循如下原则:
    1. 获取对象的锁
    2. 改变条件
    3. 通知所有等待在对象上的线程

伪代码如下

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

生产者消费者代码如下:
详解生产者消费者问题 lock newCondition的方法 附加代码 和遇到的问题详解

线程池

https://blog.csdn.net/u011974987/article/details/51027795

一、为什么要用线程池
  1. 每次新建线程的时候,直接new Thread开销大。
  2. 缺乏统一管理,需要用线程池来管理线程。如果不管理,new Thread不可控,可能占用很多系统资源。
用了线程池后的好处
  1. 创建的线程可以重复利用,减少线程的创建和消亡
  2. 可以增加线程定时执行的功能,如newScheduledThreadPool线程池

https://www.cnblogs.com/dolphin0520/p/3932921.html

ThreadPoolExecuter

是Java并发包中的核心类,构造函数的源码如下

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize
    核心池的大小。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务到达后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize时,就会把到达的任务放到缓存队列中。
  • maximumPoolSize
    线程池最大线程数,表示在线程池中最多创建多少线程。
  • keepAliveTime
    默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。
  • TimeUnit
    keepAliveTime的时间单位
  • workQueue
    一个阻塞队列,用来存放等待执行的任务。

线程池处理流程

《40个Java多线程问题总结》

http://www.importnew.com/18459.html

多个线程间共享数据

一、每个线程执行代码相同
二、线程执行代码不同

封装成一个类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值