本帖最后由 键盘有花生油 于 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): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
|