进程
线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eoKunGnP-1620203162745)(/Users/faro_z/Library/Application Support/typora-user-images/image-20210504211610442.png)]
分时调度
抢占式调度
**同步:**排队执行,效率低但是安全
**异步:**同时执行,效率高但是数据不安全
并发:指两个或多个事件,在==同一时间段==内发生。(比如说一天内,一小时内)
**并行:**指两个或多个事件,在同一时间发生(同时发生)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UZDhhlwB-1620203162747)(/Users/faro_z/Library/Application Support/typora-user-images/image-20210504212449234.png)]
一般,比较耗时的操作,可以看成是线程阻塞
比如说,我们要等待用户输入,那么,那一段线程,就是阻塞了
java 中实现多线程,一共有三种方法
public class ThreadDemo { public static void main(String[] args) { new ThreadClass().start(); for (int i = 0; i < 10; i++) { System.out.println("锄禾日当午"+i); } } } class ThreadClass extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("汗滴禾下土"+i); } } }
每个线程都有自己的栈空间,但是共用一个堆空间
public class RunnableDemo { public static void main(String[] args) { //子线程 new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println("汗滴禾下土"+i); } }, "子线程").start(); //主线程(的一部分,整个 main 都是主线程) for (int i = 0; i < 10; i++) { System.out.println("锄禾日当午"+i); } } }
Callable<T>
接口Callable,是带返回值的线程
上面两个实现方法,可以看成主线程之外的子线程去完成任务,和主线程无关了
但是 Callable,相等于主线程让子线程去完成任务,等到子线程任务完成了,再给主线程返回一个结果。比如说,主线程可以派发 1000 个任务,给 1000 个线程,等着 1000 个任务执行完毕了,可以获得 1000 个结果。
Callable 的使用步骤如下:
class Xxx implements Callable<T> { @Override public <T> call() throws Exception { return T; } }
FutureTask<Integer> task = new FutureTask<>(callable);
new Thread(task).start
获取子线程返回结果的方法如下:
get()
方法**注意:**使用get()
,会让主线程等待子线程执行完毕,然后去获得方法
public class CallableDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { MyCallable cl = new MyCallable(); FutureTask<Integer> task = new FutureTask<>(cl); //执行子线程 new Thread(task).start(); Integer res = task.get(); System.out.println("子线程计算结果为:"+res); for (int i = 0; i < 10; i++) { Thread.sleep(100); System.out.println("main"+i); } } } class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { for (int i = 0; i < 10; i++) { Thread.sleep(1000); System.out.println("子线程正在执行"+i); } return 100; } }
isDone()
方法可以先判断子线程有没有执行完,执行完了,再去获取子线程的返回值,避免主线程等待
public class CallableDemo2 { public static void main(String[] args) throws InterruptedException, ExecutionException { MyCallable2 cl = new MyCallable2(); FutureTask<Integer> task = new FutureTask<>(cl); new Thread(task).start(); for (int i = 0; i < 10; i++) { Thread.sleep(1000); if (task.isDone()) { Integer res = task.get(); System.out.println("子线程已经执行完了,结果是:"+res); } System.out.println("main"+i); } } } class MyCallable2 implements Callable<Integer> { @Override public Integer call() throws Exception { for (int i = 0; i < 10; i++) { Thread.sleep(500); // System.out.println("子线程正在执行"+i); } return 100; } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r16OCaP5-1620203162753)(/Users/faro_z/Library/Application Support/typora-user-images/image-20210505154137082.png)]
currentThread()
获取当前线程
public class ThreadDemo1 { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); new Thread(() -> { System.out.println(Thread.currentThread().getName()); }, "子线程").start(); } }
sleep()
线程休眠
public class ThreadDemo1 { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { Thread.sleep(1000); System.out.println("hahaha"); } } }
interrupt()
在早期,有个 stop()
方法,但是,使用 stop 强行让一个线程死亡,可能导致其没来得及释放资源,导致资源的浪费。
所以,我们要使用interrupt()
来让线程察觉到外部标记,然后自己决定死亡
public class ThreadDemo2 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+":"+i); //这里的 InterruptedException ,就是检查有没有被中断的 } catch (InterruptedException e) { //e.printStackTrace(); System.out.println("检测到打扰标记,线程死亡!"); /** * 在死亡前,可以在这里进行资源释放 * 类似于交代后事 */ System.out.println("爷要 si 了,资源都释放掉了!"); //如果要线程死亡,我们只要让 run 方法 return 就好了 return; } } }, "子线程"); t1.start(); for (int i = 0; i < 5; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+":"+i); } /** * 给线程 t1 添加中断标记 */ t1.interrupt(); } }
setDaemon()
用户线程:当一个进程不包括任何一个存货的用户线程时,进行结束
**守护线程:**当最后一个用户线程结束后,守护线程自动死亡
==注意:==守护线程的设置,一定要在线程启动之前
public class ThreadDemo3 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + i); } }, "守护线程"); /** * 将 t1 设置为守护线程 * 一定要在 start 之前,进行守护线程的设置 */ t1.setDaemon(true); t1.start(); for (int i = 0; i < 5; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+":"+i); } } }
join()
join()
的作用,是保证子线程,在主线程之前完成
如果没有设置超时,那么,主线程在子线程完成之前,不会与之抢夺时间片
public class JoinDemo { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }, "子线程"); t.start(); t.join(); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()); } } }
join(int time)
还可以设置子线程排在主线程前面的时间:
其他代码不变,这里,我们设置子线程排在主线程前面的时间为
并为主线程每次打印增加等待
可以看到,在两秒后,主线程便开始与子线程抢夺时间片:
public class ThreadDemo4 { public static void main(String[] args) { MyThread t = new MyThread(); new Thread(t,"A").start(); new Thread(t,"B").start(); new Thread(t,"C").start(); } } class MyThread implements Runnable { private int ticket = 10; @Override public void run() { while (ticket>0) { System.out.println(Thread.currentThread().getName()+"开始卖票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println(Thread.currentThread().getName()+"余票:"+ticket); } } }
public class ThreadDemo5 { public static void main(String[] args) { MyThread2 t = new MyThread2(); new Thread(t,"A").start(); new Thread(t,"B").start(); new Thread(t,"C").start(); }}class MyThread2 implements Runnable { //临界资源区 private int ticket = 10; /** * 锁的对象,是要是唯一的,都可以 * 有时候为了不使用自定义的锁,甚至可以使用 Class 作为锁 * 比如使用 MyThread2.class */ private static final Object lock=new Object(); @Override public void run() { while (true) { synchronized (lock) { if (ticket<=0) break; else { System.out.println(Thread.currentThread().getName()+"开始卖票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println(Thread.currentThread().getName()+"余票:"+ticket); } } } }}
即在方法前,加上 synchronized 关键字
同步方法,其实使用的是隐式锁
如果是同步方法,锁的是当前对象
如果是静态方法,锁的是该类的 Class
使用 JUC 里的 lock
public class ThreadDemo6 { public static void main(String[] args) { MyThread3 t = new MyThread3(); new Thread(t,"A").start(); new Thread(t,"B").start(); new Thread(t,"C").start(); } } class MyThread3 implements Runnable { private int ticket = 10; private Lock lock = new ReentrantLock(); @Override public void run() { while (true) { lock.lock(); if (ticket<=0) break; else { System.out.println(Thread.currentThread().getName()+"开始卖票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println(Thread.currentThread().getName()+"余票:"+ticket); } lock.unlock(); } } }
之前我写过一个详细的 synchronized 用法的博客,点此跳转
或者看 java/JUC/多线程.md
**公平锁:**先来,先得到锁
**非公平锁:**大家争抢这个锁
我们一般使用的,都是非公平锁(synchronized,ReentrantLock)
在构造 ReentrantLock 时,传入一个 true,表示这是一个公平锁
Lock lock = ReentrantLock(true);
什么是死锁?
线程 A,拿到 A锁,执行任务,中途又需要 B 锁
线程 B,拿到 B锁,执行任务,中途又需要 A 锁
如果这两个线程是同时执行的,那 A拿不到 B 锁,B拿不到 A 锁,就会相互僵持,出现死锁
比如说我们想要在下载完以后,自动播放电影
那么,当下载线程结束以后,要去通知播放线程
这,就是多线程通信
多线程通信的实现,是:
Object 里的 wait()
和notify()/notifyAll()
方法
有了wait()
和notify()/notifyAll()
方法,我们就可以解决生产者和消费者问题
所谓生产者和消费者问题,就是设置一个缓存,当缓存为空,消费者不能消费,生产者必须生产;当缓存已经满了,生产者必须停止,消费者必须消费;剩下的时候,生产者、消费者可以随意。
因为这个缓存是临界区,所以必须加锁
但是,光加锁,不能解决生产者、消费者问题。如果在任何时候,都让生产者和消费者自由争抢时间片,那如果缓存已满,可能生产者还会抢到时间片,这样,就会让缓存溢出。
这个时候,就需要用到线程通信,来解决生产者和消费者问题。
我们定义三个类:
**Cook:**生产者,每次做和上一次不一样的菜
**Waiter:**消费者,每次端出菜
**Food:**食物,缓存长度为 1,即没有菜的时候,cook必须做菜,waiter 不能端菜;有菜的时候,cook 不能做菜,waiter 必须端菜
public class ProducerAndCustomer { public static void main(String[] args) { Food food = new Food(); new Thread(new Cook(food)).start(); new Thread(new Waiter(food)).start(); } } class Waiter implements Runnable { private Food food; public Waiter(Food food) { this.food = food; } @Override public void run() { for (int i = 0; i < 100; i++) { /** * 这里因为要访问临界资源 isEmpty,所以必须加锁 */ synchronized (food) { if (!food.isEmpty) { food.get(); food.isEmpty=true; food.notifyAll(); try { food.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } class Cook implements Runnable{ private Food food; public Cook(Food food) { this.food = food; } @Override public void run() { for (int i = 0; i < 100; i++) { /** * 这里因为要访问临界资源 isEmpty,所以必须加锁 */ synchronized (food) { if (food.isEmpty) { if (i%2==0) { food.setName("煎饼果子"); food.setTest("咸味"); } else { food.setName("豆腐脑"); food.setTest("超甜味"); } System.out.println("厨师把菜做好了,菜品为:"+food.toString()); //厨师做好了菜,将盘子设置为不空 food.isEmpty=false; //唤醒其他线程 food.notifyAll(); //当前线程休眠 try { food.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } class Food { private String name; private String test; public boolean isEmpty=true; public Food(String name, String test) { this.name = name; this.test = test; } public Food() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getTest() { return test; } public void setTest(String test) { this.test = test; } public void get() { System.out.println("服务员端走了菜:"+this.toString()); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("名称:").append(this.name).append("\n") .append("味道:").append(this.test).append("\n"); return sb.toString(); } }
结果:
我们可以看到,是严格按照先做菜,再端菜的顺序执行的。
线程一共有六种状态
如果我们要使用大量线程,每个线程执行的时间很短,那每次创建、销毁线程,就会浪费大量时间。所以,我们要先提前创建部分线程,要用的时候,去里面取,不用的时候,再放回去
虽然好处多多,但是,我们在日常开发的时候,使用自定义线程池的几率少之又少,因为 Java 本来就是按照多线程去设计的,不用我们再手动创建线程池。
一共有四种线程池
/** 缓存线程池. (长度无限制) 执行流程: 1. 判断线程池是否存在空闲线程 2. 存在则使用 3. 不存在,则创建线程 并放入线程池, 然后使用 * * * * * * */ ExecutorService service = Executors.newCachedThreadPool(); //向线程池中 加入 新的任务 service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } });
/** * 定长线程池. * (长度是指定的数值) * 执行流程: * 1. 判断线程池是否存在空闲线程 * 2. 存在则使用 * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 */ ExecutorService service = Executors.newFixedThreadPool(2); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } });
有时候,我们可能需要多个作业排队执行,这个时候,就需要用到这种线程池了
效果与定长线程池 创建时传入数值1 效果一致. /** * 单线程线程池. 执行流程: * 1. 判断线程池 的那个线程 是否空闲 * 2. 空闲则使用 * 4. 不空闲,则等待 池中的单个线程空闲后 使用 */ ExecutorService service = Executors.newSingleThreadExecutor(); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } });