方式1,继承Thread类

  1. 定义一类继承Thread,并重写run方法

    public class MyThread extends Thread{
    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    System.out.println("线程开启了"+i);
    }
    }
    }
  2. 创建线程对象,并调用start方法开启线程

    public static void main(String[] args) {
    MyThread t1 = new MyThread();
    MyThread t2 = new MyThread();
    t1.start();
    t2.start();
    }

方式2,实现Runnable接口

  1. 定义一类实现Runnable,并重写run方法

    public class MyRunnable implements Runnable {
    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    System.out.println("第二种方式实现多线程"+i);
    }
    }
    }
  2. 创建Thread对象,并将Runnable对象作为构造方法的参数进行传递

    MyRunnable mr = new MyRunnable();
    Thread t1 = new Thread(mr);
    t1.start();

方式3,实现Callable接口

  1. 定义类实现Callable接口(指定泛型,重写call方法)

    public class MyCallable implements Callable<String>{
    @Override
    public String call() throws Exception {
    for (int i = 0; i < 100; i++) {
    System.out.println("跟女孩表白");
    }
    return "答应";
    }
    }
  2. 创建Callable实现类对象,并将其作为参数传递给FutureTask,最后将FutureTask对象作为参数传递给Thread类的构造

    public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();

    FutureTask<String> ft = new FutureTask<>(mc);

    Thread t1 = new Thread(ft);

    t1.start();
    String result = ft.get(); // get方法带有阻塞效果,如果获取不到,会一直等待,后续代码无法执行
    System.out.println(result);
    }
    }

注意:ft.get(); 必须在开启线程后调用

三种实现方式的对比

  • 编码的复杂度

    继承Thread最简单

  • 程序的扩展性

    实现接口的方式扩展性相对更好

  • 是否可以使用Thread的方法

    第一种方式可以直接使用Thread的方法

获取和设置线程的名称

  1. 线程的默认的名称

    规则:Thread-编号(编号是从0开始的一个数字)

  2. 获取名字

    Thread类提供了String getName()方法

  3. 设置名称

    • setName(String name);
  • 通过有参构造传递名称,需要手动重写有参构造

获取当前线程对象

  1. Thread提供的静态方法

    Thread对象 Thread.currentThread();

    哪个线程在执行这行代码,就返回哪个线程所对应的对象

线程的休眠

  1. Thread类中提供sleep方法·

    Thread.sleep(long 毫秒值);

    哪个线程在执行这行代码,哪个线程就休眠指定毫秒

线程的调度模型

java中的多线程是一种抢占式的模型。多个线程争夺CPU的执行权。线程中存在一个优先级的概念,优先级越高的线程抢夺到CPU的执行权的概率理论上更高。

  1. 线程的默认优先级

    默认是5(线程的优先级是通过数字来表示的,取值范围是1-10)

  2. 如何设置线程的优先级

    setPriority(int priority);

  3. 如何获取线程的优先级

    int getPriority();

守护线程

备胎线程:用于守护普通线程的,当普通线程直接结束,守护线程会自动结束执行。

  1. 如何将一个线程变成守护线程。

    setDaemon(true);

多线程的安全问题

产生的本质原因:

多个线程在同时操作共享数据

同步

同步代码块解决

语法:

synchronized(锁对象){
操作共享数据的代码
}

基本原理:

  1. 默认锁是打开的
  2. 当有线程进入synchronized代码块后锁会自动锁上
  3. 当synchronized中的操作共享数据的代码执行完毕后,锁会自动打开

同步方法解决

  • 同步方法

    访问修饰符 synchronized 返回值 方法名(){
    操作共享数据的代码
    }

    锁:this

  • 同步静态方法

    访问修饰符 static synchronized 返回值 方法名(){
    操作共享数据的代码
    }

    锁:当前类.class(当前类字节码对象)

Lock锁

语法

  1. 创建Lock锁对象ReentranctLock

  2. 操作共享数据之前获取锁

    lock();

  3. 操作完共享数据要释放锁

    unlock();

try{
// 获取锁
// 操作共享数据的代码
}catch(异常){

}finally{
// 释放锁
}

示例:

public class Ticket implements Runnable {
private int ticket = 100;
// 创建锁对象
ReentrantLock lock = new ReentrantLock(); // 可重入锁

@Override
public void run() {
while (true) {
try {
lock.lock(); // 获取锁
if (ticket == 0) {
break;
} else {
Thread.sleep(10);
ticket--;
System.out.println(Thread.currentThread().getName() + "在买票,还剩下" + ticket + "张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
}
}

消费者生产者(等待唤醒机制)

假设:消费者一共只能吃10个汉堡

  • 消费者线程

    1. 判断桌子上是否有汉堡
    2. 如果桌子上没有就等待
    3. 如果桌子上有就开吃
      • 通知生产者生成汉堡
      • 汉堡的数量减1
  • 生产者线程

    1. 判断桌子上是否有汉堡

    2. 如果有就等待

    3. 如果没有就生产汉堡,将汉堡放在桌子上

      通知消费者线程吃汉堡

阻塞队列

队列:一种先进先出的数据结构

阻塞队列:在传统的队列的基础上,加入了两个附加操作

  • 当获取元素的线程,发现队列为空,会自动等待,等到队列不为空为止
  • 当添加元素的线程,发现队列已满,会自动等待,等待队列可用为止

对象:

  • ArrayBlockingQueue

    依赖数组实现,有界的

  • LinkedBlockingQueue

    依赖链表实现的,无界的,(最大长度是int的最大值)

方法:

  • put(元素对象)

  • 队列顶端的元素 take();

复习

  1. 能够知道什么是进程什么是线程

    • 进程

      操作系统中正在执行的程序。每一个程序开始运行后,至少会有一个进程。

    • 线程

      线程是某一个进程中的执行逻辑。一个进程中可能包含多个线程。某一个线程只可能属于某一个进程。

  2. 并发和并行的

    • 并行

      在同一时刻,有多个指令在多个CPU上同时执行。

    • 并发

      在同一时刻,有多个指定在单个CPU上交替执行

  3. 线程3种创建方式

    • 继承Thread类
      • 写一个类继承Thread,重写run方法
    • 实现Runnable接口
      • 写一个类实现Runnable接口,重写run方法
      • 创建Runnable的对象,并将其作为参数传递给Thread的构造方法
    • 实现Callable接口
      • 写一个类实现Callable接口,重写call方法
      • 创建Callable的对象,并将其作为参数传递给FutureTask的构造方法
      • 将FutureTask对象作为参数传递给Thread的构造方法
  4. 线程中的常见的方法

    • 获取线程的名称

      getName(); 默认名字:Thread-编号

    • 设置线程的名称

      setName(String name);

      有参构造方法

    • 获取当前线程对象

      Thread Thread.currentThread();哪个线程执行到这行代码,就返回该线程对象

    • 线程的睡眠

      Thread.sleep(long 毫秒值),哪个线程执行到这行代码,哪个线程就睡眠

    • 设置线程的优先级

      setPriority(int priority)

      参数的取值范围:1-10,值越大优先级越大

    • 获取线程的优先级

      int getPriority(); 默认值是5

  5. 守护线程

    可以理解为备胎线程,用于守护其它的普通线程而存在。如果普通线程都执行结束,守护线程也会自动结束执行。

    怎么将线程设置为守护线程:

    setDaemon(true);

  6. 线程的安全问题

    • 什么是安全问题:

      多个线程在并发操作共享数据时,会出现数据错乱的问题。

    • 产生的本质原因

      多个线程在在同时操作共享数据

    • 解决思想

      在同一个时刻,只能让一个线程操作功效数据,当这个线程操作完毕之后,其它线程才可以操作。

    • 解决方式

      • 同步

        • 同步代码块

          synchronized(锁对象){
          操作共享数据的代码
          }

          锁对象是任意的。多个线程需要使用同一个锁对象。

        • 同步方法

          访问修饰符 synchronized 返回值 方法名(){

          }

          访问修饰符 static synchronized 返回值 方法名(){

          }

          同步方法使用this作为锁对象,同步静态方法使用 当前类.class作为锁对象

      • Lock锁

        1. 创建锁对象

          ReentrantLock

        2. 在操作之前获取锁

          lock();

        3. 操作之后释放锁

          unlock(); // 一般写在finally语句块中

  7. 死锁

    多个线程出现相互等待的情况,造成程序无法继续。

    出现的原因:锁的嵌套

  8. 生产者和消费者

    等待唤醒机制

    • 让当前线程等待

      Object类提供了一个wait();

    • 唤醒其它线程

      Object类提供两个方法

      • notify();

        随机换当前锁对象绑定的处于等待状态的某一个线程。

      • notifyAll();

        唤醒当前锁对象绑定的所有处于等待状态的线程。