黑马程序员技术交流社区
标题:
JavaSE第二阶段day11 多线程笔记
[打印本页]
作者:
学习让我快乐啊
时间:
2018-4-23 17:07
标题:
JavaSE第二阶段day11 多线程笔记
本帖最后由 小石姐姐 于 2018-4-26 10:47 编辑
多线程课程笔记
多线程概念
进程: Process, 一个应用程序在内存中的执行区域
线程: Thread, 进程中的一条执行路径
并发: 并行发生, 同时发生, 多线程就可以实现并发
同步: 注意并不是同时的意思, 同步是指一步接一步的执行, 一个执行完毕再开始执行下一个. 单线程就是同步
异步: 不是一步一步执行, 而是同时执行多步, 每个步骤何时结束不确定. 多线程就是异步
阻塞: 上一行代码正在执行, 还没有执行完毕, 程序就阻塞在这里了, 下一行代码必须等上一行不再阻塞后才能执行
单线程和多线程的特点
单线程: 同一时间只做一件事, 安全性高, 效率低
多线程: 同一时间做多个事情, 安全性低, 效率高
多线程的实现方式
继承Thread类, 重写run方法
实现Runnable接口(仍然是创建Thread类对象), 重写run方法
java.lang.Thread类: 实现了Runnable接口
构造方法
Thread Thread(): 创建Thead对象
Thread Thread(Runnable r): 通过Runnable对象创建Thread对象
Thread Thread(Runnable r, String threadName): 通过Runnable对象创建Thread对象并指定线程名
成员方法
void start(): 启动线程, 即让线程开始执行run()方法中的代码
String getName(): 获取线程的名称
void setName(String name): 设置线程名称
静态方法
static Thread currentThread(): 返回对当前正在执行的线程对象的引用
static void sleep(long millis): 让所在线程睡眠指定的毫秒
多线程中的常见问题
资源共享: 卖票问题
共享资源定义位置: 共享资源要定义在多个线程能够共同使用的地方, 如:
多个Thread共用同一个Runnable实现类对象, 则定义为Runnable实现类的非静态成员变量
如果只用Thread子类, 则可以定义为Thread子类的静态成员变量
操作共享数据的线程安全问题: 使用同步解决
同步代码块
synchronized (锁对象) {}
锁对象
必须是多个线程共享的对象:
一个类的Class对象
如果是实现Runnable, 则可以是this
同步方法
public (static) synchronized void method() {}
锁对象
静态同步方法, 锁对象是: 方法所在类的Class对象
非静态同步方法, 锁对象是: this
线程的生命周期
线程的声明周期中有5种状态
创建: new一个线程对象, 此时还没有调用start()
就绪: 调用start()方法后, 进入就绪状态, 等待CPU执行
运行: 获取了CPU的执行权, 开始运行线程
阻塞: 调用了sleep(), wait(), 或由于IO操作导致阻塞. 阻塞解除后仍会返回就绪状态, 等待CPU执行
销毁: 线程执行完毕
+---------------------------+
| 阻塞 |
+--v----------------------^-+
| |
|解除阻塞 被阻塞|
new | |
+------+ +--v---+ +--^---+ +------+
| | start() | >--------------> | 线程执行结束 | |
| 创建 >------------> 就绪 | CPU调度 | 运行 >---------------> 销毁 |
| | | <--------------< | | |
+------+ +------+ +------+ +------+
```
**线程之间的通信**
- 使用`Object`类的成员方法
- `void wait()`: 使当前线程处于等待状态, 并且会立刻释放锁
- `void notify()`: 随机唤醒一个处于等待状态的线程
- `void notifyAll()`: 唤醒所有处于等待状态的线程
- 注意: **这三个方法必须在同步代码块中, 且只能用锁对象来调用, 否则会抛异常**
- `sleep()`和`wait()`的区别
- sleep
- 让当前线程在指定时间内睡眠, 时间结束后继续执行
- 是Thread类的静态方法
- 不会释放锁
- wait
- 让当前线程等待, 直到有人唤醒
- 是Object类的非静态方法
- 等待会立刻释放锁
注意:
wait的线程必须要有人notify才能唤醒, 如果所有的线程都wait了, 那程序整个就全都在等待了. 所以在写代码时必须考虑仔细
线程释放锁的3种情况
synchronized同步代码执行完毕
线程发生了异常导致线程终止
线程调用了wait()方法进行等待, 等待会立刻释放锁
死锁: dead lock
同步代码块中的线程不出来, 也不释放锁; 同步代码块外的线程拿不到锁, 只能等在外面.
发生死锁的原因:
同步代码块内的线程, 可能处在死循环, IO阻塞, sleep()状态, 导致内部持有锁的线程无法出同步代码块
多个线程互相持有锁又不释放锁: 两个线程执行的任务都是双层同步代码块, 每层同步都需要一个锁, 两个线程中同步代码块的锁是相反的
死锁的结果: 程序卡死, 无法继续执行
如何避免死锁:
避免在同步代码块中执行死循环, IO阻塞操作, sleep()
避免多个线程互相持有锁又不释放锁的情况
// 嵌套同步代码块演示死锁
public class Test {
// 定义2个锁对象
public static String lock1 = "{锁1}";
public static String lock2 = "{锁2}";
public static void main(String[] args) {
// 创建2个线程对象
MyThread1 thread1 = new MyThread1();
MyThread2 thread2 = new MyThread2();
thread1.setName("MyThread1");
thread2.setName("MyThread2");
// 开始执行
thread1.start();
thread2.start();
// 注意观察控制台的小红点
}
}
class MyThread1 extends Thread {
@Override
public void run() {
System.out.println(getName() + "线程开始执行run()");
// 外层同步代码块, 使用锁1
synchronized (Test.lock1) {
System.out.println(getName() + "线程拿到了" + Test.lock1);
// 睡一下
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 内层同步代码块, 使用锁2
synchronized (Test.lock2) {
System.out.println(getName() + "持有2把锁");
}
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {
System.out.println(getName() + "线程开始执行run()");
// 外层同步代码块, 使用锁2
synchronized (Test.lock2) {
System.out.println(getName() + "线程拿到了" + Test.lock2);
// 睡一下
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 内层同步代码块, 使用锁1
synchronized (Test.lock1) {
System.out.println(getName() + "持有2把锁");
}
}
}
}
另一种加锁方式: Lock类
JDK5增加
// 创建锁对象, 该锁对象也要所有线程共享唯一一个
private Lock lock = new ReentrantLock();
lock.lock(); // 加锁, 相当于执行到synchronized
// 同步的代码
lock.unlock(); // 释放锁, 相当于同步代码块执行完毕
作者:
Yin灬Yan
时间:
2018-4-24 13:31
我来占层楼啊
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/)
黑马程序员IT技术论坛 X3.2