Java学习笔记-全栈-Java基础-10-多线程

2021-08-18 15:21:50 浏览数 (1)

1. 程序、进程、线程

概念

  • 程序:静态的概念(资源分配的单位)
  • 进程:运行的程序(调度执行的单位)
  • 线程:一个程序中有多个事件同时执行,每个事件一个线程,多个线程共享代码和数据空间

2. 创建线程的方法

  • 1.继承thread类(由于类只能单继承,少用)
  • 2.实现runnable接口(主要使用)
  • 3.实现callable接口(juc下的)

每个线程都有优先级,但优先级只代表优先的概率更大

  • 用户线程:必须等用户执行完才能停下,最高优先级
  • 守护线程:jvm停止之前,一定会等用户线程,但不等守护线程,最低优先级(只用于服务用户线程)
  • 默认状况下,所有线程都是用户线程

3. 通过lambda实现多线程

3.1 什么是lambda

对于一次性的方法: 外部类-》静态内部类-》局部内部类-》匿名内部类-》lambda

代码语言:javascript复制
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。
代码语言:javascript复制
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存在依赖,这就导致不合理的结果。
代码语言:javascript复制
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());
	}
}

0 人点赞