并行和并发
并行:指应用能够同时执行不同的任务,可以同时执行
比如:厨房里有一个锅头,那就可以有一个厨师炒菜,如果有俩个锅头,那就可以有俩个厨师同时炒菜。
并发:描述并发不能脱离时间的描述,它指应用能够交替执行不同的任务,并不是同时执行多个任务,只不过是线程在不断的切换任务我们无法察觉到而已。
比如:描述并发就好像在说,一个厨师在一小时之内可以做出几道菜一样。
进程
指程序运行资源分配的最小单位
我们所说的资源包括:cpu,磁盘io,内存空间。同一进程中的多条线程共享该进程中的全部系统资源,进程和进程是相互独立的。进程是程序在系统的一次执行活动(比如:我们在桌面打开一个文档),程序是死的,静态的;而进程是活的,动态的。进程可以分为系统进程和用户进程:凡是为了完成操作系统的各种功能的进程就是系统进程;用户进程就是我们来启动的进程。
线程
它是cpu调度的最小单位,必须依赖于进程而存在
线程是进程的一个实体,它是比进程更小的、能独立运行的基本单位。线程是无处不在的,任何一个程序都必须要创建线程。
高并发的意义和好处:
1.可以充分利用cpu的资源
2.加快响应用户的时间
3.可以使你的代码更模块化,简单化,异步化
创建线程有几种方式?
查看Jdk官方文档明确给出答复,创建线程的方式有俩种
1.继承thread类
2.实现runnable接口,
Thread和runnable的区别:
Thread是java里对线程的唯一抽象,runnable只是对任务(业务逻辑)的抽象,thread可以接受任意一个runnable的实例并运行。
线程启动
1、类名 extends Thread;然后 类.start
2、类名 implements Runnable;然后交给 Thread 运行
线程中止
stop()方法在终结线程是强制性的,它终止的时候并不一定会保证线程的资源正常释放,所以jdk不建议我们用它。
interrup()方法也可以对线程进行中断,它就好像对调用了这个方法的线程打了个招呼:你要中断了。并不代表会立马中断。Java里的线程是协作式的,不是抢占式的。线程会通过检查自身的中断标志位是否被置为true来进行响应。
isinterrupted()用来判断是否被中断,
interrupted()也可以判断当前线程是否被中断,不过同时会将中断标识位写为false。
run()和start()的区别
我们new thread类只是new 出了一个thread的实例,并没有和线程真正挂起钩来,只有执行了start方法,才真正意义上启动了线程。
Start()让一个线程进入就绪队列等待分配cpu,分配到后调用执行里面的run方法,start方法不能重复调用,不然会抛异常。
Run()方法是业务逻辑的实现,可以重复执行,也可以被单独调用。
yield()
使当前线程让出cpu占有权,但什么时候让出来不可以设定,也不会释放锁资源。【不是每个线程都需要锁的,执行yield的线程也不一定持有锁,我们也可以释放锁之后再调用yield方法】,执行到yield方法的线程会进入到就绪状态,当它分到cpu的时候再去执行。
join():
把指定的线程加入到当前线程,可以把俩个交替执行的线程合并为顺序执行。
就好像打饭排队一样,当前小王【线程a】在排队打饭,然后小王的对象【线程b】来 了,小王调用对象的join方法,那么小王的对象就先打饭,什么时候小王的对象打完 饭了,才会轮到小王。
守护线程:
被用作完成支持性工作,但是注意一点,在java虚拟机推出时daemon线程中的finally块并不一定会执行。守护线程我们一般用不到。
线程的共享
Synchronized:
可以修饰方法和同步代码块的形式来进行使用,它可以确保在同一个时刻,只有一个线程处于方法或者同步块中,保证线程对变量的访问可见性和排他性,又称为内置锁机制。
对象锁:用于对象实例方法上或对象实例上,
类锁:用于类的静态方法或者类的class对象上,
切记加锁一定要加同一把锁(同一个对象),错误的加锁会导致线程的不安全性。
volatile:
最轻量的同步机制,它保证了不同线程对这个变量进行操作时的可见性。一个线程修改了某个变量的值,对其他线程来说立即可见。
注意:volatile不能保证数据在多个线程下同事写时的安全,它比较适用的场景是一写多读。
线程的协作
线程之间的相互配合,比如:一个线程修改了一个对象的值,其他线程感知到了变化,然后进行相应的操作。相当于前者是生产者,后者是消费者。简单的办法就是一直让消费者线程不断循环检查变量,循环条件设置不满足,当满足条件时推出循环。但是会出现一些问题:
(1)无法确保及时性
(2)开销大,如果使用睡眠,睡眠时间少,可能造成消费者无法发现条件变化
等待/通知机制
wait() : 调用该方法的线程进入等待状态,只有等待其他线程的通知或被中断才会返回,调用wait方法会释放对象的锁
wait(long) :超时等待一段时间,时间是毫秒,没有通知就超时返回
wait(long,int) :对于超时时间更细粒度的控制,可以到达纳秒
notify() : 通知一个在对象上等待的线程,使其从wait方法返回,但是返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入等待状态
notifyAll() :同事所有等待在对象上的线程
等待和通知的标准范式
等待遵循原则
- 获取对象的锁
- 如果条件不满足,那么就调用对象的wait()方法,被通知后仍然要检查条件
- 条件满足则执行对应的逻辑
通知原则
1 获得对象的锁
2改变条件
3通知所有等待在对象上的线程
在调用wait()和notify()系列方法之前,线程一定要获得对象的对象级别的锁,就是只能在同步方法或同步块中调用。进入wait()方法,当前线程释放锁。尽量使用notifyAll(),因为notify()只会唤醒一个线程。我们无法保证唤醒的线程一定就是我们需要唤醒的线程。