线程上下文
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);
}
}
}
管道输入/输出流
- 等待/通知的经典范式
- 等待方(消费者)遵循如下原则:
- 获取对象的锁
- 如果条件不满足,调用对象的wait()方法,被通知后仍要检查条件
- 条件满足则执行对应的逻辑
对应伪代码如下:
synchronized(对象) {
while(条件不满足) {
对象.wait();
}
对应的处理逻辑
}
- 通知方(生产者)遵循如下原则:
- 获取对象的锁
- 改变条件
- 通知所有等待在对象上的线程
伪代码如下
synchronized(对象) {
改变条件
对象.notifyAll();
}
生产者消费者代码如下:
详解生产者消费者问题 lock newCondition的方法 附加代码 和遇到的问题详解
线程池
https://blog.csdn.net/u011974987/article/details/51027795
一、为什么要用线程池
- 每次新建线程的时候,直接new Thread开销大。
- 缺乏统一管理,需要用线程池来管理线程。如果不管理,new Thread不可控,可能占用很多系统资源。
用了线程池后的好处
- 创建的线程可以重复利用,减少线程的创建和消亡
- 可以增加线程定时执行的功能,如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
多个线程间共享数据
一、每个线程执行代码相同
二、线程执行代码不同
封装成一个类