前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java学习笔记-全栈-Java基础-10-多线程

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

作者头像
devi
发布2021-08-18 15:21:50
3190
发布2021-08-18 15:21:50
举报
文章被收录于专栏:搬砖记录

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());
	}
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/12/15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 程序、进程、线程
  • 2. 创建线程的方法
  • 3. 通过lambda实现多线程
    • 3.1 什么是lambda
    • 4. 线程状态
      • 4.1 Thread.State
      • 5. 线程同步
      • 6. 线程通信(线程同步、并发协作)
      • 7. 生产者与消费者模式
        • 7.1 管程法
          • 7.2 信号灯法
          • 8. 其他相关
            • 8.1 定时任务
              • 8.2 HappenBefore
                • 8.3 Volatile
                  • 8.4 CAS操作
                    • 8.5 本地线程(重要)
                    相关产品与服务
                    容器服务
                    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档