这一部分主要记录了Java中多线程的基础操作,文章结构分为「多线程的实现方法、多线程的基本操作、线程安全」三个部分以及一个「生产者与消费者模型」的实例。
多线程的实现方法
方法一:继承Thread类
需要:线程类 + 主类
表格描述
操作位置 | 操作方式 | 方法名/类名 | 说明 |
---|---|---|---|
线程类(MyThread) | 继承 | Thread | 定义一个类MyThread继承Thread类 |
— | 重写 | void run() | 在MyThread类中重写run()方法 |
主类 | new | 线程类 | 创建MyThread类的对象 |
— | 对象.方法 | void start() | 【启动线程】执行线程类重写的方法 |
代码描述
1 | //MyThread类 |
方法二:实现Runnable接口
主要:线程类 + 主类
表格描述
操作位置 | 操作方式 | 方法名/类名/接口名 | 传参 | 说明 | 备注 |
---|---|---|---|---|---|
线程类(MyRunnable) | 实现 | Runnable | |||
— | 重写 | void run() | *需要获取Thread.currentThread() 才能对类操作 |
||
主类 | new | 线程类(MyRunnable) | 创建MyRunnable类的对象 | ||
— | new | Thread类 | 线程类对象 | 创建Thread类的对象, 把MyRunnable对象作为构造方法的参数 |
*多种构造方法 |
— | 对象.方法 | void start() |
Thread类构造方法
方法名 | 说明 |
---|---|
Thread(Runnable target) | 分配一个新的Thread对象 |
Thread(Runnable target, String name) | 分配一个新的Thread对象 并给线程命名 |
代码描述
MyRunnable类:
1 | public class MyRunnable implements Runnable{ |
主类:
1 | public class MyRunnableDemo { |
好处
- 避免了Java单继承的局限性(Runnable 可以再继承其他类)
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
对线程的操作
主要包括:「获取/设置线程名称、线程调度、线程控制」以及特殊方法「获取当前线程对象的引用」
A:获取当前线程对象的引用
因为对线程的操作需要对“Thread类的引用”操作。所以,在【Runnable类】和【主类】要对当前类操作时,需要用此方法
方法名 | 说明 | 使用方法 |
---|---|---|
Thread currentThread() | 返回对当前正在执行的线程对象的引用 | Thread.currentThread().xxx |
例如:在【Runnable类】和【主类】中要获得线程名称,使用Thread.currentThread().getName()
B:设置/获取线程名称
方法一:使用Thread类方法
方法列表:
方法名 | 说明 |
---|---|
void setName(String name) | 【设置线程名字】为name |
String getName() | 【返回线程名称】 |
方法二:使用带参构造函数命名
多线程实现方法 | 构造函数 | 使用示例 |
---|---|---|
继承Thread类 | Thread(String name) | MyThread my = new MyThread(“小狗”); |
实现Runnable方法 | Thread(Runnable target, String name) | Thread th = new Thread(myRunnable, “小猫”); |
C:线程调度
优先级
主要靠优先级来实现,关于优先级有如下说明:
- 默认优先级:Thread.NORM_PRIORTY (=5)
- 优先级范围:Thread.MIN_PRIORTY (=1) ~ Thread.MAX_PRIORITY (=10)
- 线程优先级高代表获取线程优先级高,不保证先运行
优先级操作方法:
方法名 | 说明 | 示例 |
---|---|---|
final int getPriority() | 获取优先级 | tp1.getPriority() |
final void setPriority(int newPriority) | 设置优先级 | tp1.setPriority(5); |
D:线程控制
控制方法
方法名 | 说明 | 示例 |
---|---|---|
static void sleep(long millis) | 使线程停留指定的毫秒数 | Thread.sleep(1000); *异常处理 |
void join() | 等待此线程死亡后才能执行其他 | tj1.join(); *异常处理 |
void setDaemon(boolean on) | 标记守护线程※ | td1.setDaemon(true); |
控制意义
sleep()
:让这个线程停下来,给别的线程抢资源。让线程们大致均匀的抢资源join()
:让这个线程死亡再执行其他,阻止其他线程抢资源setDaemon(boolean on)
:如果只剩下守护线程,那么 虚拟机就退出
线程安全——同步 synchronized
判断多线程是否会出现数据安全问题的标准
- 是否多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据(含比较、更改、输出等各种操作)
关于锁
锁是通过对象来判定的,对象是同一个,就是同一个锁,会同时锁住。
A:同步代码块(给代码块加锁)
使用位置
锁住“操作共享数据的多条语句”
只要同步代码块锁中传入的对象是同一个,就是同一个锁,会同时锁住
使用格式
1 | private Object obj = new Object();//一定要在外界创建对象,一个对象就是一把锁 |
B:同步(成员)方法(给方法加锁)
使用格式
分类同步方法和同步静态方法,区别就是是否有static
1 | private <static> synchronized void sellTicket(){ |
锁的对象
- 非静态方法:
this
- 静态方法:
类名.class
(此方法在反射中,可以获得类)
C:线程安全的类【理解即可】
线程安全数据类型:
StringBuffer(线程安全可变的字符序列)
- 线程安全,可变的字符序列
- 从JDK 5开始,被StringBuilder 替代。 通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步
Vector(线程安全列表List)
- 从Java 2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector被同步。
- 如果不需要线程安全的实现,建议使用ArrayList代替Vector
Hashtable(线程安全Hash表)
- 该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键或者值
- 从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Hashtable被同步。
- 如果不需要线程安全的实现,建议使用HashMap代替Hashtable
使非线程安全的数据类型变成线程安全(Collections)
- static List synchronizedList ( List list);
- static List synchronizedSet ( Set list);
- static List synchronizedMap ( Map list);
示例:
1 | List<String> list = Collections.synchronizedList(new ArrayList<String>()); |
线程安全——锁 Lock接口
使用方法请关注「代码描述」
Lock说明
- 是接口,采用它的实现类ReentrantLock来实例化
- 比
synchronized
更广放的锁定操作 - 使用于线程类中
- Lock 加锁解锁 要用 try{…}finally{…} 环绕,防止程序出错不能解锁
构造方法
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加解锁方法
方法名 | 说明 |
---|---|
void lock() | 加锁 |
void unlock() | 解锁 |
※Lock 加锁解锁 要用 try{…}finally{…} 环绕,防止程序出错不能解锁
代码描述
1 | public class SellTicket implements Runnable { |
生产者消费者模型
模型概述
是一个经典的多线程模型,提供两个线程和一个数据共享区。数据共享区的存在解耦了生产者和消费者关系。
此程序关键在于数据共享区提供的操作
名称 | 说明 |
---|---|
生产者线程 | 生产数据 |
消费者线程 | 使用数据 |
共享数据区域 | 生产者产生的数据放入共享区域,不关心消费者使用 消费者从共享区域域获取数据,不关心数据生产 |
示意图:
等待与唤醒
使用注意
wite()
与notify()
方法,必须在同步(锁)内部使用;wite()
之后必须要notify()
唤醒方可继续执行;
方法
来自 Object类
方法名 | 说明 |
---|---|
void wait() | 当前线程等待,直到另一个线程调用该对象唤醒( notify()方法或notifyAll()方法) |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
实例【生产者与消费者】
实例指南
生产者消费者案例中包含的类:
- 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
- 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
- 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
- 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下
A:创建奶箱对象,这是共享数据区域 B:创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作 C:创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作 D:创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递 E:启动线程
代码实现
Box类(重点)
1 | public class Box { |
Producer类
1 | public class Producter implements Runnable{ |
Customer类
1 | public class Customer implements Runnable{ |
测试类
1 | public class BoxDemo { |