进程
线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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
接口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());
}
});