线程简介

1.进程和线程

  • 程序:存放在磁盘中的可执行文件
  • 进程:当运行一个程序时,操作系统就会创建一个进程。
  • 线程:线程就是进程内部的程序流,也就是说操作系统内部支持多进程的,而每个进程的内部又是支持多线程的,线程是轻量的,新建线程会共享所在进程的系统资源
  • 多线程:采用时间片轮转法来保证多个线程的并发执行,所谓并发就是指宏观并行微观串行的机制

线程的使用

  • java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例

1.创建线程

  • 继承Thread类并重写run方法,然后创建该类的对象调用start方法。
  1. public class ExtendsThread extends Thread {
  2. @Override
  3. public void run() {
  4. System.out.println("用Thread类实现线程");
  5. }
  6. }
  • 实现Runnable接口并重写run方法,构造Thread对象作为参数传入,然后调用start方法。
  1. public class RunnableThread implements Runnable {
  2. @Override
  3. public void run() {
  4. System.out.println("用实现Runnable接口实现线程");
  5. }
  6. }

继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类,而实现 Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后的开发中 推荐使用第二种方式

2.启动线程

  • main方法是程序的入口,当start方法调用成 功后线程的个数由1个变成了2个。主线程继续向下执行,两 个线程各自独立运行互不影响
public static void main(String[] args) {
    // 方式一
    Thread t1 = new ExtendsThread("ExtendsThread");
    t1.start();

    // 方式二:也可以通过匿名内部类来传入Runnable实现类
    Thread t2 = new Thread(new RunnableThread(), "RunnableThread");
    t2.start();
}

3.关闭线程

  • 通过interrupt停止线程
public class StopThread implements Runnable {

    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count=" + count++);
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new StopThread());
    thread.start();
    Thread.sleep(500);
    thread.interrupt();
}
  • 线程阻塞停止线程

通过抛出异常终止线程

public class StopThread implements Runnable {

    @Override
    public void run() {
        try {
            while (true) {
                subTask();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    void subTask() throws InterruptedException {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count=" + count++);
            Thread.sleep(1000);
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new StopThread());
    thread.start();
    Thread.sleep(500);
    thread.interrupt();
}

当发出了中断请求,清除了中断信号,可以通过重新设置中断信号进行中断线程

public class StopThread implements Runnable {

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            subTask();
        }
    }

    void subTask() {
        try {
            int count = 0;
            while (!Thread.currentThread().isInterrupted() && count < 1000) {
                System.out.println("count=" + count++);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new StopThread());
    thread.start();
    Thread.sleep(500);
    thread.interrupt();
}
  • volatile标记位停止线程(不建议使用):当volatile标记位发生改变时,线程处于休眠状态时是无法跳出while循环的,这样就会出现线程无法退出的情况

线程的生命周期

  • 状态说明 | 状态名称 | 说明 | | —- | —- | | NEW | 初始状态,线程被构建,但是还没有调用start()方法 | | RUNNABLE | 运行状态,Java线程将就绪和运行两种状态统称“运行中” | | BLOCKED | 阻塞状态,表示线程获取锁时阻塞 | | WAITING | 等待状态,线程进入等待状态,需要等待其他线程做出动作 | | TIME_WAITING | 超时等待状态,带有超时时间的等待状态 | | TERMINATED | 终止状态,表示线程执行完毕 |
  • 状态图

Java线程状态

线程常用方法

1.获取线程信息

Thread thread = new Thread(new RunnableThread(), "RunnableThread");
// 获取线程ID
long id = thread.getId();
// 获取线程名称
String name = thread.getName();
// 修改线程名称
thread.setName("RenameThread");
// 获取当前线程实例
Thread thread1 = Thread.currentThread();

2.让出处理器资源

  • 让线程让出处理器资源进入排队
Thread.yield();

3.线程休眠

  • 使当前线程休眠1秒
Thread.sleep(1000);

4.线程优先级

  • 设置线程优先级为10
Thread thread = new Thread(new RunnableThread(), "RunnableThread");
int priority = thread.getPriority();
thread.setPriority(10);
  • 获取线程优先级
thread.getPriority();

5.线程等待

  • 当子线程thread结束时才会继续执行下面的代码
Thread thread = new Thread(new RunnableThread(), "RunnableThread");
thread.start();
// 等待子线程执行完毕
thread.join();
// 等待子线程1秒后执行
// thread.join(1000);

6.Deamon线程

  • 当子线程是守护线程,主线程结束时子线程也会结束
Thread thread = new Thread(new RunnableThread(), "RunnableThread");
thread.setDaemon(true);
  • 获取线程是否是守护线程
thread.getDaemon();

synchronized关键字

1.基本介绍

  • synchronized关键字可以修饰方法或者以同步块的形式来进行使用
  • synchronized关键字主要确保多个线程在同一时间,只能有一个线程处于方法或者同步块中
  • synchronized关键字保证了线程对变量访问的可见性和排他性

2.使用

  • 同步当前变量的引用
public synchronized void syncMethod() {
}
// 等价于
public void syncMethod() {
    synchronized (this) {
    }
}
  • 同步当前类
public synchronized static void syncMethod() {
}
// 等价于
public void syncMethod() {
    synchronized (MySync.class) {
    }
}

线程安全

1.线程安全场景

  • 访问共享变量或资源:多个线程同时读取写入同一个共享变量,会导致读写不一致问题
  • 依赖时序的操作:两个线程同时删除一个数据,一个线程率先删除了,另一个线程则会报错
  • 不同数据之间存在绑定关系:两条需要绑定的数据只改变了一条,导致数据匹配不上
  • 对方没有声明自己是线程安全(synchronized)的:比如ArrayList不是线程安全的类
  • Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全

2.三种线程安全问题

  1. 运行结果错误:由并发读写造成的
  2. 发布和初始化导致线程安全问题:没有在正确的时间发布和初始化
  3. 活跃性问题

    • 死锁:线程由于争夺导致系统无法继续运行
    • 活锁:消息队列出现了错误数据无限重试机制
    • 饥饿:Java线程优先级比较低,导致可能始终分配不到CPU资源

3.死锁

  • 死锁Demo
public class DeadLockDemo {
    private static String A = "A";
    private static String B = "B";

    public static void main(String[] args) {
        new DeadLockDemo().deadLock();
    }

    private void deadLock() {
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B){
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (B) {
                synchronized (A){
                }
            }
        });
        t1.start();
        t2.start();
    }
}

Lock锁

1.常用方法

方法声明 功能介绍
void lock() 获取锁,调用该方法当前线程将会获取锁
void lockInterruptibly() 可中断的获取锁,可以响应中断信号
boolean tryLock() 非阻塞的获取锁,调用后返回是否获取到了锁
boolean tryLock(long time, TimeUnit unit) 有超时时间的非阻塞获取锁
void unlock() 释放锁
Condition newCondition 获取等待组件,组件与当前锁绑定,线程只有获取到了锁才能调用该组件的wait方法,调用后将释放锁

2.使用方式

  • Lock接口是控制多个线程对共享资源进行访问的工具,主要实现类是ReentrantLock类
  • finally中释放锁,目的是保证能在获取锁之后,最终能够被释放
Lock lock = new ReentrantLock();
lock.lock();
try {

}finally {
    lock.unlock();
}

3.与synchronized方式的比较

  • Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动 释放
  • Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁
  • 使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好

等待/通知机制

  • wait方法必须在 synchronized 保护的同步代码中使用

  • wait方法会释放monitor锁,所以必须首先进入到synchronized内持有这把锁

  • 等待/通知相关方法被定义在超类Object中

方法声明 功能介绍
void wait() 用于使得线程进入等待状态,直到其它线程调用notify()或notifyAll()方法
void wait(long timeout) 用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止
void notify() 用于唤醒等待的单个线程
void notifyAll() 用于唤醒等待的所有线程

生产者消费者模式

  • 队列的创建
public class MyBlockingQueue {

    private int maxSize;
    private LinkedList<Object> storage;

    public MyBlockingQueue(int size) {
        this.maxSize = size;
        storage = new LinkedList<>();
    }

    public synchronized void put(Object o) throws InterruptedException {
        while (storage.size() == maxSize) {
            wait();
        }
        storage.add(o);
        notifyAll();
    }

    public synchronized Object take() throws InterruptedException {
        while (storage.size() == 0){
            wait();
        }
        Object o = storage.remove();
        notifyAll();
        return o;
    }
}

这里使用while而不使用if的原因是,当线程进入等待状态被唤醒的时候,if会在被唤醒的地方接着执行下面的代码,而while会再次进行条件判断,当不满足条件时会继续进入等待状态。

  • 生产者消费者创建
public static void main(String[] args) {
    MyBlockingQueue<Object> queue = new MyBlockingQueue<>(10);
    // 生产者
    Runnable producer = () -> {
        while (true) {
            try {
                queue.put(new Object());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    new Thread(producer).start();

    // 消费者
    Runnable consumer = () -> {
        while (true) {
            try {
                queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    new Thread(consumer).start();
}

Callable接口

  • 实现Callable接口
public class CallableTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt();
    }
}
  • 启动线程
public static void main(String[] args) throws ExecutionException, InterruptedException {
    FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableTask());
    Thread thread = new Thread(futureTask);
    thread.start();
    Integer integer = futureTask.get();
}

FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实 现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,也可以用于获取方法调用 后的返回结果

更新时间:{docsify-updated}