JAVA_14


JAVA_多线程

1.程序、进程、线程

  1. 多线程是Java语言的重要特性,大量应用于网络编程、服务器端程序的开发,最常见的UI界面底层原理、操作系统底层原理都大量使用了多线程。
  2. “程序(Program)”是一个静态的概念,一般对应于操作系统中的-个可执行文件,比如:我们要启动酷狗听音乐,则对应酷狗的可执行程序。当我们双击酷狗,则加载程序到内存中,开始执行该程序,于是产生了“进程”。
  3. 执行中的程序叫做进程(Process),是一个动态的概念。现代的操作系统都可以同时启动多个进程。比如:我们在用酷狗听音乐,也可以使用wps写文档,也可以同时用浏览器查看网页。进程具有如下特点:

1.进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是结构的基础。
2.每个进程由3部分组成:cpu、data、code。每个进程都是独立的,保有自己的cpu时间,代码和数据。
3.进程的查看
Windows系统:Ctrl+Alt+Del,启动任务管理器即可查看所有进程。
Linux系统: ps or top。

  1. 一个进程可以产生多个线程。同一进程的多个线程也可以共享此进程的某些资源(比如:代码、数据),所以线程又被称为轻量级进程(lightweight process).

1.是能够进行运算的最小单位。它被包含在之中,是中的实际运作单位。
2.一个进程可拥有多个线程
3.一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且它们从同一堆中分配对象并进行通信、数据交换和同步操作。
4.线程的启动、中断、消亡,消耗的资源非常少。
5.每个java程序都有一个主线程:main thread(对应main方法启动)

  1. JAVA中线程和操作系统线程的关系(源码解读)
    green threads 是一种由运行环境或虚拟机(VM)调度,而不是由本地底层操作系统调度的线程。绿色线程并不依赖底层的系统功能,模拟实现了多线程的运行,这种线程的管理调配发生在用户空间而不是内核空间,所以它们可以在没有原生线程支持的环境中工作。
    在Java 1.1中,绿色线程是JVM 中使用的唯–种线程模型。
    在 Java1.2 之后,Linux中的JVM是基于pthread实现的,即 现在的Java中线程的本质,其实就是操作系统中的线程。
    我们分析Thread类的start(),就能看出最终调用的是native方法start0(),也就是调用了操作系统底层的方法。
  2. 通过继承Thread类实现多线程
    继承Thread类实现多线程的步骤:

1.在Java中负责实现线程功能的类是java.lang.Thread 类。
2.可以通过创建 Thread的实例来创建新的线程。
3.每个线程都是通过某个特定的Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。
4.调用Thread类的start()方法来启动一个线程,

public class TestThread extends Thread { //继承Thread类
	//重写run()方法。run()是线程体
	@0verride
	public void run(){
		for(int i=0;i<10;i++){
		System.out.println(this.getName()+":"+i); //getName()返回的是线程名称
		}
	}
	public static void main(string[] args){
		TestThread t1=new TestThread(); //创建线程对象
		t1.start();//启动线程
		TestThread t2 = new TestThread();
		t2.start();
	}
}	

2.java线程两种实现方式

  1. 通过继承 Thread 类实现多线程
    维承 Thread 类文现多线程的步骤:

1.在 Java 中负古实现线程功能的类是 java.lang.Thread 类。
2、可以通过创建 Thread 的实例米创建新的线程。
3、每个线程都是通过 nun()来完或其操作的,方法 run()称为线程休,
4、调用 Thread 类的 start0方法来启动一个线程.

  1. 通过 Runnable 接口实现多线程
    在开发中,我们应用更多的是通过 Runnable 接口实现多线程。这种方式克服了继承Thread 类的缺点,即在实现 Runnable 接口的同时还可以继承某个类。
  2. 使用lambda 创建(JDK8 新增)
//通过匿名内部类的方式创建线程
new Thread(new Runnable(){
	@0verride
	public void run(){
		for(int i=0;i<10;i++){
			System.out.println(Thread.currentThread().getName()+":"+i);
	}).start();


	new Thread(()->{
	for(int i=0;i<10;i++){
		System.out.println(Thread.currentThread().getName()+":"+i);
	}).start();;

3.线程状态

  1. 线程有五种状态:新生状态、就绪状态、运行状态、阻塞状态、死亡状态。
  2. 可以通过设置一个boolean变量live终止线程
  3. 线程常用方法
方法功能
isAlive()判断线程是否还“活”着,即线程是否还未终止
getPriority()获得线程的优先级数值
setPriority()设置线程的优先级数值
setName()给线程一个名字
getName()取得线程的名字
currentThread()取得当前正在运行的线程对象,也就是取得自己本身

4.线程同步和安全性

  1. 同步问题的提出
    现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比如:教室里只有一台电脑,多个人都想使用。天然的解决办法就是,在电脑旁边,大家排队。前一人使用完后,后一人再使用。
  2. 线程同步的概念
    多个线程访问同一个对象,并且某些线程还想修改这个对象。这时,我们就需要用到“线程同步”。 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
  3. 实现线程同步
    由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java 语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。
    这个机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和synchronized 块。
  4. synchronized 方法
    通过在方法声明中加入 synchronized 关键字来声明,语法如下
public synchronized void accessVal(int newVal);

synchronized 方法控制对“对象的成员变量”的访问:每个对象对应一把锁,每个synchronized 方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法-旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁重新进入可执行状态。

  1. synchronized 块
    synchronized 方法的缺陷:若将一个大的方法声明为 synchronized 将会大大影响效

    Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
    synchronized 块:通过 synchronized 关键字来声明 synchronized 块,语法如下:
synchronized(syncObject){
//允许访问控制的代码
}

5.生产者消费者模式

  1. 什么是生产者?
    生产者指的是负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。
  2. 什么是消费者?
    消费者指的是负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。
  3. 什么是缓冲区?
    消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据,
  4. 线程协作和通信
    线程并发协作(也叫线程通信),通常用于生产者/消费者模式。需要使用wait()/notify()/notifyAll()方法。
    线线程通信常用方法
方法名作 用其他说明
final void wait()表示线程-直等待线程执行 wait()方法时候,会释放当前的锁,然后让出 CPU,进入等待状态
void wait(long timeout)线程等待指定毫秒参数的时间线程执行 wait()方法时候,会释放当前的锁,然后让出 CPU,进入等待状态
final void notify()唤醒一个处于等待状态的线程
final void notifyAll()唤醒同一个对象上所有调用 wait0方法的线程
以上方法均是java.lang.Object 类的方法;都只能在同步方法或者同步代码块中使用,否则会抛出异常。
//馒头类
class Mantou {
	int id;
	public Mantou(int id){this.id=id;}
}
class Syncstack{ //缓冲区(馒头)、push压栈、pop出桟
	int index = 0;
	Mantou[] ms = new Mantou[10];
	public synchronized void push(Mantou mantou){
		while(index==ms.length){//说明慢头满了
			try {
				//wa1t()后,线程会将持有的对象锁释放,进入等待池(等待状态)
				//这样,锁池中的线程就可以竞争获得对象锁。
				this.wait();
			}catch(InterruptedExceptione){e.printStackTrace();}	
		}
		//唤醒在当前对象的等待池中的一个线程,进入当前对象的锁池
		this.notify();
		ms[index] = mantou;
		index++;
	}
	public synchronized Mantou pop(){
		while(index==0){ //馒头筐空了
			try {
				//如果馒头筐空了。消费线程就进入等待。
				this.wait();
			}catch(InterruptedException e){e.printstackTrace();}
		}
		this.notify();
		index--;
		return ms[index];
	}
}
class Produce extends Thread{ //生产者线程
	SyncStack ss = null;
	public Produce(syncstack ss){
		this.ss = ss;
	}
	@Override
	public void run(){
		for(int i=0;i<10;i++){
			System.out.println("生产慢头:"+i);
			Mantou m = new Mantou(i);
			ss.push(m);
		}
	}
}
class Consumer extends Thread {
	SyncStack ss =null;
	public Consumer(Syncstack ss){
		this.ss = ss;
	}
	@0verride
	public void run(){
		for(int i=0;i<10;i++){
			Mantou m= ss.pop();
			System.out.println("消费馒头:"+m.id);
		}
	}
}

6.线程池

  1. 提交任务->核心线程池已满->线程池任务已满->总线程池已满->按饱和策略处理无法执行的任务
    否 创建新线程执行任务 任务添加到队列,等待执行 创建非核心线程执行任务
  2. 池化技术
    池化技术指的是提前准备一些资源,在需要时可以重复使用这些预先准备的资源。它有两个优点:
    1.提前创建,
    2.重复利用。
    常见的池化技术的使用有:线程池、数据库连接池、Httpclient 连接池等
  3. 线程池是什么?
    通俗点讲,当有工作来,就会向线程池拿一个线程,当工作完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用。
    这样就避免了频繁的创建线程、销毁线程。极大的提高的响应速度。假如创建线程用的时间为 T1,执行任务用的时间为 T2,销毁线程用的时间为 T3,那么使用线程池就免去了 T1和 T3 的时间;
  4. 线程池的优势:
    1.解耦作用;线程的创建于执行完全分开,方便维护。
    2.重复使用,降低系统资源消耗:通过重用已存在的线程,降低线程创建和销毁造成的消耗。
    3.统一管理线程:控制线程并发数量,降低服务器压力,统一管理所有线程;
    4.提升系统响应速度。使用线程池以后,工作效率通常能提高 10 倍以上
  5. handler 的拒绝策略
    1.AbortPolicy.不执行新任务,直接抛出异常,提示线程池已满(默认)
    2.DisCardPolicy:不执行新任务,也不抛出异常
    3.DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行4.CallerRunsPolicy:直接调用 execute 来执行当前任务
  6. 四种内置线程池的使用
    1.CachedThreadPool(百战工厂:全是临时工干活)
    该线程池中没有核心线程,非核心线程的数量为Integer.max value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
    2.SecudleThreadPool(百战工厂:有正式工、也有临时工)
    周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。也可以执行周期性的任务。
    3.SingleThreadPool(百战工厂:只有一个正式工)
    只有一条线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO 先进先出, LIFO 后进先出,优先级)执行适用于有顺序的任务的应用场景。
    4.FixedThreadPool(百战工厂:全是正式工)
    定长的线程池,只有核心线程,核心线程的即为最大的线程数量,没有非核心线程。

7.ThreadLocal

  1. ThreadLocal 是什么?常见误解

1.ThreadLocal 很容易让人望文生义,想当然地认为是一个“本地线程”
2.很多人会说,ThreadLocal 是为了避免线程同步问题,个人认为不太恰当,

  1. 正确说法
    1.ThreadLocal为每个线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
    2.ThreadLocal 更多是为了实现数据隔离、避免多次传参问题,围绕这点,有很多场景:

1、(数据隔商)进行事务探作,用于存储线程事务信息(Spring 实现事务隔高级别的源码)
2、(数据隔离)数据库连接管理
3、(数据隔离) Cookie、Session 的管理,
4、(数据隔离) Android 开发中,Looper 类
5、(避免多次传参)在进行对象跨层传递的时候,使用 ThreadLocal可以避免多次传递,打破层次间的约束。

  1. 内存泄露
    不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露
    内存泄漏产生的原因:长生命周期对象持有短生命周期的对象导致短牛命周期对象无法被释放。
  2. 强引用与弱引用
    1.强引用
    最普遍的引用,一个对象具有强引用,会被垃圾回收器回收。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不回收这种对象。
    如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为 null,这样可以使 JVM 在合适的时间就会回收该对象。
    2.弱引用
    进行垃圾回收时,无论内存是否充足,会回收只被弱引用关联的对象。java中,用java.lang.ref.WeakReference 类来表示
    3.ThreadLocal如何引起内存泄漏
    弱引用可能会造成内存泄漏。如果 ThreadLocal 只被弱引用会被 GC 回收。回收后没有再调用 get0)/set0)/remove0)方法,从而造成 value 对象无法回收。
    但ThreadLocal内存泄漏的真正根源是:由于ThreadLocalMap 的生命周期跟Thread 一样长,如果没有手动删除对应 key 和 value,就会导致内存泄漏,而不一定是弱引用。
  3. ThreadLocal 正确的使用方法
    每次使用完 ThreadLocal变量,都调用它的 remove()方法清除数据,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值