黑马程序员技术交流社区

标题: 学习经历Day03-Day06 [打印本页]

作者: 键盘有花生油    时间: 2018-11-20 16:17
标题: 学习经历Day03-Day06
本帖最后由 键盘有花生油 于 2018-11-20 16:40 编辑
Day06
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