进程 => 一个内存中的应用程序
每个进程都有一个独立的内存空间线程 => 进程中的一个执行路径
进程包含线程, 至少一个线程共享内存空间, 自由切换, 并发执行乐说 =>
电脑运行着QQ, 微信, idea, vscode等10个应用程序(进程), 共有1600多个线程, CPU都没有怎么动诶~ R7牛啤, AMD, YES!!
eg =>
4个面试官找1000个人面试快, 还是1000个人去找面试官快(不用等待, 面试一个就下一个)
同步:
排队执行, 效率低但是安全
异步:
同时执行, 效率高但是数据不安全
并发:
同一时间段发生
并行:
同一时刻发生(相当于同时发生)
相比继承Thread有如下优势:
通过创建任务,然后给线程分配的方式来实现的多线程, 更适合多个线程同时执行相同任务的情况.可以避免单继承所带来的局限性.任务与线程本身是分离的,提高了程序的健壮性.后续学习的线程池技术,接受Runnable类型的任务,不接收Thread 类型的线程. //1 创建一个任务对象 MyRunnable r = new MyRunnable(); //创建线程并给他任务 (任务线程分离提高健壮性) Thread t = new Thread(r); //启动线程 t.start();常用API:
start()
sleep(long millis): 休眠
setDaemon(boolean): 守护线程 (人在塔在) (会自动释放)
Thread的静态方法sleep可直接通过类调用
Thread.sleep(1000);
阻塞 => (广义)消耗时间的操作
线程是一个独立的执行路径, 中断应由其自己决定 (所以stop()强制关闭的方法时不安全的, 故而将其删除了)
所以如何进行中断操作呢 ? => interrupt()方法 main() => thread.interrupt(); //main函数调用interrupt(), 任务捕获线程中断异常, catch块中进行return; 结束任务run(); try { Thread.sleep(1000); } catch (InterruptedException e) { //e.printStackTrace(); System.out.println("发现了中断标记,线程自杀"); return; }会随着主程序运行的结束而结束
//线程分为守护线程和用户线程 //用户线程:当一个进程不包含任何的存活的用户线程时,进行结束 //守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。 Thread t1 = new Thread(new MyRunnable()); //设置守护线程 t1.setDaemon(true); t1.start();多线程抢占式调度可能会出现执行到一半, 另一个线程插手也开始执行的问题, 到临界判断条件时就已发生错误
synchronize => 同步 (锁)
代码示例:
public static void main(String[] args) { Runnable run = new Ticket(); new Thread(run,"A").start(); new Thread(run,"B").start(); new Thread(run,"C").start(); } static class Ticket implements Runnable{ private int count = 20; //相当于锁的一个标识 //锁必须声明在外部, 确定是同一把锁!!同一把!!就是同一个任务!! private Object o = new Object(); @Override public void run() { while (true) { //锁会加在每一次卖票的前面, 所以其他线程有几率会抢到这个锁, 但一开始抢到锁的线程有优势, 再一次进行循环时抢到锁的几率也较大 synchronized (o){ if(count>0){ System.out.println("准备卖票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println(Thread.currentThread().getName() + "卖票结束,余票:" + count); }else { break; } } } } }代码示例:
static class Ticket implements Runnable{ //总票数 private int count = 10; @Override public void run() { synchronized (this){ ... //sale是用同步方法加锁, 这是同步代码块, 但是都是对同一任务进行加锁的 } while (true) { boolean flag = sale(); if(!flag){ break; } } } public synchronized boolean sale(){ ...//加锁方式二 => 同步方法 }注意:
公平锁 => new ReentrantLock(true); => 一百个人, 先来先到, A先来就A先执行
非公平锁 => new ReentrantLock(falese); => 一百个人, 谁抢到这个锁, 谁就先执行
要求 =>
lock() 需要紧跟try代码块
且unlock() 需要在finally代码块的第一行
互相怀疑 => 我预测了你预测我的预测
可见 => 给女朋友讲编程(3) - 线程死锁
各线程通过合作完成同一项任务 => 男女搭配干活不累
wait() 使当前线程等待直到他该醒来的时候
notifyAll() 唤醒在当前方法内睡着的所有线程
经典定义:
生产者生产完资料之后, 进入休眠状态, 消费者对资料进行消费, 消费后生产者继续生产资料
多线程通信其实也是排队的原理, 那为什么不能使用synchronized()同步呢 ?
synchronized加的锁不是公平锁, 所以就会发生消费者在生产者还未生产完资料时就将资料给消费掉 或者 出现生产者不断的抢占锁生产资料而消费者永远抢不到锁的情况 或者 消费者一直对一个资料进行消费, 永远不会结束消费
生产者和消费者所执行的任务不是同一任务, 所以synchronized关键字没办法对其进行修饰就会产生线程不安全的问题, 因此 =>
解决办法=> 休眠和唤醒 + flag标志 + synchronized标识
Class A{ boolean flag = true; public synchronized void b(){ if(flag){ ... flag = false; this.notifyAll();//唤醒所有线程, 包括生产者消费者(b, c) this.wait();//让自己(b)休眠 } } public synchronized void c(){ if(!flag){ ... flag = true; this.notifyAll();//唤醒所有线程(b, c) this.wait();//c休眠 } } }历史:
池 => 容器, 线程通常用来做一个小任务之后就会释放掉
线程的工作流程:
创建线程 => 创建任务 => 执行任务 => 关闭任务
频繁创建线程和销毁线程需要时间, 大大降低系统的效率
因此出现 => 线程池
线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
合理设计线程池的长度, 否则也会令资源有多余的开销
将匿名内部类简化, 利用的是面向函数编程的思想
Thread t = new Thread(() -> System.out.println("Lambda表达式")); t.start();