黑马程序员技术交流社区
标题: 学习经历Day03-Day06 [打印本页]
作者: 键盘有花生油 时间: 2018-11-20 16:17
标题: 学习经历Day03-Day06
本帖最后由 键盘有花生油 于 2018-11-20 16:40 编辑
l 多线程的结果的随机性打印结果。
抢占式,本质是多个线程在抢夺cpu的资源,
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg
l 多线程的内存示范
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg
l 获取当前线程的名称
1. String get.name()
2. currentThread静态方法直接掉用,获取名称
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image006.png
l 链式编程
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image008.png
l 设置线程名字:
1. set.Name
2. 创建一个带参数的构造方法,参数传递线程名称,
修改添加线程创建对象以后 对象名.setname
修改main线程的名字直接在main方法中Thread.setname
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image010.png
cfile:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image012.png
l Thread底下的sleep方法:
表示钟表内部的时间的,让它放慢1秒后执行
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image014.png
l 创建多线成的第二种方法Runnable
创建多线程程序的第二种方式:实现Runnable接口
java.lang.Runnable
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
java.lang.Thread类的构造方法
(1)Thread(Runnable target) 分配新的 Thread 对象。
(2)Thread(Runnable target, String name) 分配新的Thread 对象。
实现步骤:
树
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
Thread和实现Runnable接口创建多线程程序的好处:
1.避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
耦合性: 相互之间的关系的紧密程度
耦合性高: 相互之间的关系非常紧密
耦合性低: 相互之间的关系不太紧密
我们追求 "低耦合"
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image016.png
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image018.png
匿名内部类创建接口
new 父接口/父类() {
重写方法
};
继承Thread类
实现Runnable接口
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image020.png
l 线程安全的问题图解:模拟电影院卖票:线程安全问题概述
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image022.jpg
问题发生场景:
多个线程操作共享资源
问题发生原因:
JVM是抢占式调度, CPU在每个线程之间切换是随机的, 代码执行到什么位置是不确定的
在操作共享资源时, 由于一个线程还没有执行完, 另一个线程就来操作, 就会出现问题
如何解决:
在操作共享资源时, 让线程一个一个来执行, 不要并发操作共享变量, 就可以解决问题
l 解决线程安全的解决问题方式1:同步代码块
1. 同步技术三种
(1) 同步代码块
解决线程安全问题的一种方案:使用同步代码块
格式:
树
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
同步代码块就类似于房间和房门
房间
+------+
| | 线程1
| \ 门锁 线程2
| | 线程3
+------+
线程进入房间后, 锁上房间, 其他线程就进不来
+------+
| |
|线程1| 线程2
| | 线程3
+------+
线程1做完事情, 打开锁出了门, 其他线程就可以抢着进来
+------+
| |
| \线程2
| | 线程3
+------+
线程1
同步代码块图解
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image024.png
l 同步技术的原理
树
锁对象, 也称为"同步锁","对象锁", "对象监视器"
l 同步的原理:
线程进入同步代码块前, 会"争夺锁对象","只有一个线程"会抢到锁对象
进入同步代码块的线程, 会"持有锁对象",并执行同步代码块中的代码
此时同步代码块外的线程, 处于"阻塞"状态, 只能等待
当同步代码块内的线程执行完代码块, 会离开同步代码块, 并"归还锁对象"给同步代码块
等在同步代码块外的其他线程就可以继续争夺锁对象
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image026.jpg
l 解决线程安全问题的二种方案:使用同步方法
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符
格式:定义方法的格式
树
修饰符synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
非静态同步方法的锁对象: this
静态同步方法:
publicstatic synchronized void method(){
//可能会产生线程安全问题的代码
}
静态同步方法的锁对象: 当前类的字节码对象 Class对象
RunnableImpl.class -> Class对象
l 获取一个类的字节码对象的3种方式:
1. 对象名.getClass() new RunnableImpl().getClass()
2.类名.class RunnableImpl.class
3.Class.forName("类的全名"); Class.forName("com.itheima.test05.RunnableImpl");
l 字节码对象的特点:
同一个类, 他的字节码对象只有"唯一的一个"
Person
newPerson() this
new Person() this
..
Person.class 只有一个
锁对象必须是多个线程共享的同一个对象
}
l 第三种 方式
解决线程安全问题的三种方案:使用Lock锁
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
树 void lock()获取锁。
void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock implements Lock接口
使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
cfile:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image028.png
使用修改代码, 使用Lock锁解决卖票问题
public class RunnableImpl implementsRunnable {
// 定义多线程共享的票数
private int ticket = 100;
// 定义Lock锁
Lock l = new ReentrantLock();
@Override
public void run() {
// 这个run方法会被3个窗口执行, 所以每个窗口不知道循环多少次才能卖完, 所以使用死循环
while (true) {
// 加锁(获取锁)
l.lock();
try {
// 判断, 当有票时, 再卖票
if (ticket > 0) {
// 先打印当前窗口卖出的票
System.out.println(Thread.currentThread().getName()+ "卖出了第" + ticket + "张票");
// 然后让票数-1
ticket--;
} else {// 在这里也可以写个else, 里面break
break; // 没票了, 结束循环
}
} finally {
// 释放锁
l.unlock();
}
}
}
}
public class Test {
public static void main(String[] args) {
// 先创建一个卖票任务
RunnableImpl runnable = new RunnableImpl();
// 创建3个线程模拟3个售票窗口
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
// 为了看的明显, 也可以给线程设置名字
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
// 启动线程, 开始卖票
t1.start();
t2.start();
t3.start();
}
}
l 线程的状态
锁对象, 也称为"同步锁","对象锁", "对象监视器"
Object类中关于线程的方法:
java.lang.Object类:
//成员方法 (<<注意>>: 只能通过"锁对象"调用)
树
void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
void wait(): 让当前线程处于"无限等待"状态
void wait(long timeout): 让当前线程处于"计时等待"状态, 时间到或被唤醒后结束此状态
void wait(long timeout, int nanos): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
"注意!! 以上方法只能通过锁对象调用"
l 线程的生命周期中, 可以出现有6种状态:
1."NEW 新建"
线程被创建, 但没有调用 start() 启动
2."RUNNABLE 可运行"
调用 start()方法后已启动, 但可能正在执行 run() 方法的代码, 也可能正在等待CPU的调度
3. "BLOCKED (锁)阻塞"
线程试图获取锁, 但此时锁被其他线程持有
4."WAITING 无限等待"
通过锁对象调用无参的 wait() 进入此状态.
等待其他线程通过锁对象执行 notify() 或notifyAll() 才能结束这个状态
5."TIMED_WAITING 计时等待"
如通过锁对象调用有参的 wait(long millis) 或 sleep(long millis), 则进入此状态.
直到时间结束之前被其他线程通过锁对象执行 notify()或 notifyAll()唤醒, 或时间结束自动唤醒
6."TERMINATED终止"
run()方法结束(执行结束, 或内部出现异常), 则进入此状态
线程间通信
卖包子吃包子
*/
public class Test {
public static void main(String[] args) {
// 创建一个对象作为锁对象
Object lock = new Object();
// 使用匿名内部类方式创建一个线程, 作为顾客线程
new Thread(){
@Override
public void run() {
// 死循环要包子吃包子
while (true) {
// 使用同步代码块, 同步代码块中有锁对象, 可以用来调用wait()方法
synchronized (lock) {
// 先向老板要包子
System.out.println("[顾客说:] 老板! 来2斤包子!");
// 顾客先等着老板做
try {
lock.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
// 如果能执行到这里, 说明被老板唤醒了, 开始吃
System.out.println("[顾客说:] 嗯嗯!真香~");
System.out.println("-------------------");
}
}
}
}.start();
// 使用匿名内部类方式创建一个线程, 作为老板线程
new Thread(){
@Override
public void run() {
// 死循环做包子
while (true) {
// 先睡5秒模拟做包子的时间(实际上是为了让老板线程抢锁不要太快)
try {
Thread.sleep(2000);
} catch(InterruptedException e) {
e.printStackTrace();
}
// 使用同步代码块, 同步代码块中有锁对象, 可以用来调用notify()方法
synchronized (lock) {
// 打印做好了包子
System.out.println("[老板说:] 包子做好了, 来吃吧");
// 老板要通知顾客, 使用锁对象调用notify()
lock.notify();
}
}
}
}.start();
}
}
file:///C:/Users/939969~1/AppData/Local/Temp/msohtmlclip1/01/clip_image030.jpg
l Object类中wait(long timeout)和notifyAll()方法
// 成员方法 (只能通过"锁对象"调用)
树
void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
void wait(long timeout): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
l wait() 和 sleep() 的区别:
1.wait会释放锁, 恢复时需要重新获取锁; sleep不会释放锁
2. wait可以被notify/notifyAll唤醒;sleep不会
3. wait要用锁对象调用; sleep要用Thread类名调用
l 进入到TimeWaiting(计时等待)有两种方式
1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
唤醒的方法:
void notify() 唤醒在此对象监视器上等待的单个线程。
void notifyAll() 唤醒在此对象监视器上等待的所有线程。
java.lang.Thread类: 表示线程. 实现了Runnable接口
// 构造方法
树
Thread(): 创建Thead对象
Thread(String threadName): 创建Thead对象并指定线程名
Thread(Runnable target): 通过Runnable对象创建Thread对象
Thread(Runnable target, String threadName):通过Runnable对象创建对象并指定线名
// 成员方法
void run(): 用于让子类重写, 表示该线程要执行的任务.不能直接调用
void start(): 启动线程, 即让线程开始执行run()方法中的代码
String getName(): 获取线程的名称
void setName(String name): 设置线程名称
// 静态方法
static Thread currentThread(): 返回对当前正在执行的线程对象的引用
static void sleep(long millis): 让所在线程睡眠指定的毫秒
u 今日API
Thread Thread(): 创建Thead对象
Thread Thread(String threadName): 创建Thead对象并指定线程名
Thread Thread(Runnable target): 通过Runnable对象创建Thread对象
Thread Thread(Runnable target, String threadName): 通过Runnable对象创建对象并指定线程名
//成员方法
void run(): 用于让子类重写, 表示该线程要执行的任务.不能直接调用
void start(): 启动线程, 即让线程开始执行run()方法中的代码
String getName(): 获取线程的名称
void setName(String name): 设置线程名称
//静态方法
static Thread currentThread(): 返回对当前正在执行的线程对象的引用
static void sleep(long millis): 让所在线程睡眠指定的毫秒
java.lang.Object类:
//成员方法 (只能通过"锁对象"调用)
voidnotify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
voidnotifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
voidwait(): 让当前线程处于无限等待状态
voidwait(long timeout): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
voidwait(long timeout, int nanos): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |