须知少时拏云志,曾许人间第一流
基本概念
并发和并行
- 并行:在同一时刻,有多个指令在多个CPU上同时执行
- 并发:在同一时刻,有多个指令在单个CPU上交替执行
进程和线程
- 线程是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中的实际运作单位。简单理解为:应用软件中相互独立,可以同时运行的功能
- 进程:是正在运行的程序独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
并发性:任何进程都可以同其他进程一起并发执行
- 线程:是进程中的单个顺序控制流,是一条执行路径 单线程:一个进程如果只有一条执行路径,则称为单线程程序 多线程:一个进程如果有多条执行路径,则称为多线程程序
多线程的实现方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
继承Thread类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package thread.case1;
public class MyThread extends Thread{
@Override public void run() {
for (int i = 0; i < 10; i++) { System.out.println(this.getName()+":hello world!"); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package thread.case1;
public class ThreadDemo1 { public static void main(String[] args) {
MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread();
myThread1.setName("线程1"); myThread2.setName("线程2");
myThread1.start(); myThread2.start();
} }
|
实现Runnable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package thread.case2;
public class MyRun implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) {
Thread t = Thread.currentThread(); System.out.println(t.getName()+"hello world!");
} } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package thread.case2;
public class ThreadDemo { public static void main(String[] args) {
MyRun my1 = new MyRun(); MyRun my2 = new MyRun();
Thread thread1 = new Thread(my1); Thread thread2 = new Thread(my2);
thread1.setName("1:"); thread2.setName("2:");
thread1.start(); thread2.start();
} }
|
实现Callable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package thread.case3;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } return sum; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package thread.case3;
import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;
public class ThreadDemo { public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread t = new Thread(ft);
t.start();
Integer sum = ft.get();
System.out.println(sum);
}
}
|
三种实现方式的对比

常见的成员方法


线程的优先级

优先级不是绝对的,而是概率问题
1 2 3 4 5 6 7 8 9 10
| package thread.method;
public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package thread.method;
public class ThreadDemo { public static void main(String[] args) { MyRunnable mr1 = new MyRunnable(); MyRunnable mr2 = new MyRunnable();
Thread t1 = new Thread(mr1); Thread t2 = new Thread(mr2);
t1.setName("线程1"); t2.setName("线程2");
t1.setPriority(1); t2.setPriority(10);
t1.start(); t2.start();
} }
|
守护线程
守护线程,当其他非守护线程结束,守护线程也会陆续结束


出让线程

执行结果会出现某一线程执行完后,执行另一个线程。或者是某一个线程多次连续执行,此时出让线程,会将执行结果尽可能均匀一些,并非绝对。

插入线程

主线程和t线程争夺执行权,使用join方法,将t线程插入到主线程之前

线程的生命周期

线程的安全问题
售票问题,简易的售票存在的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package thread.ThreadSafe1;
public class MyThread extends Thread{
static int ticket = 0; @Override public void run() { while (true){ if (ticket<100){ try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } ticket++; System.out.println(this.getName()+"正在卖第"+ticket+"张票"); }else{ break; } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package thread.ThreadSafe1;
public class MyThread extends Thread{
static int ticket = 0; @Override public void run() { while (true){ if (ticket<100){ try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } ticket++; System.out.println(this.getName()+"正在卖第"+ticket+"张票"); }else{ break; } } } }
|
出现问题一的原因:

一开始,线程1、2、3都会抢夺CPU资源,其中线程1抢到后会睡眠、此时不会执行代码,也不会抢夺CPU资源。这时,线程2会抢夺CPU资源,线程3同理。当线程1睡醒,票号++,还未输出票号,CPU资源倍线程2抢夺,票号++,线程3也如此,这导致票号就会出现重复。
出现问题二的原因:

原理与问题一相同,三条线程抢夺资源,都未执行到break语句,票号++,超出了票号。
解决思路:将操作共享数据的代码锁起来,让线程轮流执行代码块

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package thread.ThreadSafe1;
public class ThreadDemo {
public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread();
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package thread.ThreadSafe1;
public class MyThread extends Thread {
static int ticket = 0;
static Object o = new Object();
@Override public void run() { while (true) { synchronized (o) { if (ticket < 100) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } ticket++; System.out.println(this.getName() + "正在卖第" + ticket + "张票"); } else { break; } } } } }
|
细节:通常将锁对象设置为当前类的字节码文件,字节码文件对象唯一,因此可以当做锁对象
同步方法

使用同步方法,实现多线程售票
按照同步代码块的方式书写代码,然后将同步代码块抽取成一个方法,将同步代码块标识删除,在方法上添加synchronized,标注方法类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package thread.ThreadSafe2;
public class MyRunnable implements Runnable {
int ticket = 0;
@Override public void run() { while (true) { if (sellTicket()) break; } }
private synchronized boolean sellTicket() { if (ticket == 100) { return true; } else { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } ticket++; System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); } return false; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package thread.ThreadSafe2;
public class ThreadDemo { public static void main(String[] args) { MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); Thread t3 = new Thread(mr);
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start(); } }
|
如果是单线程,不考虑数据安全,使用StringBuilder
如果是多线程,需要考虑数据安全,使用StringBuffer
lock锁
虽然可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

使用lock锁替代同步代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package thread.lock;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread {
static int ticket = 0;
Lock lock = new ReentrantLock();
@Override public void run() { while (true) {
lock.lock(); if (ticket < 100) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } ticket++; System.out.println(this.getName() + "正在卖第" + ticket + "张票"); } else { break; }
lock.unlock(); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package thread.lock;
public class ThreadDemo {
public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread();
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start(); } }
|
此时会出现的问题是,票号重复、票号超出上限,导致错误的原因是,继承Thread类创建多个MyThread对象,使得锁对象不唯一。
将锁对象设置唯一以后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package thread.lock;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread {
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override public void run() { while (true) {
lock.lock(); if (ticket < 100) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } ticket++; System.out.println(this.getName() + "正在卖第" + ticket + "张票"); } else { break; }
lock.unlock(); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package thread.lock;
public class ThreadDemo {
public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread();
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start(); } }
|
此时会出现的问题是:程序未结束运行。原因在于当票号为100时,直接执行到break代码跳出while循环,会将解锁的代码跳过不执行,此时另外两个线程扔在等待执行。解决方法是利用Finally代码块的特性,将上锁的部分用try代码块包围,程序一定会执行finally代码块,必定会解锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package thread.lock;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread {
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override public void run() { while (true) {
lock.lock(); try { if (ticket < 100) { Thread.sleep(100); ticket++; System.out.println(this.getName() + "正在卖第" + ticket + "张票"); } else { break; } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package thread.lock;
public class ThreadDemo {
public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread();
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start(); } }
|
死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
- 资源有限
- 同步嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class Demo { public static void main(String[] args) { Object objA = new Object(); Object objB = new Object();
new Thread(()->{ while(true){ synchronized (objA){ synchronized (objB){ System.out.println("小康同学正在走路"); } } } }).start();
new Thread(()->{ while(true){ synchronized (objB){ synchronized (objA){ System.out.println("小薇同学正在走路"); } } } }).start(); } }
|
注意避免锁的嵌套
生产者和消费者(等待唤醒机制)
wait和notify
概述
- 生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够对多线程编程的理解更加深刻。
- 所谓生产者消费者问题,实际上主要是包含了两类线程:
- 一类是生产者线程用于生产数据
- 一类是消费者线程用于消费数据
- 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package thread.waitandnodify;
public class Cook extends Thread { @Override public void run() { while (true) { synchronized (Desk.lock) { if (Desk.count == 0) { break; } else { if (Desk.foodFlag == 1) { try { System.out.println(this.getName()+":已经做了一碗了,吃货来吃"); Desk.lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } else { System.out.println(this.getName()+":做好了一碗面"); Desk.foodFlag = 1; Desk.lock.notifyAll(); } } } }
} }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package thread.waitandnodify;
public class Cook extends Thread { @Override public void run() { while (true) { synchronized (Desk.lock) { if (Desk.count == 0) { break; } else { if (Desk.foodFlag == 1) { try { System.out.println(this.getName()+":已经做了一碗了,吃货来吃"); Desk.lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } else { System.out.println(this.getName()+":做好了一碗面"); Desk.foodFlag = 1; Desk.lock.notifyAll(); } } } }
} }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package thread.waitandnodify;
import java.util.concurrent.locks.ReentrantLock;
public class Desk {
public static int foodFlag = 0;
public static int count = 10;
public static Object lock = new Object(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package thread.waitandnodify;
public class ThreadDemo { public static void main(String[] args) { Foodie f = new Foodie(); Cook c = new Cook();
c.setName("厨师"); f.setName("吃货");
f.start(); c.start(); } }
|
Desk可以理解为公共资源类,控制生产者、消费者的执行
生产者、消费者中只有一个锁,两个线程由一个锁限制,这使得生产者、消费者线程只能同时执行一个,另外一个只有等待锁释放以后才能执行,实现类互斥访问
当一个线程调用wait方法,它暂时停止运行,静茹等待状态,同时释放锁,等待其他线程唤醒,并重新竞争获取对象的锁
阻塞队列


- BlockingQueue的核心方法:
- put(anObject): 将参数放入队列,如果放不进去会阻塞
- take(): 取出第一个数据,取不到会阻塞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Cooker extends Thread { private ArrayBlockingQueue<String> bd; public Cooker(ArrayBlockingQueue<String> bd) { this.bd = bd; } @Override public void run() { while (true) { try { bd.put("汉堡包"); System.out.println("厨师放入一个汉堡包"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class Foodie extends Thread { private ArrayBlockingQueue<String> bd; public Foodie(ArrayBlockingQueue<String> bd) { this.bd = bd; } @Override public void run() { while (true) { try { String take = bd.take(); System.out.println("吃货将" + take + "拿出来吃了"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public class Demo { public static void main(String[] args) { ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
Foodie f = new Foodie(bd); Cooker c = new Cooker(bd);
f.start(); c.start(); } }
|
阻塞队列的底层put、take方法中内置了一个锁,这个锁实现了一直put放入直到满后wait,释放锁。take一直拿直到拿完后wait,释放锁。
同一个阻塞队列底层是同一把锁。
线程的状态

java虚拟机中没有运行状态

| 线程状态 |
具体含义 |
| NEW |
一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。 |
| RUNNABLE |
当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。 |
| BLOCKED |
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
| WAITING |
一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。 |
| TIMED_WAITING |
一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。 |
| TERMINATED |
一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |
当线程抢夺到CPU的执行权的时候,虚拟机会将当前线程交给操作系统管理,因此没有定义此状态
线程池
线程池存在的意义:
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗。为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当向线程池提交任务的时候,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。

Executors默认线程池
- 概述 : JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。
- 我们可以使用Executors中所提供的静态方法来创建线程池
- static ExecutorService newCachedThreadPool() 创建一个默认的线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package ThreadPool;
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class ThreadPoolDemo1 { public static void main(String[] args) { ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); pool.submit(() -> System.out.println(Thread.currentThread().getName() + "在执行了"));
} }
|
1 2 3 4 5 6 7 8 9 10 11
| package ThreadPool;
public class MyRunnable implements Runnable {
@Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "----" + i); } } }
|
**Runnable和Callable都是函数式接口, 可以使用lambda表达式**
Executors创建指定上限的线程池
- static ExecutorService newFixedThreadPool(int nThreads) : 创建一个指定最多线程数量的线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.itheima.mythreadpool;
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor;
public class MyThreadPoolDemo2 { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService; System.out.println(pool.getPoolSize());
executorService.submit(()->{ System.out.println(Thread.currentThread().getName() + "在执行了"); });
executorService.submit(()->{ System.out.println(Thread.currentThread().getName() + "在执行了"); });
System.out.println(pool.getPoolSize());
} }
|
ThreadPoolExecutor自定义线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.itheima.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo3 {
public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); pool.submit(new MyRunnable()); pool.submit(new MyRunnable());
pool.shutdown(); } }
|
当所有核心线程执行任务,任务队伍排满任务,才会创建临时线程处理剩余任务
如果任务数量大于核心线程、临时线程、队伍长度之和,就会触发任务拒绝策略

线程池合理大小

额外扩展内容
额外内容