多线程

基本概念: 程序, 进程和线程

程序: 静态的代码

进程: 程序的一次运行过程. 有产生, 存在和消亡的过程, 即生命周期

线程: 一个进程可以并行执行多个线程. 系统会为每个进行分配内存区域, 而进程中每个线程有自己独立的运行栈和程序计数器, 线程之间共享堆空间和方法区

线程状态State

wait和sleep的区别

wait只能在同步代码块中调用, 必须释放锁.

sleep不会释放锁, 因此可以在任何地方调用.

线程的创建(重点)

继承Thread类

  1. 创建一个继承于Thread的类
  2. 重写run方法
  3. 调用start方法: 启动线程并执行run方法
public class MyThread extends Thread {
    private static int count = 100;

    @Override
    public void run() {
        while (true) {
            if (count > 0) {
                System.out.println(currentThread().getName() + ":" + count--);
            } else {
                break;
            }
        }
    }

    public static void main(String[] args) {
        MyThread thread01 = new MyThread();
        MyThread thread02 = new MyThread();
        MyThread thread03 = new MyThread();
        thread01.setName("窗口1");
        thread02.setName("窗口2");
        thread03.setName("窗口3");
        thread01.start();
        thread02.start();
        thread03.start();

    }
}

实现Runnable接口

  1. 创建一个实现了Runnable接口的类
  2. 实现类去实现接口中的run方法
  3. 创建实现类的对象, 并以该对象作为Thread类含参构造器的参数构造Thread类的对象
  4. 调用start方法
public class MThread implements Runnable {
    /**
     * 使用Runnable接口的方式只生成了一个对象, 因此多个线程共用一个对象中的数据
     */
    private int count = 100;

    @Override
    public void run() {
        while (true) {
            if (count > 0) {
                System.out.println(Thread.currentThread().getName() + ":" + count--);
            } else {
                break;
            }
        }
    }

    public static void main(String[] args) {
//只生成一个MThread类的对象
        Runnable mThread = new MThread();

//通过一个相同的对象生成多个线程
        Thread thread01 = new Thread(mThread, "线程1");
        Thread thread02 = new Thread(mThread, "线程2");
        thread01.start();
        thread02.start();
    }
}

两种方式的比较

  • 实现Runnable接口更适合用来创建多线程,

    • 一是没有单继承的局限性,
    • 二是能天然地体现出共享数据的概念, 在继承Thread类的方法中共享数据都需要使用static进行修饰, 否则不唯一
  • 两种方式都需要重写run方法

线程的生命周期

Thread.State记录了线程的生命周期的状态

  • 新建: new线程对象
  • 就绪: 执行start后, 等待cpu调用
  • 运行: 占用cpu, 执行run方法体中的内容
    • yield会从运行态返回到就绪态
    • sleep, join, wait, suspend等方法以及等待同步锁会使得当前进程阻塞
  • 堵塞: 比如一些打印输出功能
  • 死亡: 调用stop方法, 或者执行完run方法

线程的同步/并发安全问题(重点)

各种实现方法之间有着细微差异, 但核心是保证用同一把锁, 需要区分this所指代的是什么对象

  • 同步代码块
  • 同步方法
  • Lock同步锁

同步代码块synchronized

  • 锁: 任何一个类的对象都可以作为锁. 要求多个线程共用同一把锁
  • 临界区: 需要被同步的代码, 操作共享数据

优缺点

  • 优点: 解决了线程安全问题
  • 局限性: 操作临界区部分的代码相当于单线程, 稍微降低了一些速度

同步代码块显然不能包含太少代码, 同时也不能包含太多代码, 否则可能会导致逻辑发生变化, 比如下面案例中的whilesynchronized互换

public class MThread implements Runnable {
    /**
     * 使用Runnable接口的方式只生成了一个对象, 因此多个线程共用一个对象中的数据
     */
    private int count = 100;
    /**
     * 需要保证不同的线程共用同一把锁, 使用Runnable接口实现类的方式其实可以不用该
     */
    final Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (MThread.class) {
//                某种具体的类也是一个对象, 是更高层次抽象的类的一个对象
//            synchronized(this){
//            synchronized(obj){  
                if (count > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + count--);
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
//只生成一个MThread类的对象
        MThread mThread = new MThread();

//通过一个相同的对象生成多个线程
        Thread thread01 = new Thread(mThread, "线程1");
        Thread thread02 = new Thread(mThread, "线程2");
        thread01.start();
        thread02.start();
    }
}

同步方法

使用synchronized关键字对方法名进行修饰. 对于非static方法, 锁默认为this, 而对于static方法, 锁默认为的类名.class

Lock同步锁

lock方式需要手动的申请和释放锁, 而synchronized方式自动释放锁

import java.util.concurrent.locks.ReentrantLock;

public class LockTest implements Runnable {
    private int count = 100;
    /**
     * 实例化lock
     */
    ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
//            lock()和unlock()必须成对使用, lock在try前面, unlock在finally里面
//            获取锁
            lock.lock();
            try {
                if (count > 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + count--);
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

死锁问题

隐蔽的死锁问题经常是同步方法中调用其它对象的同步方法, 每次执行同步方法都要获取当前对象, 先后需要两个对象及以上作为锁便满足死锁条件

避免死锁的策略

  • 避免嵌套锁
  • 尽量少的使用同步资源

线程的通信


   转载规则


《》 熊水斌 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
反射反射是动态语言的关键, 允许程序在执行期间获取任何类的内部信息, 并直接操作任意对象的内部属性和方法 加载完某个类后, 在堆内存的方法区中产生了一个Class类型的对象—每个类对应一个且只有唯一一个Class对象. 这个对象包含类的完整
2022-11-11
下一篇 
面向对象类是抽象的概念, 对象是具体的实例 Java类及类成员public class Person { private String name; private Integer age; static {
2022-11-11
  目录