一、多线程
1.1 进程
线程是依赖于进程。 进程:是正在运行的程序
-
每个系统进行资源分配和调用的独立单位
-
每一个进程都有它自己的内存空间和系统资源
1.2 线程
线程:是进程中的单个顺序控制流,是一条执行路径
- 单线程:只有一条执行路径
- 多线程:有多条执行路径
1.3 多线程的实现方式
软件包:Java.lang public class Thread -> 具体类 extends Object implements Runnable Java虚拟机允许应用程序同时执行多个线程。
方式一:定义一个类继承Thread类,并重写Thread类的run方法。
public class MyThread extends Thread{
@Override
public void run() {
super.run();
for(int i = 0; i <100; i++) {
System.out.println(i);
}
}
}
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//run 方法并没有启动线程
// my1.run();
// my2.run();
//viod start() 导致线程开始执行;Java虚拟机调用此线程的run方法
my1.start();
my2.start();
}
问题一: 为什么要重写run方法? 因为run()是用来封装线程执行的代码 问题二:run()方法和start()方法的区别? run():封装线程执行的代码,直接调用,相当于普通方法的调用 start():启动线程;然后由VM调用此线程的run()方法
方式二:实现Runnable接口 1. 定义一个类实现Runnable接口 2. 在MyThread2类中重写run()方法 3. 创建MyThread2类的对象 4. 创建Thread类的对象,把MyThread2对象作为构造方法的参数 5. 启动线程
1.定义一个类实现Runnable接口
public class MyThread2 implements Runnable{
@Override
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
//这个类只实现类runable接口 没有继承Thread类
}
}
}
public class MyThreadDemo2 {
public static void main(String[] args) {
//2:在MyRunnable类中重写run()方法
MyThread2 my = new MyThread2();
//3:创建MyRunnable类的对象
//4:创建Thread类的对象,把MyRunnable对象作为构造方法的参数
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
//Thread(Runable target, String name)
Thread t1 = new Thread(my,"A");
Thread t2 = new Thread(my,"B");
//启动线程
t1.start();
t2.start();
}
}
多线程的实现方式:
- 继承Tread类
- 实现Runable接口
对比继承Thread类,实现Runable接口的好处:
- 避免Java单继承的局限性
- 适合多个相同的程序代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面对对象的设计思想
1.4设置和获取线程名称
Thread类中设置和获取线程名称的方法
- viod setName(String name): 将此线程的名称更改为等于参数name
- String getName(): 返回线程的名称
- 或者通过构造方法也可以设置线程名称👇
MyThread类
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
Main //Thread(String name)
MyThread my1 = new MyThread("线程1");
MyThread my2 = new MyThread("线程2");
如何获取main()方法所在线程名称? · public static Thread currentThread(): 返回对当前正在执行的线程对象的引用。
System.out.println("Thread.currentThread.getName()");
1.5线程优先级
线程有俩种调度模型
- 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
- 抢占式调度模型:优先级高的线程先用CPU,如果优先级相同,则随机,优先级高的获取时间片相对多一些 Java使用的是抢占式调度模型
在只有一个CPU的时候,CPU在某一时刻只能执行一条指令,线程只有得到时间片才能执行。所以多线程的程序执行是有随机性的。
Thread类中设有获取线程优先级的方法:
| 方法 | 说明 |
|---|---|
| public final int getPriority(): | 放回优先级 |
| public final void setPriority(int newPriority) | 更改优先级 |
线程默认优先级是5;范围是1-10 线程优先级高仅仅表示线程获取CPU的时间片几率高,在次数多/多次运行下,才有对比。
1.6线程控制
| 方法名 | 说明 |
|---|---|
| static void sleep( long millis ) | 是当前线程停止执行指定毫秒数 |
| viod join( ) | 等待此线程死亡 |
| void setDaemon( boolean on) | 标记为守护线程,当运行线程都是守护线程,VM将退出 |
1.7线程生命周期
二、线程同步
2.1数据安全问题的解决
判断多线程程序是否会有数据安全问题的标准:
- 是否多线程环境
- 是否共享数据
- 是否有多条语句操作共享数据
如何解决:创建安全环境
如何实现:给多条语句操作共享数据的代码加锁,让每一时刻只能有一个线程执行
Java提供了同步代码块: 格式:
synchronizd(任意对象){
多条语句操作共享数据的代码
}
相当于给代码加锁,任意对象可以看成是一把锁
同步的好处和弊端:
- 好处:解决了多线程的数据安全问题
- 坏处:当线程很多时,每个线程都会判断同步上的锁,很浪费资源,降低运行效率。
2.2同步方法
同步方法:把synchronized关键字加到方法上
- 格式:修饰符 synchronized 返回值类型 方法名(参数){ }
- 同步方法的锁的对象是:this
同步静态方法:把synchronized关键字加到静态方法上
- 格式:修饰符 static synchronized 返回值类型 方法名(参数) { }
- 步静态方法的锁的对象是:类名.class
2.3线程安全的类
StringBuffer:
- 线程安全,可变的字符序列
- JDK5开始,被StringBuilder替代。通常用StringBuilder类,因为它支持所有相同操作,但它更快,因为不执行同步
Vector:
- 底层数据是数组 - ArrayList
- 从Java2平台v1.2开始,该类改进了List接口,成为Collections Framework的成员。与新的集合实现不同,Vector被同步。如不需要线程安全的实现,建议使用ArrayList替代。
Hashtable:
- 该类实现了一个哈希表,将键映射到值。任何非null对象都可以用作键或者值
- 从Java2平台v1.2开始,该类改进,实现了Map接口,成为Collection Framework的成员。与新的集合实现不用,Hashtable被同步。如不需要线程安全的实现,建议使用HashMap替代。
2.5 Lock锁
JDK5后提供了一个新的锁对象Lock。 Lock实现提供比使用synchronized方法和语句的可以获得更加广泛的锁定操作。
- void lock(): 获得锁
- void unlock(): 释放锁
Lock是接口不能直接实例化,采用它的实现类ReentrantLock来实例化 ReentrantLock的构造方法:
- ReentrantLock():创建一个ReentrantLock实例
三、生产者消费者
生产者消费模式是一个非常经典的多线程协作模式。 实际上主要是包含了俩类线程:
- 一类是生产者线程用于生产数据
- 一类是消费者线程用于消费数据
为了了解生产者和消费者的关系,通常会采用共享的数据区域,像是个仓库。
- 生产者生产数据后直接放置在共享数据区域,并不需要关心消费者的行为
- 消费者直接从共享数据区域中获取数据,并不需要关心生产者的行为
3.1生产者消费者模式概述
为了体现生产和消费过程中的等待和唤醒, Java就提供了几个方法供我们使用,这几个方法在Object类中 Object类的等待和唤醒方法:
| 方法名 | 说明 |
|---|---|
| void wait( ) | 导致当前线程等待,直到另一个线程调用该对象的 notify0方法或notifyl0方法 |
| void notify( ) | 唤醒正在等待对象监视器的单个线程 |
| void notifyAll( ) | 唤醒正在等待对象监视器的所有线程 |