A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

就业班JavaSE高级部分day06  线程 同步 线程间通信

线程:
CPU执行哪个线程是随机的, 不能人为干预
Java线程调度是抢占式的, 多个线程互相抢夺CPU的执行权
CPU高速随机切换 (本质)
线程抢夺CPU资源
多线程的内存:
多线程情况下, 每个线程都有各自的栈内存
每个线程各自的方法调用, 进的是各自线程的栈
""是每个线程各自的, ""是所有线程共用的
Thread常用方法:
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): 让所在线程睡眠指定的毫秒
创建多线程程序的方式2: 实现Runnable接口:
创建线程的第2种方式:
        1. 定义类, 实现Runnable接口
        2. 重写 run() 方法, 要执行的代码(任务)
        3. 创建Runnable实现类对象 (任务对象)
        4. 创建Thread类对象, 在构造方法中传入Runnable实现类对象 (将任务和线程绑定)
        5. 通过Thread对象调用 start() 方法启动线程

java.lang.Thread类: 表示线程. 实现了Runnable接口
        // 构造方法
    Thread Thread(Runnable target): 通过Runnable对象创建Thread对象
    Thread Thread(Runnable target, String threadName): 通过Runnable对象创建对象并指定线程名
Thread和Runnable的区别:
实现Runnable的好处:
        1. 避免单继承的局限性
        2. 增强了程序的扩展性, 降低了程序的耦合性(解耦)
                线程是Thread, 任务是Runnable实现类对象. 相当于将线程和任务分离
               
耦合性: 相互之间的关系的紧密程度
耦合性高: 相互之间的关系非常紧密
耦合性低: 相互之间的关系不太紧密
我们追求 "低耦合"
匿名内部类方式创建线程:
new 父接口/父类() {
    重写方法
};​
继承Thread类
实现Runnable接口
如何使用匿名内部类方式简化创建线程的步骤
public class Test {
    public static void main(String[] args) {
        // 匿名内部类方式
        new Thread(new Runnable(){
            @Override
            public void run() {
                // 要执行的任务
            }
        }).start();
    }
}
线程安全问题的原因
问题发生场景:
        多个线程操作共享资源
问题发生原因:
        JVM是抢占式调度, CPU在每个线程之间切换是随机的, 代码执行到什么位置是不确定的
        在操作共享资源时, 由于一个线程还没有执行完, 另一个线程就来操作, 就会出现问题
如何解决:
        在操作共享资源时, 让线程一个一个来执行, 不要并发操作共享变量, 就可以解决问题

        @Override
    public void run() {
        while (true) {

            // 操作ticket的地方就是操作共享变量
            if (ticket > 0) {    // 这一行用ticket进行了判断
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                ticket--;        // 这一行对ticket进行了修改
            }

        }
    }
解决线程安全问题方式1: 同步代码块
解决多线程操作共享数据的安全问题的3种方式:
        1. 同步代码块
        2. 同步方法
        3. Lock锁机制
       
同步代码块: 使用 synchronized 关键字修饰的代码块, 并传入一个当作锁的对象
格式:
    synchronized (锁对象) {
                // 操作共享数据的代码
        
    }
注意:
        锁对象可以是"任意类型的一个对象"
        锁对象必须是"被多个线程共享的唯一的"对象
        锁对象的作用: 只让一个线程在同步代码块中执行

锁对象, 也称为"同步锁", "对象锁", "对象监视器"
同步技术解决线程安全问题的原理
同步的原理:
    线程进入同步代码块前, 会"争夺锁对象", "只有一个线程"会抢到锁对象
    进入同步代码块的线程, 会"持有锁对象", 并执行同步代码块中的代码
    此时同步代码块外的线程, 处于"阻塞"状态, 只能等待
    当同步代码块内的线程执行完代码块, 会离开同步代码块, 并"归还锁对象"给同步代码块
    等在同步代码块外的其他线程就可以继续争夺锁对象
同步方法
同步方法: 使用 synchronized 关键字修饰的方法, 具有默认的锁对象
静态同步方法的锁对象: this
        // 非静态同步方法
        public synchronized void method(){
                // 可能会产生线程安全问题的代码
        }

静态同步方法:
        public static synchronized void method(){
                // 可能会产生线程安全问题的代码
        }
静态同步方法的锁对象: 当前类的字节码对象 Class对象
RunnableImpl.class -> Class对象
获取一个类的字节码对象的3种方式:
        1. 对象名.getClass()          new RunnableImpl().getClass()
        2. 类名.class                 RunnableImpl.class
        3. Class.forName("类的全名");  Class.forName("com.itheima.test05.RunnableImpl");
字节码对象的特点:
        同一个类, 他的字节码对象只有"唯一的一个"        
Person
        new Person()  this
    new Person()  this
    ...
Person.class 只有一个     
锁对象必须是多个线程共享的同一个对象
Lock锁
java.util.concurrent.locks.Lock接口: JDK 5 新增的Lock接口
        // 成员方法
        void lock(): 获取锁
        void unlock(): 释放锁       
java.util.concurrent.locks.ReentrantLock类: Lock的实现类
使用方式:
public class RunnableImpl implements Runnable {
    // 成员变量创建锁对象, 该锁对象也要所有线程共享唯一一个
    Lock lock = new ReentrantLock();  // 成员变量
    @Override
    public void run() {
        // 加锁
        lock.lock();
        try {
            // 操作共享变量的代码...
        } finally {
            // 在finally中保证释放锁
            lock.unlock();  
        }
    }
}
线程的生命周期中, 可以出现有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()方法结束(执行结束, 或内部出现异常), 则进入此状

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马