java学习笔记324-338 进程与线程,同步方法,锁
前言
b站java课程学习笔记整理。
b站视频: 黑马程序员全套Java教程_Java基础入门视频教程,零基础小白自学Java必备教程
324. 进程和线程
线程是依赖于进程而存在。
进程:
- 正在运行的应用程序
- 是系统进行资源分配和调用的独立单位
- 每一个进程都有它自己的内存空间和系统资源
线程:
- 是进程中单个顺序控制流,是一条执行路径。
- 单线程: 一个进程如果只有一条执行路径,则称为单线程程序(例如记事本设置时不能接着打字)
- 多线程:一个进程如果有多条执行路径,则称为多线程程序(例如扫雷游戏进行时,计时器不受鼠标影响正常工作)
325. 继承Thread类的方式实现多线程
线程时程序中执行的线程。Java虚拟机允许应用程序同时执行多个线程。
多线程的实现方式:
方式1:继承Thread
类
- 定义一个类
MyThread
继承hread
类 - 在
MyThread
类中重写run()
方法 - 创建
MyThread
类的对象 - 启动线程
1 | package 线程; |
1 | package 线程; |
326. 设置和获取线程名称
Thread
类中设置和获取线程名称的方法:
void setName(String name)
: 将线程的名称设置为name
String getName()
返回此线程的名称
类要继承Thread
。
可以无参构造或者带参构造线程。Thread.currentThread()
返回当前正在执行的的线程对象的引用。
1 | package 线程; |
1 | package 线程; |
327. 线程优先级
线程有两种调度模型:
- 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用cpu的时间片
- 抢占式调度模型: 优先让优先级高的线程使用cpu,如果线程的时间级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。
Java使用的式抢占式调度模型。
cpu在某一时刻只能执行一条指令,线程只有得到cpu时间片,也就是使用权,才可以执行指令。所以说相同优先级的多线程执行具有随机性。设置和获取线程的优先级:
public final int getPriority()
: 返回此线程的s优先级。public final void setPriority(int newPriority)
: 更改此线程的优先级。- 线程范围的优先级最小值和最大值,默认值(可以设置的范围)为:
MAX_PRIORITY = 10;
和MIN_PRIORITY = 1;
,NORM_PRIORITY = 5.
优先级高只是代表获取时间片的几率高,并不意味着每次都要跑到最前面。要在次数比较多时才能看到效果
328. 线程控制
方法名 | 说明 |
---|---|
static void sleep(long millies) |
使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
void join() |
等待这个线程死亡(必须等待这个线程执行完毕,别得线程才能执行) |
void setDaemon(boolean on) |
将此线程标记为守护线程,主线程执行完毕后,这些守护线程应该立即结束。 |
1 | package 线程; |
329. 线程的生命周期
330. 实现Runnable接口的方式实现多线程
- 定义一个类
MyRunnable
实现Runnable
接口。 - 在
MyRunnable
类中重写run()
方法。 - 创建
MyRunnable
类的对象。 - 创建
Thread
类的对象,把MyRunnable
对象作为构造方法的参数。 - 启动线程。
1 | package Runnable实现多线程; |
1 | package Runnable实现多线程; |
这种方法并没有继承Thread
类,这样他就可以有自己的父类了。
多线程的实现方案:
- 继承
Thread
类 - 实现
Runnable
接口
Runnable
的好处:
- 避免了单继承的局限性。
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想。
331. 卖票
有一百张票,三个窗口同时实现卖票。
思路:
-
定义一个类
SellTicket
实现Runnable
接口,里面定义一个成员变量:private int tickets = 100;
-
在
SellTicket
类中重写run()
方法实现卖票,代码步骤如下:- 判断票数大于0,就卖票,并告知对应的卖票窗口。
- 卖了票之后总数要减一。
- 票没了,也可能有人来问。所以用死循环让卖票动作一直执行。
-
定义一个测试类
SellTicketDemo
,里面有main
方法,代码步骤如下- 创建
SellTicket
类的对象 - 创建三个
Thread
类的对象,把SellTicket
对象作为构造方法的参数,并给出对应的窗口名称。 - 启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package 卖票;
public class SellTicket implements Runnable{
private int tickets = 100;
public int getTickets() {
return tickets;
}
public void setTickets(int tickets) {
this.tickets = tickets;
}
public void run() {
while(true){
if(getTickets()>0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+getTickets()+"张票");
tickets--;
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package 卖票;
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket sellTicket = new SellTicket();
Thread t1 = new Thread(sellTicket,"售票窗口1");
Thread t2 = new Thread(sellTicket,"售票窗口2");
Thread t3 = new Thread(sellTicket,"售票窗口3");
t1.start();
t2.start();
t3.start();
}
}332. 卖票案例的思考
在出一张票的时候需要延迟100ms。
出现三次卖第100张票的原因(相同的票出现了多次):
- t1线程抢到时间片,开始执行,然后休眠进入阻塞态。
- t2线程抢到时间片,开始执行,然后休眠进入阻塞态。
- t3线程抢到时间片,开始执行,然后休眠进入阻塞态。
- t1休息完了,抢到时间片,开始出第100张票。他还没开始做
tickets--
的时候 - t2休息完了,抢到时间片,开始出第100张票。他还没开始做
tickets--
的时候 - t3休息完了,抢到时间片,开始出第100张票。他还没开始做
tickets--
- 执行了三次
tickets--
的操作,票数到了97。但是输出了三次100.
出现卖第负数张票的原因:其实道理是一样的。(1, 0 , -1)。
根本原因:线程执行的随机性。
333. 同步代码块解决数据安全问题
为什么出现问题?
- 是否有多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
如何解决这个安全问题?
- 破坏掉安全问题的环境(去除三个条件其一)
怎么实现呢?
- 把代码锁起来,让任意时刻只有一个进程进入。
- java使用同步代码块
- 格式
1
2
3synchronized(任意对象){
多条语句操作的代码
}synchronized(任意对象){}
就相当于上锁了。 - 创建
1 | package 卖票; |
1 | package 卖票; |
334. 同步方法解决数据安全问题
同步方法就是把synchronized
关键字加到方法上。
- 格式:
- 修饰符
synchronized
返回值类型方法名(方法参数){}
- 修饰符
同步方法锁的是**this
对象**。
同步静态方法:synchronized
关键字加到静态方法上。
- 格式:
修饰符 static synchronized 返回值类型方法名 (方法参数){}
静态同步方法的锁是:
类名.class
335. 线程安全的类
StringBuffer
- 更安全
- 被
StringBuilder
替代(但StringBuilder
不是同步方法,不安全)
Vector
ArrayList
代替Vector
(但ArrayList
不是同步方法,不安全)
Hashtable
HashMap
可以代替,但同样,HashMap
不安全。
多线程环境下使用:
Collections.synchronizedList(new ArrayList<String>() )
包装成一个线程安全的类。
336. Lock锁
Lock
实现提供比使用synchronized
方法和语句可以更广泛的锁定操作。
Lock
中提供了获得锁和释放锁的方法。
void lock()
: 获得锁void unlock()
: 释放锁
Lock
是接口不能直接实例化,这里采用它的实现类ReentrantLock
来实例化。
ReentrantLock
的构造方法:
ReentrantLock()
: 创建一个ReentrantLock
的实例
1 | package 卖票; |
一般使用try...finally
的方法来在程序结束后释放锁。
337. 生产者消费者模式概述
生产者消费者模式:
- 一类是生产者线程用于生产数据
- 一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库。
- 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为。
- 消费者只需要从共享数据区中获取数据,并不需要关心生产者的行为。
为了体现等待和唤醒问题:
方法名 | 说明 |
---|---|
void wait() |
导致当前线程等待,直到另一个线程调用该对象的notify() 方法或notifyAll() 方法 |
void nofity() |
唤醒正在等待对象监视器的单个线程 |
void notifyAll() |
唤醒正在等待对象监视器的所有线程 |
338. 生产者消费者案例
模拟牛奶工送奶。
1 | package 生产者和消费者; |
1 | package 生产者和消费者; |
1 | package 生产者和消费者; |
1 | package 生产者和消费者; |