Java多线程(一):线程(Thread)的使用

1. 相关概念

进程是执行程序的一次执行过程,是一个动态的概念。它是系统进行资源分配和调度的基本单位。进程之间相互独立。

通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是程序执行的最小单位

关键概念:

  • 原子性: 指一个操作不会被中断
  • 可见性: 当某一个线程修改了某一共享变量的值,其它线程能马上直到这个修改,并获得新的值。
  • 有序性: 在并发情况下程序依旧可以有序的执行(受指令重排序影响,并发程序执行时可能会出现乱序)

2. 线程的生命周期

线程的生命周期
在使用线程之前,需要了解下它的生命周期,即线程运行过程中的几个阶段。

线程的生命周期包括五个阶段:

  • 新建: 当创建了一个Thread类的对象后,此线程进入新建状态,此时线程还未被启动。
  • 就绪: 调用了线程的start()方法后,此线程就进入了就绪状态;这时候线程处于等待CPU分配资源阶段,只要获得了CPU的资源,就可以开始执行。
  • 运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态(开始执行run()方法)。
  • 阻塞:
    • 线程在运行时,可能会因为一些原因(如wait()lock()等方法)进入阻塞状态;
    • 进入阻塞状态的线程需要某些特殊的机制将其唤醒,如notify()方法、等待时间结束等;
    • 被唤醒的线程不会直接被执行,而是会进入到就绪状态,与其它线程一起竞争CPU资源。
  • 死亡: 当线程执行完毕或由于一些原因被杀死,线程就进入死亡状态,此线程将被销毁,同时释放资源。

3. 线程的创建

在了解的线程的生命周期之后,就可以开始使用线程了。

线程的创建方法有3种:

  • 继承Thread类,重写run方法

    public class TestThread extends Thread {
    
        public TestThread(String threadName) {
            super(threadName);
        }
    
        @Override
        public void run() {
            System.out.println("利用Thread创建线程:" + Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            new TestThread("线程1").start();
            new TestThread("线程2").start();
        }
    }
    
  • 实现Runnable接口

    public class TestRunnable implements Runnable {
    
        @Override
        public void run() {
            System.out.println("利用Runnable创建线程:" + Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            new Thread(new TestRunnable(), "线程1").start();
            new Thread(new TestRunnable(), "线程2").start();
        }
    }
    
  • 实现Callable接口

    public class TestCallable implements Callable<Boolean> {
    
        @Override
        public Boolean call() {
            System.out.println("利用Callable创建线程:" + Thread.currentThread().getName());
            return true;
        }
    
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newFixedThreadPool(4);
            
            for (int i = 0; i < 2; i++) {
                executorService.submit(new TestCallable());
            }
    
            // 执行完所有任务后,关闭线程池(不再接受新任务)。如果线程池已经关闭,则调用没有其他效果。
            executorService.shutdown();
        }
    }
    

4. 线程的中止

Thread方法提供了一个stop()方法,但它已经被标注为废弃的方法,并不推荐使用。原因是因为stop()方法太过暴力,强行中止一个允许了一半的线程,可能会引起数据不一致的问题

想要结束一个线程,一般来说有以下几种方法:

  • 方式一:设置标志位

    如果想要中止一个线程,最好的办法就是让他“自己中止”。可以通过设置标志位的方法,在线程的内部根据标志位的值选择是否需要中止线程。当然,为避免数据不一致的问题,线程应该选择一个安全的地方停止。

// 通过设置标志位来停止线程
public class StopThread implements Runnable {
    private boolean stopFlag = false; // 线程停止的标志位

    @Override
    public void run() {
        Date nowTime = null;
        while(true) {
            nowTime = new Date(System.currentTimeMillis());
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(nowTime));
            if (stopFlag) {
                //在合适的地方判断标志位的值,以选择是否停止线程
                System.out.println("线程已停止");
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void setStopFlag(boolean stopFlag) {
        this.stopFlag = stopFlag;
    }

    public static void main(String[] args) {
        StopThread stopTest = new StopThread();
        Thread thread = new Thread(stopTest);
        thread.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        stopTest.setStopFlag(true);
    }
}
  • 方式二:使用中断(interrupt)

    JDK也为此提供的相关的支持,利用中断也可以实现类似的功能

方法功能
public void Thread.interrupt()中断线程
public boolean Thread.isInterrupted()判断线程是否被中断,可在括号中加上参数true,表示清除当前的中断状态
public static boolean Thread.interrupted()判断线程是否被中断,并清楚当前的中断状态,其实现其实就是内部调用并返回了currentThread().isInterrupted(true)
public class StopByInterrupt {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            Date nowTime = null;
            while(true) {
                nowTime = new Date(System.currentTimeMillis());
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(nowTime));
                if (Thread.currentThread().isInterrupted()) {
                    //在合适的地方判断标志位的值,以选择是否停止线程
                    System.out.println("线程已停止");
                    break;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    /*
                    * public static native void sleep() throws InterruptedException
                    * 类似sleep()和wait()这样的操作
                    * 会被interrupt打断,同时抛出InterruptedException异常
                    * 并清除中断标志位
                     */
                    e.printStackTrace();
                    System.out.println("捕获了线程中sleep的InterruptedException异常");
                    // 我们需要对异常进行捕获并处理,这里选择再次中断自己,是为了让线程可以正常进入到中断处理程序中,以保证数据的一致性
                    Thread.currentThread().interrupt();
                }
            }
        });
        thread.start();

        Thread.sleep(5000);

        thread.interrupt();
    }
}

5. 线程间通讯

在Java中,不同的线程之间具备一定的通讯能力。为了支持多线程之间的协作,JDK针对synchronized和Lock分别提供了相应的通讯方法。

5.1 synchronized中的通讯(wait() 和 notify())

JDK为synchronized提供了两个非常重要的接口线程:等待 wait()方法和通知notify()方法。这两个方法并不在Thread类中,而是在 Object 类中定义。这也意味着任何对象都可以调用这两个方法。

方法功能
public final void wait() throws InterruptedException在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待(进入这个对象的等待队列)
public final native void notify()在一个对象实例上调用notify()方法后,它就会从这个对象的等待队列中随机唤醒一个线程

注意点:

  • wait()notify()方法必须synchronized语句中使用
  • 无论是wait()还是notify()执行前都需要首先获得目标对象的一个监视器
  • 无论是wait()还是notify()执行后都会释放它所持有的监视器
public class TestWait {

    public static void main(String[] args) {
        Object communicationObject = new Object();
        String[] products = new String[2];
        Producer producer = new Producer(communicationObject, products);
        Consumer consumer = new Consumer(communicationObject, products);

        new Thread(producer, "生产者1").start();
        new Thread(producer, "生产者2").start();
        new Thread(producer, "生产者3").start();

        new Thread(consumer, "消费者1").start();
        new Thread(consumer, "消费者2").start();
        new Thread(consumer, "消费者3").start();
    }
}

class Producer implements Runnable {
    private final Object communicationObject; // 通讯对象
    private String[] products;

    public Producer(Object communicationObject, String[] products) {
        this.communicationObject = communicationObject;
        this.products = products;
    }

    /**
     * 生产者生产产品
     * @return 表示是否生产成功
     */
    public boolean produce() {
        for (int i = 0; i < products.length; i++) {
            if (null == products[i]) {
                products[i] = String.valueOf(i);
                System.out.println(Thread.currentThread().getName() + ": 生产了产品" + products[i]);
                return true;
            }
        }
        return false;
    }

    @Override
    public void run() {
        int number = 0;
        // 生产产品
        while (number < 5) {
            synchronized (communicationObject) {
                if (produce()) {
                    // 生产成功则通知消费者进行消费
                    number++;
                    communicationObject.notify();
                } else {
                    // 仓库存满了,等待消费者消费
                    try {
                        communicationObject.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable {
    private final Object communicationObject; // 通讯对象
    private String[] products;

    public Consumer(Object communicationObject, String[] products) {
        this.communicationObject = communicationObject;
        this.products = products;
    }

    /**
     * 消费者消费产品
     * @return 表示是否消费成功
     */
    public boolean consume() {
        for (int i = 0; i < products.length; i++) {
            if (products[i] != null) {
                System.out.println(Thread.currentThread().getName() + ": 消费了产品" + products[i]);
                products[i] = null;
                return true;
            }
        }
        return false;
    }

    @Override
    public void run() {
        int number = 0;
        // 消费产品
        while (number < 5) {
            synchronized (communicationObject) {
                if (consume()) {
                    // 消费之后通知生产者可以开始生产了
                    number++;
                    communicationObject.notify();
                } else {
                    // 产品消费完了,等待生产
                    try {
                        communicationObject.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

5.2 Lock中的通讯(Condition)

wait()notify()相似,在锁(Lock)中提供了Condition接口以实现线程间通讯的功能。它们的用法十分的相似,唯一的不同就是Condition需要用在Lock语句块中。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestCondition {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock(); // 可重入锁实例
        Condition condition = lock.newCondition(); // 通过lock对象获取condition
        String[] products = new String[2];
        Producer producer = new Producer(lock, condition, products);
        Consumer consumer = new Consumer(lock, condition, products);

        new Thread(producer, "生产者1").start();
        new Thread(producer, "生产者2").start();
        new Thread(producer, "生产者3").start();

        new Thread(consumer, "消费者1").start();
        new Thread(consumer, "消费者2").start();
        new Thread(consumer, "消费者3").start();
    }
}

class Producer implements Runnable {
    private final Lock lock; // 可重入锁实例
    private final Condition condition; // Condition实例
    private String[] products; // 产品仓库

    public Producer(Lock lock, Condition condition, String[] products) {
        this.lock = lock;
        this.condition = condition;
        this.products = products;
    }

    /**
     * 生产者生产产品
     * @return 表示是否生产成功
     */
    public boolean produce() {
        for (int i = 0; i < products.length; i++) {
            if (null == products[i]) {
                products[i] = String.valueOf(i);
                System.out.println(Thread.currentThread().getName() + ": 生产了产品" + i);
                return true;
            }
        }
        return false;
    }

    @Override
    public void run() {
        int number = 0;
        // 生产产品
        while (number < 5){
            lock.lock();
            if (produce()) {
                // 生产成功则通知消费者进行消费
                number++;
                condition.signal();
            } else {
                // 仓库存满了,等待消费者消费
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            lock.unlock();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable {
    private final Lock lock;
    private final Condition condition;
    private String[] products;

    public Consumer(Lock lock, Condition condition, String[] products) {
        this.lock = lock;
        this.condition = condition;
        this.products = products;
    }

    /**
     * 消费者消费产品
     * @return 表示是否消费成功
     */
    public boolean consume() {
        for (int i = 0; i < products.length; i++) {
            if (products[i] != null) {
                System.out.println(Thread.currentThread().getName() + ": 消费了产品" + products[i]);
                products[i] = null;
                return true;
            }
        }
        return false;
    }

    @Override
    public void run() {
        int number = 0;
        // 消费产品
        while (number < 5){
            lock.lock();
            if (consume()) {
                // 消费之后通知生产者可以开始生产了
                number++;
                condition.signal();
            } else {
                // 产品消费完了,等待生产
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            lock.unlock();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

5.3 挂起(suspend)和继续执行(resume)

已经被标注为废弃方法,不推荐使用!

原因是suspend()挂起线程的同时,并不会释放锁资源。利用wait()notify()方法可以很好的代替它。

6. 加入(join,等待线程结束)和谦让(yeild)

一个线程的输入可能会依赖于另一个或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕。JDK提供了jion()方法来实现这个功能。

yield()则是一个比较有趣的方法,它会使当前的线程让出CPU,让出后再去重新竞争CPU的资源。

方法功能
public final void join() throws InterruptedException一直阻塞当前线程,直到目标线程执行完毕(无限期等待)
public final synchronized void join(long millis) throws InterruptedException阻塞当前线程,等待目标线程执行,如果等待时间超过最大等待时间,就放弃等待继续往下执行
public static native void yield()静态方法:使当前线程让出CPU,让出后再去重新竞争CPU的资源
  • join()方法的 本质是调用当前线程对象实例上的wait()

    // 相当于在线程中调用threadA.wait();
    while (isAlive()) {
        wait(0); // synchronized void join(long millis)
    }
    
  • 被等待的线程执行完毕之后,会在退出前调用notifyAll()方法通知所有等待程序继续执行。 因此,不要在应用程序中调用Thread实例上的类似wait()notify(),这可能会影响系统API或者被系统API影响。

    // JVM源码中
    lock.notify_all(thread);
    
  • 可以被interrupt打断

join举例:

public class JoinThread {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("进入:" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("退出:" + Thread.currentThread().getName());
        }, "join测试线程");

        thread.start();
        // 等待join测试线程执行完毕
        thread.join();
        /*
        * 等价于:
        * synchronized (thread) {
        *     thread.wait();
        * }
         */
        System.out.println("join测试线程执行完毕,程序继续执行。");
    }
}

yield()举例:

public class YieldThread implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "执行到:" + i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (0 == i % 2) {
                Thread.yield();
            }
        }
    }

    public static void main(String[] args) {
        YieldThread yieldRunnable = new YieldThread();
        for (int i = 0; i < 3; i++) {
            new Thread(yieldRunnable, "线程" + i).start();
        }
    }
}
/*
执行结果:
线程0执行到:0
线程1执行到:0
线程2执行到:0
线程2执行到:1
线程0执行到:1
... ...
*/

参考书籍: Java高并发程序设计(第二版)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值