多线程-初阶
1. 认识线程
1.1 概念
线程是什么?
一个线程就是一个“执行流”。每个线程之间都可以按照顺序执行自己的代码。多个线程之间“同时”执行着多份代码。
为什么要有线程?
并发编程成为刚需,单核 CPU 的发展遇到了瓶颈.。要想提高算力,,就需要多核 CPU.。而并发编程能更充分利用多核 CPU 资源。
有些任务场景需要等待“IO”,为了让等待IO的时间能够去做一些其他的工作,也需要用到并发编程。
其次, 虽然多进程也能实现并发编程, 但是线程比进程更轻量。
创建线程比创建进程更快。
销毁线程比销毁进程更快。
调度线程比调度进程更快。
最后,线程虽然比进程轻量,但还是不满足,于是有了“线程池”(ThreadPool)和“协程”(Coroutine)。
进程和线程的区别
进程是包含线程的。每个进程至少有一个线程存在,即主线程。
进程和进程之间不共享内存空间。同一个进程的线程之间共享同一个内存空间。
进程是资源分配的最小单位,线程是系统调度的最小单位。
1.2 第⼀个多线程程序
每个多线程都是一个独立的执行流。
每个多线程之间是“并发”执行的。
public class ThreadDemo {private static class MyThread extends Thread{@Overridepublic void run(){while ( true){Random random = new Random();System.out.println(Thread.currentThread().getName());try {Thread.sleep(random.nextInt(10));} catch (InterruptedException e) {throw new RuntimeException(e);}}}public static void main(String[] args) {MyThread t1 = new MyThread();t1.start();Random random = new Random();while (true){System.out.println(Thread.currentThread().getName());try {Thread.sleep(random.nextInt(10));} catch (InterruptedException e) {throw new RuntimeException(e);}}}} }
1.3 创建线程
private static class MyThread implements Runnable{@Overridepublic void run(){System.out.println("Thread is running");}public static void main(String[] args) {Thread t=new Thread(new MyThread());t.start();} }
对比上面两种方法:
继承Thread类,直接使用this就表示当前线程对象的引用。
实现Runnable接口,this表示的是MyRunnable的引用。需要使用Thread.currentThread()。
其他变形
匿名内部类创建Thread子类对象
public static void main(String[] args) {Thread t1=new MyThread(){@Overridepublic void run(){System.out.println("aaaa");}};t1.start(); }
Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("使⽤匿名类创建 Runnable ⼦类对象");} });
Thread t3=new Thread(()->{System.out.println("使用匿名内部类创建Thread子类对象"); }); Thread t4=new Thread(()-> System.out.println("使⽤匿名类创建 Thread ⼦类对象"));
2. Thread类及常见方法
Thread类是JVM用来管理线程的一个类,换句话说,每个线程都有一个唯一的Thread对象与之相关。
2.1 Thread的常见构造方法
Thread t1 = new Thread(); Thread t2 = new Thread(new MyRunnable()); Thread t3 = new Thread("这是我的名字"); Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
2.2 Thread的几个常见属性
public class ThreadDemo {public static void main(String[] args) {Thread thread=new Thread(()->{for (int i=0;i<10;i++){System.out.println("Thread is running");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName() + ": 我即将死去");});System.out.println(Thread.currentThread().getName()+ ": ID: " + thread.getId());System.out.println(Thread.currentThread().getName()+ ": 名称: " + thread.getName());System.out.println(Thread.currentThread().getName()+ ": 状态: " + thread.getState());System.out.println(Thread.currentThread().getName()+ ": 优先级: "+ thread.getPriority());System.out.println(Thread.currentThread().getName()+ ": 后台线程: " + thread.isDaemon());System.out.println(Thread.currentThread().getName()+ ": 活着: " + thread.isAlive());System.out.println(Thread.currentThread().getName()+ ": 被中断: " + thread.isInterrupted());thread.start();while (thread.isAlive()) {}System.out.println(Thread.currentThread().getName()+ ": 状态: " + thread.getState());}}
2.3 中断一个线程
目前常见的有以下两种方式:
1.通过共享标记来进行沟通。
2。调用interrupt()方法来通知。
示例1:使用自定义的变量来作为标志位(需要给标志位上加volatile关键字)
public class ThreadDemo {private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic void run() {while (!isQuit){System.out.println(Thread.currentThread().getName()+"别管我,忙着转账呢");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName()+"啊?差点坏了大事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target,"李四");System.out.println(Thread.currentThread().getName()+"让李四开始转账");thread.start();Thread.sleep(10*1000);System.out.println(Thread.currentThread().getName()+ ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");target.isQuit = true;} }
示例2:使用Thread.interrupted()或者Thread.currentThread().isInterrupted() 代替自定义标志位。
public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {while (!Thread.interrupted()){System.out.println(Thread.currentThread().getName()+"别管我,忙着转账呢");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println(Thread.currentThread().getName()+"有内鬼终止交易");break;}}System.out.println(Thread.currentThread().getName()+"啊?差点坏了大事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target,"李四");System.out.println(Thread.currentThread().getName()+"让李四开始转账");thread.start();Thread.sleep(10*1000);System.out.println(Thread.currentThread().getName()+ ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");thread.interrupt();} }
Thread收到通知的方式有两种:
2.4 等待一个线程-join()
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Runnable target =()->{for (int i = 0; i < 10; i++){try {System.out.println(Thread.currentThread().getName()+":我还在工作!");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+":我工作结束了!");};Thread thread1=new Thread( target,"李四");Thread thread2=new Thread( target,"王五");System.out.println("让李四先开始工作");thread1.start();thread1.join();System.out.println("让王五开始工作");thread2.start();thread2.join();System.out.println("程序结束");} }
2.6 获取当前线程引用
public static void main(String[] args) throws InterruptedException {Thread thread= Thread.currentThread();System.out.println("Current Thread: " + thread); }
3. 线程的状态
public static void main(String[] args) {for (Thread.State state: Thread.State.values()){System.out.println(state);} }
4. 多线程带来的风险-线程安全(重点)
4.1 观察线程不安全
public class ThreadDemo {private static int count=0;public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for (int i = 0; i < 1000; i++){count++;}});Thread t2=new Thread(()->{for (int i = 0; i < 1000; i++){count++;}});t1.start();t2.start();//如果没有join,肯定不行,线程还没自增完,就开始打印了。t1.join();t2.join();System.out.println(count);} }
4.2 线程安全的概念
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。
4.3 线程不安全的原因
线程调度是随机的,这是线程安全问题的罪魁祸首,随即调度使一个程序在多线程环境下,执行顺序存在很多的变数。
修改共享数据
上面线程不安全的代码中,涉及到多个线程对count变量进行修改。此时count是一个多线程都能访问到的"共享数据"。
原子性
什么是原子性



5. synchronized 关键字 - 监视器锁 monitor lock
5.1 synchronized特性
1) 互斥
for (int i = 0; i < 50000; i++) {synchronized (locker) {synchronized (locker) {count++;}} }
5.2 synchronized 使用示例
private Object lock = new Object(); public void method(){synchronized ( lock){} }
锁当前对象
public void method(){synchronized ( this){} }
直接修饰方法
public synchronized void method(){}
5.3 Java 标准库中的线程安全类
线程不安全:
6. volatile 关键字
volatile 能保证内存可见性
volatile修饰的变量,能够保证"内存可见性"。
代码示例
public class CounterDemo {static class Counter {public int flag = 0;}public static void main(String[] args) {Counter counter = new Counter();Thread t1 = new Thread(() -> {while (counter.flag == 0) {// do nothing}System.out.println("循环结束!");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("输⼊⼀个整数:");counter.flag = scanner.nextInt();});t1.start();t2.start();} }
t1读的是自己工作内存中的内容。
当t2对flag变量进行修改,此时t1感知不到flag的变化。
如果给flag加上volatile
static class Counter {public volatile int flag = 0; }
volatile 和 synchronized 有着本质的区别。synchronized 能够保证原子性,volatile保证的是内存可见性。
public class CounterDemo {static class Counter {public volatile int flag = 0;}public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i=0;i<1000;i++){counter.flag++;}System.out.println("循环结束!");});Thread t2 = new Thread(() -> {for (int i=0;i<1000;i++){counter.flag++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter.flag);} }
最终count的值仍然无法保证是2000。
7 wait和notify
7.1 wait()方法
wait做的事情:
使当前执行代码的线程进行等待。(把线程放到等待队列中)
释放当前锁
满足一定条件时被唤醒,重新尝试获取这个锁。
wait要搭配synchronized来使用。脱离了synchronized使用wait会抛出异常。
wait结束等待的条件:
其他线程调用该对象的notify方法。
wait等待时间超时。
其他线程调用该等待线程的interrupted的方法,导师wait抛出InterruptedException异常。、
代码示例:
public static void main(String[] args) throws InterruptedException {Object obj = new Object();synchronized ( obj){System.out.println("等待中");obj.wait();System.out.println("被唤醒");} }
7.2 notify()方法
public class WaitTaskDemo {static class WaitTask implements Runnable{private Object locker;public WaitTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker){ // while ( true){try {System.out.println("wait 开始");locker.wait();System.out.println("wait 结束");} catch (InterruptedException e) {e.printStackTrace();} // }}}}static class NotifyTask implements Runnable {private Object locker;public NotifyTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {System.out.println("notify 开始");locker.notify();System.out.println("notify 结束");}}}public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1=new Thread(new WaitTask(lock));Thread t2=new Thread(new NotifyTask(lock));t1.start();Thread.sleep(1000);t2.start();} }
7.3 notifyAll()方法
public class WaitTaskDemo {static class WaitTask implements Runnable{private Object locker;public WaitTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker){ // while ( true){try {System.out.println("wait 开始");locker.wait();System.out.println("wait 结束");} catch (InterruptedException e) {e.printStackTrace();} // }}}}static class NotifyTask implements Runnable {private Object locker;public NotifyTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {System.out.println("notify 开始");locker.notify();System.out.println("notify 结束");}}}public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1=new Thread(new WaitTask(lock));Thread t3=new Thread(new WaitTask(lock));Thread t4=new Thread(new WaitTask(lock));Thread t2=new Thread(new NotifyTask(lock));t3.start();t4.start();Thread.sleep(1000);t2.start(); // t1.start(); // Thread.sleep(1000); // t2.start();} }
只能唤醒一个线程。
将notify修改为notifyAll
@Override public void run() {synchronized (locker) {System.out.println("notify 开始");locker.notifyAll();System.out.println("notify 结束");} }
能同时唤醒3个wait中的线程。
8. 阻塞队列实现
public class BlockingQueue {private int[] items=new int[1000];private volatile int size=0;private volatile int head=0;private volatile int tail=0;public void put(int value) throws InterruptedException {synchronized ( this){// 此处最好使⽤ while.// 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,// 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能⼜已经队列满了// 就只能继续等待while (size==items.length){wait();}items[tail]=value;tail=(tail+1)%items.length;size++;notifyAll();}}public int take() throws InterruptedException {int ret=0;synchronized (this){while (size==0){wait();}ret=items[head];head=(head+1)%items.length;size--;notifyAll();}return ret;}public synchronized int size(){return size;}public static void main(String[] args) throws InterruptedException {BlockingQueue queue = new BlockingQueue();Thread customer=new Thread(()->{try {int value=queue.take();} catch (InterruptedException e) {e.printStackTrace();}},"消费者");customer.start();Thread producer=new Thread(()->{Random random = new Random();try {queue.put(random.nextInt());} catch (InterruptedException e) {e.printStackTrace();}},"生产者");producer.start();customer.join();producer.join();} }
9. 定时器
定时器类似于一个"闹钟",达到一个设定的事件之后,就执行某个指定好的代码。
标准库中的定时器
public static void main(String[] args) {Timer timer=new Timer();timer.schedule(new TimerTask(){@Overridepublic void run() {System.out.println("定时任务执行了");}},3000); }
10. 线程池
线程池的最大好处就是减少每次启动,销毁线程的损耗。
标准库中的线程池
public static void main(String[] args) {//创建出10个线程的线程池ExecutorService pool = Executors.newFixedThreadPool(10);//可以注册一个任务到线程池中pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}}); }
实现线程池
public class MyThreadPoolDemo {static class MyThreadPool{private BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>();//通过这个方法,来把任务添加到线程中public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}//n标识线程池里面有几个线程//创建了一个固定数量的线程池public MyThreadPool(int n){for (int i = 0; i < n; i++){Thread t=new Thread(()->{while (true) {try {//取出任务,并执行Runnable runnable=queue.take();runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}}}public static void main(String[] args) throws InterruptedException {MyThreadPool pool=new MyThreadPool(4);for (int i = 0; i < 1000; i++){pool.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"正在执行任务");}});}}}