手记

Java 多线程(一)

1. 多线程使用方法

使用多线程,绝大部分情况都是通过如下两种方式实现的,即继承Thread类或者实现Runnable接口。以下对两种方式分别进行介绍并比较。

1.1 使用Thread类实现多线程

自定义线程类要继承 Thread 类,并在 run 方法中实现自己的逻辑。参数的传递,可以采用构造方法传参或者设值的方式。

public class MyThread extends Thread
{
    private String name;

    public MyThread(String name)
    {
        this.name = name;
    }

    public void run()
    {
        for (int i = 0;i < 5;i++)
        {
            System.out.println("Thread name is " + name + ", say hello " + i);
            try
            {
                Thread.sleep(500);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args)
    {
        Thread alice = new MyThread("Alice");
        Thread bob = new MyThread("Bob");
        alice.start();
        bob.start();
    }
}

1.2 使用Runnable接口实现多线程

自定义线程类要实现 Runnable 接口,重写 run 方法。参数的传递,可以采用构造方法传参或者设值的方式。

package com.example.demo.mutilThread;

public class MyRunnable implements Runnable
{
    private String name;

    public MyRunnable(String name)
    {
        this.name = name;
    }

    public void run()
    {
        for (int i = 0;i < 5;i++)
        {
            System.out.println("RunnableThread name is " + name + ", say hello " + i);
            try
            {
                Thread.sleep(500);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args)
    {
        Thread cache = new Thread(new MyRunnable("Cache"));
        Thread dodge = new Thread(new MyRunnable("Dodge"));
        cache.start();
        dodge.start();
    }
}

1.3 小结

线程创建完毕之后,调用Thread的start方法,该并不是直接开始执行该线程,而是将该线程设置成为可运行的状态,即随时都都可以运行。至于什么时候真正的开始运行,是由操作系统的调度决定的。

在自定义线程类中重写的是run方法,但是在使用多线程的时候,调用的是start方法。打开start方法的实现,可以看到其中调用了本地方法start0,而在该方法中,最终调用了线程实例的run方法。如果在主线程中不使用start,而是使用run方法,那么则不会新开辟一个线程来执行其中的逻辑,而是直接在主线程中执行,跟普通的方法一样。

通过执行结果可以看到,多个线程之间的程序执行是乱序的,并不是每个线程按照某种固定的顺序执行。但是可以通过设计对应的控制逻辑,使得多个线程的执行变得有序。

对比两种方式,其实更加推荐使用Runnable的方式,因为实现Runnable接口,可以规避java中的单继承的限制,并且对于线程池来说,不能直接放入继承Thread的类。另外,Runnable的代码可以被多个线程共享(即用一个Runnable实现类初始化多个Thread实例),适合于多个线程处理同一资源的情况,比如买票场景。

2.线程状态

线程的各个状态以及之间的转换条件和路径如下图所示:

初始状态:Thread实例初始化完成。

可运行状态:调用线程实例的start方法后,线程进入到可运行线程池中等待运行。

运行状态:处于可运行状态的线程获取到了CPU,进入到运行状态,开始执行线程逻辑。

阻塞状态:阻塞状态是因为处于正在运行状态的线程放弃CPU。一般阻塞状态分为3种:

  • 等待阻塞:线程执行了对象的wait方法。此时线程会释放持有的锁,被放置进入JVM的等待池中。
  • 同步阻塞:线程在尝试获取同步锁失败,被放置进入锁池中。
  • 其他阻塞:调用sleep方法或者join方法,或者等待IO的时候。

死亡状态:线程执行完毕或者异常退出run方法。

3 常用函数

  1. Thread.sleep() 函数:该函数会使对应的线程休眠指定毫秒的时间。休眠结束之后会进入到可运行状态。执行休眠的时候不会释放锁。

  2. Thread.yield() 函数:该函数会使当前线程让出CPU,从运行状态变成可运行状态。之后所有处于可运行状态的线程竞争CPU。高优先级的线程会优先被OS调度执行。如果是相同优先级,则平等竞争,但是存在一种情况是,让出CPU的线程被OS再次选中执行。需要注意的是,yield函数不会导致线程转到阻塞状态。

  3. thread.join() 函数:该方法是让当前线程等待其他线程终止。比如在A线程中调用线程B.join(),那么执行的效果就是线程A从运行状态变成阻塞状态,一直等待线程B执行完毕,线程A才会转为可运行状态。join方法一般用于主线程中需要使用子线程的执行结果的情况。

  4. thread.setPriority() 函数:设置线程的执行优先级。java的线程包含10个优先级,用1-10表示,数字越大,优先级越高。默认的优先级是5.

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;
  1. object.wait()函数:该函数在synchronized代码块中使用,作用是使当前线程进入到等待状态,并释放锁。直到其他线程调用该对象的notify或者notifyAll方法进行唤醒,被唤醒之后,需要首先获取到对象锁,才能执行逻辑。

  2. object.notify()函数:唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程唤醒。

  3. object.notifyAll()函数:功能与notify类似,不同点在于notifyAll函数唤醒对象监视器上等待的所有线程。

1人推荐
随时随地看视频
慕课网APP