是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理” [1] 。
程序是指令和数据的有序集合,其本身没有任何的含义,是一个静态的概念。
进程是执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位
通常在一个进程中可以包含若干线程,当然一个进程周至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位
注:真正多线程是指有多个cpu,即多核,如服务器,如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个线程,因为切换的很快,所以就造成同时执行的错觉。
public class MyThreadTwo extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
new MyThreadTwo().start();
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
new Thread(new MyRunnable()).start();
1. 实现Callable接口,需要返回值类型
2. 重写call方法,需要抛出异常
3. 创建目标对象
4. 创建执行服务: ExecutorService ser=Executors.newFixedThreadPool(1)
5. 提交只想:Future result1=ser.submit(t1)
6. 获取结果:boolean r1=result1,get()
7. 关闭服务: ser.shutdownNow()
区别:
shutdown() 只是关闭了提交通道,用submit()是无效的;而内部该怎么跑还是怎么跑,跑完再停。
shutdownNow() 能立即停止线程池,正在跑的和正在等待的任务都停下了。
public class MyCallable implements Callable {
@Override
public Objectcall() throws Exception {
int sum=0;
for (int i = 0; i <= 100; i++) {
sum+=i
}
return sum;
}
}
方式一:FutureTask
MyCallable myCallable = new MyCallable();
FutureTask futureTask=new FutureTask(myCallable);
new Thread(futureTask).start();
// 会等待线程执行结束才会去获取结果
Integer i= (Integer) futureTask.get();
方式二:ExecutorService
MyCallable myCallable=new MyCallable();
ExecutorService executorService = Executors.newFixedThreadPool(5);
Future r1 = executorService.submit(myCallable);
Boolean aBoolean = r1.get();
System.out.println(aBoolean);
executorService.shutdown();
核心: 多个线程共同操作同一个对象
public class MyRunnable implements Runnable {
private int ticket=10;
@Override
public void run() {
while (true){
if (ticket==0){
break;
}
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"ticket:"+ticket--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
MyRunnable myRunnable=new MyRunnable();
new Thread(myRunnable,"A").start();
new Thread(myRunnable,"B").start();
new Thread(myRunnable,"C").start();
#### 结果
Bticket:10
Aticket:9
Cticket:10
Cticket:8
Bticket:8
Aticket:8
Bticket:7
Aticket:7
Cticket:6
Aticket:4
Cticket:5
Bticket:4
Bticket:3
Cticket:2
Aticket:1
new Thread(()->{System.out.println(1); }).start();
Lambda表达式其核心就是函数式接口,对于函数式接口都可以通过lambda表达式来创建接口对象
函数式接口:任何接口,如果只创建唯一一个抽象方法,那么它就是一个函数式接口
匿名内部类—>lambda
函数式接口:
public interface MyInterface {
void print();
}
匿名内部类:
MyInterface myInterface1= new MyInterface(){
@Override
public void print() {
System.out.println(1111111);
}
}myInterface1.print();
lambda:
MyInterface myInterface2=()->{
System.out.println(2222222);
};
myInterface2.print();
public interface MyInterface {
void print(String a);
// void print(String a,int b);
}
MyInterface myInterface1=(a)->{
System.out.println(a);
};
myInterface1.print("测试参数");
前置条件:只有一个参数才可,多参数不行
MyInterface myInterface1=a->{
System.out.println(a);
};
myInterface1.print("测试参数");
前置条件:方法中只有一行代码
MyInterface myInterface1=a->
System.out.println(a);
myInterface1.print("测试参数");

推荐线程自己停止—>通过设置flag标识来控制线程的停止
private boolean flag=true:
public void run() {
while (flag){
if (ticket==0){
break;
}
}
}
public void stop(){
this.flag=false
}
MyThreadThree myThreadThree1=new MyThreadThree();
myThreadThree1.start();
for (int i = 0; i < 1000; i++) {
myThreadThree1.join();
System.out.println("主线程:"+i);
}
线程状态。线程可以处于以下状态之一
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这些对象,这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列。等待前面的线程使用完毕,下一个线程再使用。
由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时。也带来了访问冲突问题,为了保证数据在方法中被访问的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
存在以下问题:
同步锁机制synchronized
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问结束后自动解锁,其他线程才可加锁进入
修饰符 synchronized 返回值类型 方法名(形参){}
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行。否则线程会阻塞。方法一旦执行,就独占该锁,直到该方法返回才释放锁。后面被阻塞的线程才能获得这个锁继续执行
测试:
public class MyRunnable implements Runnable {
private int ticket=10;
@Override
public synchronized void run() {
while (true){
if (ticket<=0){
break;
}
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"ticket:"+ticket--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注:若将一个大的方法什么为synchronized 将会影响效率
synchronized(同步锁){
访问共享资源核心代码块
}
注:同步锁必须是同一把(同一个对象)
synchronized(Obj){
}
锁对象使用规范:
建议共享资源作为锁对象,对于实力方法建议使用this作为锁对象
对于静态方法建议是使用字节类.class码对象
测试:
public class MyRunnable implements Runnable {
private Integer ticket = 20;
@Override
public void run() {
synchronized (ticket) {
while (true) {
if (ticket <= 0) {
break;
}
try {
Thread.sleep(20);
System.out.println(Thread.currentThread().getName() + "ticket:" + ticket--);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
ReentrantLock
public class Account {
private int card;
private int money;
private ReentrantLock lock=new ReentrantLock();
public Account(int card,int money){
this.card=card;
this.money=money;
}
public void reduceMoney(int money){
try {
lock.lock();
Thread.sleep(1000);
if(this.money多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题
互相持有对方锁,等待对方释放
public class Test {
public static void main(String[] args) {
Mackeup mackeup1=new Mackeup(1);
Mackeup mackeup2=new Mackeup(2);
mackeup1.start();
mackeup2.start();
}
}
class Mirror {
}
class Desk {
}
class Mackeup extends Thread {
static Mirror mirror = new Mirror();
static Desk desk = new Desk();
int choice;
public Mackeup(int choice){
this.choice=choice;
}
@Override
public void run() {
if(choice==1){
synchronized (mirror){
System.out.println("获得镜子所");
synchronized (desk){
System.out.println("获得书桌所");
}
}
}else{
synchronized (desk){
System.out.println("获得书桌所");
synchronized (mirror){
System.out.println("获得镜子所");
}
}
}
}
}
解决:释放对象的锁
if (choice == 1) {
synchronized (mirror) {
System.out.println("获得镜子所");
}
synchronized (desk) {
System.out.println("获得书桌所");
}
} else {
synchronized (desk) {
System.out.println("获得书桌所");
}
synchronized (mirror) {
System.out.println("获得镜子所");
}
}
核心:当多个线程共同操作共享资源时,线程之间通过某种方式告知自己的状态来,协调资源,避免无效资源争夺
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁的创建销毁、实现重复利用。
优点:
线程池不是一个提高系统的并发能力的策略,是一个更好的管理线程的方案。
线程池主要解决两个问题:
一是当执行大量异步任务时线程池能够提供较好的性能。在不使用线程池时,每当需要执行异步任务时直接new一个线程来运行,而线程的创建和销毁是需要开销的。线程池里面的线程是可复用的,不需要每次执行异步任务时都重新创建和销毁线程。
二是线程池提供了一种资源限制和管理的手段,比如可以限制线程的个数,动态新增线程等。每个ThreadPoolExecutor也保留了一些基本的统计数据,比如当前线程池完成的任务数目等。
线程池的创建
有两种:ThreadPoolExecutor 和 Executors。
ThreadPoolExecutor
构造
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}
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:线程池的核心线程数量,也就是最小线程数。它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue队列里。
- maximumPoolSize:线程池的最大线程数量
- keepAliveTime:当前线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内销毁。
- TimeUtil : 时间单位 。配合keepAliveTime使用
- workQueue:阻塞队列,用来保存被添加到线程池中但尚未执行的任务,一般有:直接提交队列、有界任务队列、无界任务队列、任务优先队列
- threadFactory:线程工厂,用来创建线程,一般用默认的。
- handler:“饱和处理机制”,“拒绝策略”——当任务太多来不及处理时,如何拒绝任务。
需注意:当线程数达到核心数的时候,任务是先入队,而不是先创建最大线程数。
线程池本意只是让核心数量的线程工作着,不论是 core 的取名,还是 keepalive 的设定,所以你可以直接把 core 的数量设为你想要线程池工作的线程数。而任务队列起到一个缓冲的作用。最大线程数这个参数更像是无奈之举,在最坏的情况下做最后的努力,去新建线程去帮助消化任务。
线程池尽可能只维护核心数量的线程,提供任务队列暂存任务,并提供拒绝策略来应对过载的任务。
如果线程数已经达到核心线程数,那么新增加的任务只会往任务队列里面塞,不会直接给予某个线程,如果任务队列也满了,新增最大线程数的线程时,任务是可以直接给予新建的线程执行的,而不是入队。
三种阻塞队列
//2个核心线程最大线程为3的线程池
Executor executors = new ThreadPoolExecutor(
2, 3, 30, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new RejectHandler());
//2个核心线程最大线程为3的线程池,阻塞队列大小为2
Executor executors = new ThreadPoolExecutor(
2, 3, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new RejectHandler());
Executor executors = new ThreadPoolExecutor(
2, 6, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2),
new RejectHandler());
整体结构

流程图
Executors
实质:通过Executors的工厂方法来创建线程,其实根本上是调用ThreadPoolExecutor构造方法时传入的参数不同。
阿里开发手册不建议使用线程池,手册上是说线程池的构建不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。 用 Executors 使得用户不需要关心线程池的参数配置,意味着大家对于线程池的运行规则也会慢慢的忽略。这会导致一个问题,比如我们用 newFixdThreadPool 或者 singleThreadPool.允许的队列长度为Integer.MAX_VALUE,如果使用不当会导致大量请求堆积到队列中导致 OOM 的风险;而 newCachedThreadPool,允许创建线程数量为 Integer.MAX_VALUE,也可能会导致大量线程的创建出现 CPU 使用过高或者 OOM 的问题。而如果我们通过 ThreadPoolExecutor 来构造线程池的话,我们势必要了解线程池构造中每个参数的具体含义,使得开发者在配置参数的时候能够更加谨慎
线程池也提供了许多可调参数和可扩展性接口,以满足不同情境的需要,可以使用更方便的Executors的工厂方法
比如newCachedThreadPool (缓冲线程池,线程池线程个数最多可达Integer.MAX_ VALUE,线程自动回收)、newFixedThreadPool (固定大小的线程池)、newSingleThreadExecutor (单线程化的线程池)等来创建线程池,当然还可以自定义。
ExecutorService executorService = Executors.newFixedThreadPool(10);
线程池关闭
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务