1. 程序、进程、线程
概念
- 程序:静态的概念(资源分配的单位)
- 进程:运行的程序(调度执行的单位)
- 线程:一个程序中有多个事件同时执行,每个事件一个线程,多个线程共享代码和数据空间
2. 创建线程的方法
- 1.继承thread类(由于类只能单继承,少用)
- 2.实现runnable接口(主要使用)
- 3.实现callable接口(juc下的)
每个线程都有优先级,但优先级只代表优先的概率更大
- 用户线程:必须等用户执行完才能停下,最高优先级
- 守护线程:jvm停止之前,一定会等用户线程,但不等守护线程,最低优先级(只用于服务用户线程)
- 默认状况下,所有线程都是用户线程
3. 通过lambda实现多线程
3.1 什么是lambda
代码语言:javascript复制对于一次性的方法: 外部类-》静态内部类-》局部内部类-》匿名内部类-》lambda
import java.lang.Thread;
interface iLike{
void lambda();
}
public class Test2 {
//静态内部类
// static class Like2 implements iLike{
// public void lambda() {
// System.out.println("Like2");
// }
// }
public static void main(String[] args) {
iLike like = null;
// iLike like = new Like(); //外部类
// like.lambda();
// like = new Like2(); //静态内部类
// like.lambda();
// class Like3 implements iLike{ //方法内部类
// public void lambda() {
// System.out.println("Like3");
// }
// }
// like = new Like3();
// like.lambda();
// like = new iLike() { //匿名内部类
// public void lambda() {
// System.out.println("Like4");
// }
// };
// like.lambda();
//Lambda,是对接口的实现
like = ()->{
System.err.println("labmda Like");
};
like.lambda();
}
}
//外部类
//class Like implements iLike{
//
// public void lambda() {
// System.out.println("Like1");
//
// }
//
//}
lambda是对接口方法的实现,且该接口仅允许有一个方法。
() -> {}():
- ():接口方法的括号,接口方法如果有参数,也需要写参数。若只有一个参数时,括号可以省略。
- -> : 分割左右部分。
- {} : 要实现的方法体。只有一行代码时,可以不加括号,可以不写return。
new Thread(()->System.out.println(“多线程中的lambda”)).start().
4. 线程状态
进入就绪状态的情况:
- 1.Start()
- 2.阻塞解除
- 3.运行时调用yield,(若没有其他线程在排队,则yield无效)
- 4.Jvm线程切换
进入阻塞状态的情况
- 1.Sleep:占用资源等待(不会释放锁),通常用于模拟延时,放大错误发生的可能性(检查线程不安全情况)
- 2.Wait:不占资源等待(会释放锁)
- 3.Jion:合并线程,当此线程执行完毕后,再执行其他线程(在A里面调用B.join(),B插队了,A被阻塞了,等B执行完了A再走)
- 4.IO流阻塞,Read、write:必须由操作系统去调度,因此也会阻塞
线程死亡(一般不要使用自带的stop、destroy摧毁)
- 1.在thread中加一个Boolean flag变量,当其为true的时候执行run。thread对外提供一个停止方法,更改flag的值为false,实现停止。
注意
- 1.setDaemon只能在线程启动之前对其使用
- 2.进入新生状态之后,便拥有自己的工作空间,与主存进行交互
- 3.setPriority 范围1-10,默认5
- 4.解除阻塞都是进入就绪状态,而非进入运行状态!
- 5.解除阻塞都是进入就绪状态,而非进入运行状态!
- 6.解除阻塞都是进入就绪状态,而非进入运行状态!
4.1 Thread.State
State state = t.getState(); (t为写好的线程实例)
- NEW:尚未启动
- RUNNABLE:在JVM中执行(包含就绪状态和运行状态)
- BLOCKED:被阻塞
- WAITING:正在等待另一个线程执行特定动作
- TIMED_WAITING:正在等待另一个线程达到指定运行时间
- TERMINATED:已退出
5. 线程同步
同步锁 synchronized:
锁的是可能被多个人修改的“资源”,而非方法
- 1.调用成员方法锁this,调用静态对象锁class
- 2.对于容器锁定,有对应的并发容器类供使用
死锁:相互等待对方的资源,一般发生在锁套锁的情况。
6. 线程通信(线程同步、并发协作)
Java提供了以下方法:注意只能在同步方法或同步块中使用
方法名 | 作用 |
---|---|
final void wait() | 表示线程一直等待,直到其他线程通知;与sleep不同,wait会释放锁 |
final void wait(long timeout) | 指定等待毫秒数 |
final void notify() | 唤醒一个处于等待状态的线程 |
final void notifyAll() | 幻想同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
7. 生产者与消费者模式
1.一个生产者消费者模型,共有4个类:生产者,消费者,容器,数据。3 N个实例:生产者,消费者,容器,N个数据。 2.生产者和消费者通过容器实现通信,即,双方共用一个容器(双方传入相同的容器) 3.容器实现生产和消费的方法(实际就是容器本身的增删方法) 4.生产者和消费者为多线程,调用容器的增删。容器中的增删方法为同步方法。
7.1 管程法
代码语言:javascript复制public class Consumer2Producter {
public static void main(String[] args) {
Container container = new Container();
Comsumer comsumer = new Comsumer(container);
Product product = new Product(container);
comsumer.start();
product.start();
}
}
//生产者
class Product extends Thread{
Container container;
public Product(Container container) {
this.container = container;
}
public void run() {
for (int i = 1; i <= 10; i ) {
Steamebun bun = new Steamebun(i);
//push是同步方法,存在等待的过程,为保证输出正确,这句话要在push之前
System.out.println("生产了馒头" bun.id "号");
container.push(bun);
}
}
}
//消费者
class Comsumer extends Thread{
Container container;
Comsumer(Container container){
this.container = container;
}
public void run() {
for (int i = 1; i <= 10; i ) {
System.out.println("消费了馒头:" container.pop().id "号");
}
}
}
//数据
class Steamebun{
int id;
Steamebun(int id){
this.id = id;
}
}
//缓冲区(容器)
class Container{
Steamebun[] steamebuns = new Steamebun[10];
int count;
//生产
public synchronized void push(Steamebun steamebun){
if (steamebuns.length==count) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
steamebuns[count ] = steamebun;
this.notify();
}
//消费
public synchronized Steamebun pop() {
if (count==0) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
Steamebun bun = steamebuns[--count]; //先减再用
this.notify();
return bun;
}
}
7.2 信号灯法
跟管程法差不多,区别就在于在容器中利用flag真假来控制线程切换。(没有具体容器上限的时候需要使用信号灯法,因为管程法是利用是否达到容器上限来控制线程切换的)
8. 其他相关
8.1 定时任务
类实现:
- Java.util.Timer 类似闹钟,本身就是一个线程
- Java.tuil.TimerTask 抽象类,实现了runnable,具备多线程能力
任务调度框架:
- QUARTZ(已经集成到Swing中)
- Scheduler-调度器
- Trigger-触发条件,采用DSL模式
- JobDetail-需要处理的JOB
- Job-执行逻辑
- DSL:领域专用语言,声明式编程(简介、连贯的代码解决问题)
- 1.Method Chaining方法链,builder模式构建器
- 2.Nested Functions嵌套函数
- 3.Lambda表达式
- 4.Functional Sequence
8.2 HappenBefore
当多行代码没有依赖的时候,为了减少“内存操作速度慢而CPU速度快导致CPU等待内存而影响效率“的情况,jvm和CPU会尝试重排指令顺序,加快运行速度。这就导致代码不一定按你写的顺序执行。
语句的执行的最后一步,从CPU中将 计算好的值传回内存,在这个过程中,由于CPU速度快,内存速度慢,可能发生计算好的值还没传回内存,新的指令就使用了该值的旧值。
简单理解:语句A和语句B按AB排列,若A执行完毕会非常慢,而jvm判断B又与A没有依赖,那么为了保证CPU不闲置,jvm会提前执行B。
最常见的例子:
- 多线程下,每个线程都有自己的工作空间,当线程A从主内存中拿到数据x之后,对其进行更改;当更改x过程还没结束的时候,线程B就拿走了x,此时就出现了使用旧值的情况。 但是,表面看似B对A没有依赖,但可能因为多线程的关系,在其他线程中AB存在依赖,这就导致不合理的结果。
public class HappenBefore {
static int a;
static boolean flag;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i ) {
a=0;
flag=false;
Thread t1 = new Thread(()-> {
a=1;
flag=true;
});
Thread t2 = new Thread(()-> {
if (flag) {
a*=1;
}
if (a==0) {
System.out.println("HappenBefore a-->" a);
}
});
t1.start();
t2.start();
}
}
}
上面的代码,理想情况下,只会输出a–>0,但实际还会输出a–>1;
8.3 Volatile
为了避免多线程中的数据的HappenBefore,volatile保证了多线程之间变量的可见性。(轻量级的synchronize)
8.4 CAS操作
保证原子性,使用CAS原子操作 对于数据A,在时间轴上有三个值:内存中的A1,”看到的“A,要更新的目标值A2 CAS操作时,将A1与A进行比较,若不相同,则不进行操作,返回flase 若相同,则修改为A2,返回true。
8.5 本地线程(重要)
ThreadLocal【本地线程】
在多线程的程序中,我们一般会申请共享变量,给所有线程使用。 但是很多情况下,每个线程会对共享变量进行改变。 比如,一个String 类型的static变量,线程一将String 赋值为 “线程一”,然后在跑其他的逻辑的时候,第二个线程将String 又改成了“线程二”,那么就会出个问题,线程一跑到后面在对该String进行使用的时候,就发现值已经被改变了。 此时就需要ThreadLocal,相当于,每个线程拥有了独立的数据空间。
优点: 1.通过当前线程的前进,传递对象(而不需要通过一路传参到处传递)(创建线程的局部变量) 2.每个线程使用各自的数据空间,保证线程之间的变量处理互不影响
ThreadLocal的源码非常简单,就是通过一个HashMap存放数据。
代码语言:javascript复制public class ThreadLocal{
private Map<Runnable,Object> container = new HashMap<Runnable,Object>();
public void set(Object value){
container.put(Thread.currentThread(),value);//用当前线程作为key
}
public Object get(){
return container.get(Thread.currentThread());
}
public void remove(){
container.remove(Thread.currentThread());
}
}