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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 小石姐姐 于 2018-11-22 09:31 编辑

多线程
并发: (交替执行) 指两个或多个事件在"同一时间段内"发生[排队,轮流干]

并行: (同时执行) 指两个或多个事件在"同一时刻"发生 (同时发生)[同时进行]

进程: 一个应用程序在内存中的一次执行过程,一个程序至少包含一个进程

线程: 是进程内的一个独立执行单元 (一条代码执行路径),一个程序运行后至少有一个进程, 一个进程中可以包含多个线程
多线程的好处:
效率高
多个线程之间互不影响
线程的调度
1,分时调度:平分CPU时间,轮流占用cpu
2.抢占式调度:优先让优先级高的线程使用CPU; 如果优先级相同, 则随机选择一个线程执行
主线程
我们以前编写的代码, 也在一条线程中执行, 该线程叫作"main线程", 也称为"主线程"

单线程(程序只有一个主线程,main线程)的执行特点:同一个线程内的代码, 从上往下依次执行
创建多线程的方式
创建多线程程序的第一种方式: 继承Thread类
实现多线程的第一种方式:
1. 定义类, 继承 Thread 类
2. 重写 run() 方法, run方法内部是线程要执行的任务
3. 创建Thread子类的对象, 调用 start() 方法启动线程
java.lang.Thread类: 表示线程. 实现了Runnable接口
          void start(): 启动线程, 即让线程开始执行run()方法中的代码

注意:
必须调用 start() 方法来开启线程, 不能直接调用 run() 方法, 调用 run() 会变成单线程
同一个线程对象, 不能多次调用 start() 方法
Java是抢占式调度, 不同线程的代码, 执行顺序是随机的
多线程原理1: 线程执行的随机性
CPU执行哪个线程是随机的, 不能人为干预
Java线程调度是抢占式的, 多个线程互相抢夺CPU的执行权
CPU高速随机切换 (本质)
线程抢夺CPU资源
多线程创建方式:
第一种方式: 继承Thread类
定义类继承Thread
重写run()方法, 要执行的任务
创建子类的对象, 调用start()方法启动线程
多线程原理2: 多线程的内存
多线程情况下, 每个线程都有各自的栈内存
每个线程各自的方法调用, 进的是各自线程的栈
"栈"是每个线程各自的, "堆"是所有线程共用的
线程常用方法:Thread常用方法: getName(), currentThread()
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): 让所在线程睡眠指定的毫秒 //出现异常,需要try..catch处理
java.lang.Thread类: 表示线程. 实现了Runnable接口
// 构造方法
Thread(String threadName): 创建Thead对象并指定线程名
          // 成员方法
                  void setName(String name): 设置线程名称
创建多线程程序的方式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实现类对象. 相当于将线程和任务分离

耦合性: 相互之间的关系的紧密程度
耦合性高: 相互之间的关系非常紧密
耦合性低: 相互之间的关系不太紧密
我们追求 "低耦合"
匿名内部类方式创建线程
// 简化代码, 直接在Thread的构造方法中, 传递匿名内部类
[Java] 纯文本查看 复制代码
public class Test {
     public static void main(String[] args) {
   new Thread(new Runnable() {
   @Override
  public void run() {
     for (int i = 0; i < 100; i++) {
   System.out.println(Thread.currentThread().getName() + "---" + i);
      }
  }
  }).start();
}
}

线程安全问题(多线程访问了共享的数据就会产生线程安全问题)
线程安全问题的原因
问题发生场景:
多个线程操作共享资源
问题发生原因:
JVM是抢占式调度, CPU在每个线程之间切换是随机的, 代码执行到什么位置是不确定的
在操作共享资源时, 由于一个线程还没有执行完, 另一个线程就来操作, 就会出现问题
如何解决:
在操作共享资源时, 让线程一个一个来执行, 不要并发操作共享变量, 就可以解决问题
解决线程安全问题方式1: 同步代码块        
同步代码块: 使用 synchronized 关键字修饰的代码块, 并传入一个当作锁的对象
格式:
  synchronized (锁对象) {
// 操作共享数据的代码
}
注意:
锁对象可以是"任意类型的一个对象"
锁对象必须是"被多个线程共享的唯一的"对象
锁对象的作用: 只让一个线程在同步代码块中执行
同步技术解决线程安全问题的原理
锁对象, 也称为"同步锁", "对象锁", "对象监视器"
同步代码块唯一;必须包含所有可能会产生线程安全问题的代码块
同步方法锁对象就是实现类的对象,所以要保证实现类对象唯一
同步的原理:
线程进入同步代码块前, 会"争夺锁对象", "只有一个线程"会抢到锁对象
进入同步代码块的线程, 会"持有锁对象", 并执行同步代码块中的代码
此时同步代码块外的线程, 处于"阻塞"状态, 只能等待
当同步代码块内的线程执行完代码块, 会离开同步代码块, 并"归还锁对象"给同步代码块
等在同步代码块外的其他线程就可以继续争夺锁对象
解决线程安全问题方式2: 同步方法
同步方法: 使用 synchronized 关键字修饰的方法, 具有默认的锁对象
非静态同步方法的锁对象: this
// 非静态同步方法
public synchronized void method(){
// 可能会产生线程安全问题的代码
}
静态同步方法
静态同步方法的锁对象: 当前类的字节码对象 Class对象
静态同步方法:
public static synchronized void method(){
// 可能会产生线程安全问题的代码
}
附注:获取一个类的字节码对象的3种方式:
1. 对象名.getClass()      new RunnableImpl().getClass()
2. 类名.class         RunnableImpl.class
3. Class.forName("类的全名");  Class.forName("com.itheima.test05.RunnableImpl");
字节码对象的特点:
同一个类, 他的字节码对象只有"唯一的一个"
解决线程安全问题方式3: Lock锁
java.util.concurrent.locks.Lock接口: JDK 5 新增的Lock接口
// 成员方法
void lock(): 获取锁
void unlock(): 释放锁
java.util.concurrent.locks.ReentrantLock类: Lock的实现类
使用方式:
[Java] 纯文本查看 复制代码
public class RunnableImpl implements Runnable {
  // 成员变量创建锁对象, 该锁对象也要所有线程共享唯一一个
  Lock lock = new ReentrantLock();  // 成员变量
                  @Override
  public void run() {
            // 加锁
    lock.lock();
    try {
      // 操作共享变量的代码...
   } finally {
      // 在finally中保证释放锁
      lock.unlock();
   }
}
}

线程间的通信
线程状态概述
Object类中关于线程的方法:
java.lang.Object类:
// 成员方法 (<<注意>>: 只能通过"锁对象"调用)
void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
void wait(): 让当前线程处于"无限等待"状态
void wait(long timeout): 让当前线程处于"计时等待"状态, 时间到或被唤醒后结束此状态
void wait(long timeout, int nanos): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
"注意!! 以上方法只能通过锁对象调用"
wait() 和 sleep() 的区别:
1. wait会释放锁, 恢复时需要重新获取锁; sleep不会释放锁
2. wait可以被notify/notifyAll唤醒; sleep不会
3. wait要用锁对象调用; sleep要用Thread类名调用
线程的六种状态
                     新建   执行  阻塞   休眠    无限等待    死亡状态
C:/Users/Administrator/AppData/Local/YNote/data/qq0160B4AE0B97BA596F0CCCA205A4CECF/200dda6e70d04a6ab894d96a85703c71/clipboard.png






0 个回复

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