Java多线程学习记录

1, 线程是什么?
线程是进程的一个实体,是cpu分派调度的基本单位,同一个进程中可以存在多个线程并发执行
2, 什么是守护线程?
Java中有两种线程,一种是用户线程,另一种是守护线程。
用户线程是指用户自定义创建的线程,主线程停止,用户线程不会停止
守护线程当进程不存在或主线程停止,守护线程也会被停止
// 设定 daemonThread 为 守护线程,default false(非守护线程)
daemonThread.setDaemon(true);
3, 多线程3大特性
原子性、可见性、有序性
1,什么是原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
2,什么是可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
3,什么是有序性
程序执行的顺序按照代码的先后顺序执行。
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。

4, 线程的创建方式
在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述
5, 线程的生命周期
在这里插入图片描述

6, 线程api介绍:
Object类相关api
o.wait() :锁对象调用该方法使当前线程进入等待状态,并立刻释放锁对象,直到被其他线程唤醒进入等锁池。
o.wait(long) :锁对象调用该方法使当前线程进入等待状态,同时释放锁对象。但是超过等待的时间后线程会自动唤醒,或者被其他线程唤醒,并进入等锁池中。
o.wait(long,int) :和o.wait(long)方法一样,如果int参数大于0则前面的long数字加1
o.notify():随机唤醒一个处于等待中的线程(同一个等待阻塞池中)
o.notifyAll():唤醒所有等待中的线程(同一个等待阻塞池中)
Thread类的相关api
Thread.currentThread():返回对当前线程对象的引用Thread.interrupted():检测当前线程是否已经中断(调用该方法后后就将该线程的中断标志位设置位false,所以连续两次调用该方法第二次肯定时false)
Thread.sleep(long millis):使当前线程睡眠(不会释放锁对象,可以让其他线程有执行的机会)
Thread.yield():使当前线程放弃cpu的执行权(有可能立刻又被重新选中继续执行,只可能给优先级更高的线程机会)
t.getId():返回该线程的id
t.getName():返回该线程的名字
t.getPriority():返回该线程的优先级
t.getState():返回该线程的状态
t.getThreadGroup():返回该线程所属的线程组
t.interrupt():将该线程中断(实际并不会中断,只是将中断标志设置为true),如果线程正处在sleep(),join(),wait()方法中时(也就是正在阻塞中)调用该方法,该方法会抛出异常。
t.interrupted():检测该线程是否已经中断(对中断标志位不作处理)
t.isAlive():检测该线程是否还活着
t.isDaemon():检测该线程是否为守护线程
t.isInterrupted():检测该线程是否已经中断
t.join():在a线程中调用b.join(),则a线程阻塞,直到b线程执行完t.join(long millis):和上面的方法一样,不过a线程阻塞的时间根据long的大小有关,如果达到设定的阻塞时间,就算b线程没有执行完,a线程也会被唤醒。

7, 同步代码块,同步方法
锁类型:synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件

线程同步的方法:
同步代码块:
在这里插入图片描述
synchronized(临界资源对象)
{//原子操作}
线程只有获取临界资源对象的锁标记,才能执行其后的{}中的代码,并且必须等{}中的所有的代码都执行完成,才释放该对象的锁标记。如果没有获取临界资源的锁标记,则该线程处于阻塞状态,直到拿到锁标记,才能执行对应的代码。

同步方法:![在这里插入图片描述](https://img-blog.csdnimg.cn/20200707151807620.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDcyNDIzNQ==,size_16,color_FFFFFF,t_70在这里插入图片描述
synchronized返回值类型 方法名(形参)
{//原子操作}
相当于:synchronized (this){//原子操作}

8, JDK1.5-Lock
在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。

Lock lock  = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操作
}finally{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
  lock.ublock();
}

阻塞锁,非阻塞锁,自旋锁,互斥锁
1, 阻塞锁:
多个线程同时调用同一个方法的时候,所有线程都被排队处理了。让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。
2, 非阻塞锁
多个线程同时调用一个方法的时候,当某一个线程最先获取到锁,这时其他线程判断没拿到锁,这时就直接返回,只有当最先获取到锁的线程释放,其他线程才能进来,在它释放之前其它线程都会获取失败。
3, 自旋锁:
在这里插入图片描述
当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环

4, 互斥锁
在这里插入图片描述
互斥锁也是为了保护共享资源的同步,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁。

Lock 接口与 synchronized 关键字的区别
Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
Lock 接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回
9, 如何停止线程

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
  3. 使用interrupt方法中断线程。
    10,什么是ThreadLoca
    ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量。
    当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
    ThreadLocal的接口方法
    ThreadLocal类接口很简单,只有4个方法
    • void set (Object value)设置当前线程的线程局部变量的值。
    • public Object get()该方法返回当前线程所对应的线程局部变量。
    • public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
    • protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

案例:创建三个线程,每个线程生成自己独立序列号。
代码:

class Res {
	// 生成序列号共享变量
	public static Integer count = 0;
	public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
		protected Integer initialValue() {
			return 0;
		};
	};
	public Integer getNum() {
		int count = threadLocal.get() + 1;
		threadLocal.set(count);
		return count;
	}
}

public class ThreadLocaDemo2 extends Thread {
	private Res res;

	public ThreadLocaDemo2(Res res) {
		this.res = res;
	}

	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum());
		}
	}
	public static void main(String[] args) {
		Res res = new Res();
		ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);
		ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);
		ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);
		threadLocaDemo1.start();
		threadLocaDemo2.start();
		threadLocaDemo3.start();
	}
}

结果:
在这里插入图片描述

10,线程池
原理图:
在这里插入图片描述
1,概念解析
核心线程:
有新任务提交时,首先检查核心线程数,如果核心线程都在工作,而且数量也已经达到最大核心线程数,则不会继续新建核心线程,而会将任务放入等待队列。
等待队列:
当核心线程都在忙时,继续新增的任务,核心线程在执行完当前任务后,也会去等待队列拉取任务继续执行
非核心线程:
当等待队列满了,如果当前线程数没有超过最大线程数(maximumPoolSize),则会新建线程执行任务
线程活动保持时间:
线程空闲下来之后,保持存货的持续时间,超过这个时间还没有任务执行,该工作线程结束。
饱和策略:
等待队列已满,线程数也达到最大线程数时,线程池会根据饱和策略来执行后续操作,默认的策略是抛弃要加入的任务。

2,api参数说明
在这里插入图片描述
当线程池任务处理不过来的时候,可以通过handler指定的策略进行处理,ThreadPoolExecutor提供了四种策略:

  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;也是默认的处理方式。
  2. ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
    可以通过实现RejectedExecutionHandler接口自定义处理方式。
    3,Java通过Executors工厂类提供四种线程池
    newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,否则新建线程。(线程最大并发数不可控制)
    newFixedThreadPool:创建一个固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
    newScheduledThreadPool : 创建一个定时线程池,支持定时及周期性任务执行。
    newSingleThreadExecutor :创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    4,合理设置线程池
    CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务
    IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数
    操作系统之名称解释:
    某些进程花费了绝大多数时间在计算上,而其他则在等待I/O上花费了大多是时间,
    前者称为计算密集型(CPU密集型)computer-bound,后者称为I/O密集型,I/O-bound。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值