Java 多线程

83 阅读6分钟

Java 多线程

进程

程序是静止的,只有真正运行时的程序,才能成为进程

特点:

  • 单核CPU在任何时间点上只能运行一个进程。
  • 宏观并行、微观串行

线程

线程:又称轻量级进程(Light Weight Process)

  • 程序中的一个顺序控制流程,同时也是CPU的基本调度单位
  • 进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程

进程和线程的区别

  • 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位
  • 一个程序运行后至少有一个进程
  • 一个进程可以包含多个线程,但至少需要一个线程
  • 进程间不能共享数据段地址,但同进程的线程间可以

线程组成

任何线程都具有的基本的组成部分

  • CPU(时间片):操作系统(OS)会为每个线程分配时执行时间
  • 运行数据
    • 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
    • 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈
  • 线程的逻辑代码

线程的执行

抢占式执行:效率高、可防止单一线程长时间独占CPU

线程的创建

Java 中创建线程主要有两种方式

  • 继承Thread
  • 实现Runnable接口

继承 Thread 类

步骤

  • 编写类、继承Thread类
  • 重写run()方法
  • 创建线程对象
  • 调用start()方法启动线程
public class TortoiseThread extends Thread{
    @Override
    public void run() {
        this.setName("乌龟线程");
        while (true) {
            System.out.println("乌龟run..." + this.getName());
        }
    }
}
public class Thread1 {
    public static void main(String[] args) {
        TortoiseThread tortoiseThread = new TortoiseThread();
        tortoiseThread.start();

        Thread.currentThread().setName("兔子线程");
        while (true) {
            System.out.println("兔子run......" + Thread.currentThread().getName());
        }
    }
}

获取线程名称

  • getName()
  • Thread.currentThread().getName()

实现 Runnable 接口

步骤

  • 创建类实现Runnable接口,并实现run()方法
  • 创建Runnable实现类对象
  • 创建线程对象,传递实现类对象
  • 启动线程
public class Task1 implements Runnable{
    @Override
    public void run() {
        Thread.currentThread().setName("跑步");
        while (true) {
            System.out.println("everyday run" + Thread.currentThread().getName());
        }
    }
}
public class TestRunnable {
    public static void main(String[] args) {
        Task1 task1 = new Task1();
        Thread thread1 = new Thread(task1);
        thread1.start();
        Thread.currentThread().setName("呼吸");
        while (true) {
            System.out.println("everyday breath" + Thread.currentThread().getName());
        }
    }
}

线程的状态

ThreadStrat.png

常用方法

⽅法名说明
public static void sleep(long millis)当前线程主动休眠 millis 毫秒。
public static void yield()当前线程主动放弃时间⽚,回到就绪状态,竞争下⼀次时间⽚。
public final void join()允许其他线程加⼊到当前线程中。
public void setPriority(int)线程优先级为1-10,默认为5,优先级越⾼,表示获取CPU机会越多
public void setDaemon(boolean)设置为守护线程线程有两类:⽤户线程(前台线程)、守护线程(后台线程)

线程安全

  • 线程不安全

    • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
    • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性
    • 原子操作:不可分割的多部操作,被视作一个整体,其顺序和步骤不可打乱或缺省
  • 解决方式

    • 同步代码块
    • 同步方法

同步代码块

  • 每个对象都有一个互斥锁标记,用来分配给线程的
  • 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步代码块
  • 线程退出同步代码块时,会释放相应的互斥锁标记
synchronized (临界资源对象) { // 对临界资源对象加锁
    // 代码(原子操作)
}
public class Ticket implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while(true) {
            synchronized (this) {//this ---当前对象
                if(ticket<=0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"票");
                ticket--;
            }
        }
    }
}

注意:临界资源对象要的是一个共享资源,可能就想到ticket,将它转为包装类Integer,这样不就行了嘛。但是,分析过Integer的底层代码可知道,在[-128,127]范围内Integer返回的是同一个对象,而不在该范围内的数字将重新实例化一个对象,也就是说临界资源对象不是用一个了,那别人也能访问了,这样就达不到互斥的效果了。

同步方法

  • 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中
  • 线程退出同步方法时,会释放相应的互斥锁标记
  • 如果方法是静态,锁是类名.class
public synchronized 返回值类型 方法名() { // 对当前对象(this)加锁
    // 代码(原子操作)
}

同步规则

  • 只有在调用包含同步代码块的方法,或同步方法,才需要对象的表级锁
  • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用

死锁

  • 当第⼀个线程拥有A对象锁标记,并等待B对象锁标记,同时第⼆个线程拥有B对象锁标记,并等待A对象锁标记时,产⽣死锁。
  • ⼀个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。

线程通信

常用方法

所有的等待、通知方法必须在对加锁的同步代码块中

⽅法说明
public final void wait()释放锁,进⼊等待队列
public final void wait(long timeout)在超过指定的时间前,释放锁,进⼊等待队列
public final void notify()随机唤醒、通知⼀个线程
public final void notifyAll()唤醒、通知所有线程

例子

public class Bread {
    private int id;
    private String productName;

    public Bread() {
       
    }

    public Bread(String productName, int id) {
        super();
        this.productName = productName;
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    @Override
    public String toString() {
        return "Bread{" +
                       "id=" + id +
                       ", productName='" + productName + ''' +
                       '}';
    }
}
public class BreadCon {
    private final Bread[] cons = new Bread[6];
    private int index = 0;

    public synchronized void input(Bread b) {
        while (index >= 5) {
            try {
                wait();
            } catch (InterruptedException e) { 
                throw new RuntimeException(e);
            }
        }
        cons[index] = b;
        System.out.println(Thread.currentThread().getName() + "生产了" + b.getId() + " ");
        index++;
        notifyAll();
    }

    public synchronized void output() {
        while (index <= 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        index--;
        Bread b = cons[index];
        System.out.println(Thread.currentThread().getName() + "消费了" + b.getId() + "生产者:" + b.getProductName());
        cons[index] = null;
        notifyAll();
    }
}
public class Consume implements Runnable{
    private BreadCon con;

    public Consume(BreadCon con) {
        this.con = con;
    }

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            con.output();
        }
    }
}
public class Product implements Runnable{
    private BreadCon con;

    public Product(BreadCon con) {
        this.con = con;
    }

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            con.input(new Bread(Thread.currentThread().getName(), i));
        }
    }
}
public class Test {
    public static void main(String[] args) {
        BreadCon con = new BreadCon();

        Product product = new Product(con);
        Consume consume = new Consume(con);

        Thread thread1 = new Thread(product, "明明");
        Thread thread2 = new Thread(product, "芳芳");
        Thread thread3 = new Thread(consume, "莉莉");
        Thread thread4 = new Thread(consume, "敏敏");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}