多线程
一、创建方式
Java中创建线程主要有三种方式:
- 继承Thread类创建线程类 定义Thread类的子类,并重写该类的run方法 2、通过Runnable接口创建线程类
- 定义runnable接口的实现类,并重写该接口的run()方法 3、通过Callable和Future创建线程
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
二、线程主要方法
Thread类的常用方法如下所示
start();//启动线程
getId();//获得线程ID
getName();//获得线程名字
getPriority();//获得优先权
isAlive();//判断线程是否活动
isDaemon();//判断是否守护线程
getState();//获得线程状态
sleep(long mill);//休眠线程
join();//等待线程结束
yield();//放弃cpu使用权利
interrupt();//中断线程
currentThread();//获得正在执行的线程对象
三、线程同步
线程同步的根本原因,多个线程访问相同的资源,或者叫共享变量
1、volatile
只能保证 “可见性”,不能保证 “原子性”。
多线程机制使得多个任务同时执行处理,所有的线程共享JVM内存区域,而每个线程又单独的有自己的工作内存,当线程与内存区域进行交互时,数据从主存拷贝到工作内存,进而交由线程处理(操作码+操作数)。线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。
Volatile的作用:强制从主存中取得变量的值,而不是从线程工作空间中取得变量的值,从而使变量在多个线程间可见。
由于volatile屏蔽掉了JVM的代码优化,所以在效率上比较低,因此一定在必要时才使用。
2、synchronized
是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
普通同步方法:锁是当前实例对象
静态同步方法:锁是当前类的Class对象
同步方法块:锁是Synchronized括号里配置的对象
先修知识 1:Java 对象头
-
字宽(Word): 内存大小的单位概念, 对于 32 位处理器 1 Word = 4 Bytes, 64 位处理器 1 Word = 8 Bytes
-
每一个 Java 对象都至少占用 2 个字宽的内存(数组类型占用3个字宽)。
- 第一个字宽也被称为对象头Mark Word。 对象头包含了多种不同的信息, 其中就包含对象锁相关的信息。
- 第二个字宽是指向定义该对象类信息(class metadata)的指针
-
非数组类型的对象头的结构如下图
-
说明:
- MarkWord 中包含对象 hashCode 的那种无锁状态是偏向机制被禁用时, 分配出来的无锁对象MarkWord 起始状态
- 偏向机制被启用时,分配出来的对象状态是 ThreadId|Epoch|age|1|01, ThreadId 为空时标识对象尚未偏向于任何一个线程, ThreadId 不为空时, 对象既可能处于偏向特定线程的状态, 也有可能处于已经被特定线程占用完毕释放的状态, 需结合 Epoch 和其他信息判断对象是否允许再偏向(rebias)。
先修知识 2: CAS指令
- CAS (Compare And Swap) 指令是一个CPU层级的原子性操作指令。 在 Intel 处理器中, 其汇编指令为 cmpxchg。
- 该指令概念上存在 3 个参数, 第一个参数【目标地址】, 第二个参数【值1】, 第三个参数【值2】, 指令会比较【目标地址存储的内容】和 【值1】 是否一致, 如果一致,