黑马程序员技术交流社区
标题:
【石家庄校区】黑马七期day11笔记
[打印本页]
作者:
Black_Y
时间:
2018-4-23 15:03
标题:
【石家庄校区】黑马七期day11笔记
本帖最后由 小石姐姐 于 2018-4-27 11:39 编辑
黑马七期day11笔记
多线程:
进程:当前正在运行的程序,一个应用程序在内存中的执行区域
线程:进程中的一个执行控制单元,执行路径
一个进程可以有一个线程,也可以有多个线程
单线程:安全性高,但是效率低(同一时间只做一件事情)
多线程:安全性低,效率高(同一时间做多件事情)
并发:并行发生,同时发生,多线程就可以实现并发
同步:sync,注意并不是同时的意思,同步是指一步接一步的执行,一个执行完毕再开始执行下一个
单线程就是同步
异步:async ,不是一步一步执行,而是同时执行多步,每个步骤何时结束不确定.
多线程就是异步
创建线程的第一种方式:继承Thread类(Thread:线程)
创建线程的第二种方式:实现Runnable接口
创建线程的另一种方法是声明实现Runnable接口的类,,该类然后实现run方法.然后
创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程.
Runnable接口:用来指定每个线程要执行的任务.包含了一个run的无参数抽象方法,需要由接口实现类重写该方法.
创建线程的步骤:
1.定义类实现Runnable接口
2.覆盖接口中的run方法
3.创建Thread类的对象
4.将Runnable接口的子类对象作为参数传递给Thread类的构造函数.
5.调用Thread类的start方法开启线程.
<<<<<火车站卖票案例>>>>>
Thread是程序中的执行线程.java虚拟机允许应用程序并发地运行多个执行程序.
多线程中的线程安全问题: 售票问题原因分析
原因
线程会在执行过程中根据CPU调度来随机切换, 可能暂停在任何一行代码上
多个线程同时操作了共享数据, 在共享数据还没有发生变化时, 其他线程就对其继续操作, 导致数据不准确
票重复的原因: 拿最开始卖出3张第100张票举例
多线程中的线程安全问题: 使用同步代码块解决共享资源的问题
同步:
表示多个线程在执行同步的代码时, 这些线程只能按顺序一个一个去执行
优缺点
同步安全性高, 效率低
不同步安全性低, 效率高
使用synchronized关键字:
同步代码块: 使用synchronized修饰的代码块
作用:
被同步代码块包裹的代码相当于在一个房间内, 锁对象相当于房间的锁
一个线程进入同步代码块, 会把门锁上, 同步代码块外面的线程看到同步代码块内部有线程, 只能在外面等待
直到同步代码块内部的线程执行完毕从代码块中出来, 释放了锁, 外面等待的线程才能随机进去一个
应用位置:
哪些代码属于共享操作, 需要避免安全问题, 就将这些代码放入同步代码块
术语
线程进入同步代码块, 叫获取锁
线程出同步代码块, 叫释放锁
同步方法: 使用synchronized修饰的方法(下面讲)
锁对象
锁是一个任意类型的对象
Object对象可以, String对象可以, Student对象等也可以...
锁对象必须是被所有线程共享的, 也就是说多个线程都共用一个锁
如使用同一个Runnable对象中的成员变量
或使用某个类的静态变量
或使用某个类的字节码对象
同步必须配合锁对象来使用
出现多线程的安全问题, 检查一下2个方面
对于操作了了共享数据的代码是否是同步的?
同步使用的是否是同一个锁对象?
多线程中的线程安全问题: 使用同步方法解决
同步方法:
synchronized修饰的方法
作用:
该方法在同一时间只能被一个线程执行, 多个线程需要按顺序一个一个调用方法
同步方法的锁对象:
同步方法也有锁对象
静态同步方法:
是方法所在类的Class对象, 也就是该类的字节码对象
非静态同步方法:
是this, 也就是该方法所在类的对象
同步代码块和同步方法如何选择?
如果一段代码的功能是独立的, 会在多个地方重复执行, 且需要同步, 可以定义为同步方法, 便于复用
构造方法
Thread()分配新的Thread对象.
Thread(String name)分配新的Thread对象.将指定的name作为其线程名称
main()方法
也是执行在一个线程中的, 该线程的名称为main, 一般称为主线程, 主线程和其他线程没什么区别
所以main()方法是单线程的, 方法中的代码是一行一行向下执行的
线程的生命周期
线程的声明周期中有5种状态
创建: new一个线程对象, 此时还没有调用start()
就绪: 调用start()方法后, 进入就绪状态, 等待CPU执行
运行: 获取了CPU的执行权, 开始运行线程
阻塞: 调用了sleep(), wait(), 或由于IO操作导致阻塞. 阻塞解除后仍会返回就绪状态, 等待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把锁");
}
}
}
}
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/)
黑马程序员IT技术论坛 X3.2