胡豆秆

个人博客

欢迎来到我的个人博客~


Java多线程


线程和进程的区别总结:

  1. 进程是一段正在执行的程序,是资源分配的基本单元,而线程是CPU调度的基本单元。
  2. 进程间相互独立、不能共享资源,一个进程至少有一个线程,同一进程的各线程共享整个进程的资源(寄存器、堆栈、上下文)。
  3. 线程的创建和切换开销比进程小。

public class Thread extends Object implements Runnable

​ 线程是程序中执行的线程。 Java虚拟机允许应用程序同时执行多个执行线程。

​ 每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。 每个线程可能也可能不会被标记为守护程序。 当在一些线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护线程。

​ 创建一个新的执行线程有两种方法:继承Thread类和实现Runnable接口。

//创建Thread对象并重写run方法
new Thread() {
    @Override
    public void run() {
        System.out.println("第一个线程执行...");
    }
}.start();
//创建Thread对象并传入一个实现Runnable接口的匿名内部类
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("第二个线程执行...");
    }
}).start();

执行结果:


线程状态

线程状态 Thread.State。 线程可以处于以下状态之一:

NEW

​ 线程尚未启动的线程状态。线程创建未调用start方法前,就为NEW状态。

RUNNABLE

​ 可运行线程的线程状态。 可运行状态的线程正在Java虚拟机中执行,但它可能正在等待来自操作系统(例如处理器)的其他资源。

BLOCKED

​ 一个线程的线程状态阻塞等待监视器锁定。 处于阻塞状态的线程正在等待监视器锁定进入同步块/方法,或者在调用Object.wait后重新输入同步的块/方法。

WAITING

等待线程的线程状态 由于调用以下方法之一,线程处于等待状态:

  • 不带超时值的 Object.wait

  • 不带超时值的 Thread.join

  • LockSupport.park

​ 处于等待状态的线程正等待另一个线程,以执行特定操作。 例如,已经在某一对象上调用了 Object.wait() 的线程正等待另一个线程,以便在该对象上调用 Object.notify() 或 Object.notifyAll()。已经调用了 Thread.join() 的线程正在等待指定线程终止。

TIMED_WAITING

具有指定等待时间的等待线程的线程状态。 线程处于定时等待状态,因为在指定的正等待时间内调用以下方法之一:

  • Thread.sleep
  • 带有超时值的 Object.wait
  • 带有超时值的 Thread.join
  • LockSupport.parkNanos
  • LockSupport.parkUntil

TERMINATED

​ 终止线程的线程状态。 线程已完成执行。


常用方法

public void start()

​ 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

Thread t = new Thread() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
};
t.start();

运行结果:

public void run()

​ 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。Thread 的子类应该重写该方法。

Thread t = new Thread() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
};
t.run();

运行结果:

​ 通过去start()方法对比发现,调用start方法打印出的当前线程名为 Thread-0 (即线程 t);而调用run方法打印出的当前线程确实main。结合网上查找的资料,得出结论:调用start方法为启动线程,该线程执行自身的run方法;调用run方法为在当前线程(此处为main线程)中执行run方法,并没有启动该run方法所属的线程。直接调用run方法违背了创建线程的初衷。

public long getId()

​ 返回该线程的标识符。线程 ID 是一个正的 long 数,在创建该线程时生成。线程 ID 是惟一的,并终生不变。线程终止时,该线程 ID 可以被重新使用。

public final String getName()

​ 返回该线程的名称。

public final int getPriority()

​ 返回线程的优先级。

public Thread.State getState()

​ 返回该线程的状态。 该方法用于监视系统状态,不用于同步控制。

public final ThreadGroup getThreadGroup()

​ 返回该线程所属的线程组。 如果该线程已经终止(停止运行),该方法则返回 null。

public final boolean isAlive()

​ 测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。

public final void setName(String name)

​ 改变线程名称,使之与参数 name 相同。

​ name - 该线程的新名称。

​ 首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。

public final void setPriority(int newPriority)

​ 更改线程的优先级。

​ newPriority - 要为线程设定的优先级

​ 首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。

​ 在其他情况下,线程优先级被设定为指定的 newPriority 和该线程的线程组的最大允许优先级相比较小的一个。

public static Thread currentThread()

​ 返回对当前正在执行的线程对象的引用。

​ 可配合着其他方法一起使用,如:

new Thread() {
    @Override
    public void run() {
        System.out.println("获取线程名:" + Thread.currentThread().getName());
        System.out.println("获取Id:" + Thread.currentThread().getId());
        System.out.println("获取优先级:" + Thread.currentThread().getPriority());
        System.out.println("获取线程状态:" + Thread.currentThread().getState());
    }
}.start();

执行结果如下:

public void interrupt()

​ 中断线程。所谓的中断线程,其实只是在该线程上加了一个中断标志,并不会使该线程停止或暂停执行被中断的线程还是会继续执行,这时可以通过调用interrupted或isInterrupted方法对该线程进行需要的操作处理。

​ 如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException。

​ 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。

​ 如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。

​ 如果该线程在一个 Selector 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。

​ 如果以前的条件都没有保存,则该线程的中断状态将被设置。

public static boolean interrupted()

​ 如果当前线程已经中断,则返回 true;否则返回 false。

​ 测试当前线程是否已经中断。线程的中断状态由该方法清除(该方法会清除该线程的中断状态,即撤销该线程的中断标志,成为未中断状态)。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。

public boolean isInterrupted()

​ 如果该线程已经中断,则返回 true;否则返回 false。

​ 测试线程是否已经中断。线程的中断状态 不受该方法的影响(不会清除该线程的中断状态)。


public void run() {
    //线程并未中断
    System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted());
    //中断线程
    Thread.currentThread().interrupt();
    //线程中断后并不会死亡,此时线程状态为RUNNABLE
    System.out.println("getState:" + Thread.currentThread().getState());
    //线程中断后isInterrupted为true
    System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted());
    //此时线程已经中断,调用interrupted返回true 并且清除线程的中断状态
    System.out.println("interrupted:" + Thread.interrupted());
    //线程中断状态已经被interrupted方法清除,所以此时isInterrupted为false
    System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted());
    //线程中断状态已被清除,调用interrupted返回false 并且清除线程的中断状态
    System.out.println("interrupted:" + Thread.interrupted());
    //线程中断状态已经被interrupted方法清除,所以此时isInterrupted为false
    System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted());
}

该方法执行结果如下:

在网上找线程中断的资料时,发现了一篇写的很详细的博客,在这里推荐一下:

https://www.cnblogs.com/yangming1996/p/7612653.html


public final void join()

​ 等待该线程终止。

public final void join(long millis)

​ 等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。

public final void join(long millis, int nanos)

​ 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

​ join函数一般用于线程需要一定顺序执行的情况。在一个线程中调用另一个线程的join函数(如t.join),表示这个线程需要等待另一个线程执行结束后才继续执行。

static int count = 10;

public static void main(String[] args) throws Exception {
    Thread t1 = new Thread() {
        @Override
        public void run() {
            while(count > 5) {
                System.out.println(Thread.currentThread().getName() + "正在执行");
                count --;
            }
        }
    };
    Thread t2 = new Thread() {
        @Override
        public void run() {
            while(count > 0) {
                System.out.println(Thread.currentThread().getName() + "正在执行");
                count --;
            }
        }
    };
    System.out.println("main线程执行,准备执行t1");
    t1.start();
    //main线程需等待t1执行完毕才可继续执行
    t1.join();
    System.out.println("main线程继续执行,准备执行t2");
    t2.start();
    //main线程需等待t2执行完毕才可继续执行
    t2.join();
    System.out.println("main线程执行结束");
}

程序运行结果如下:

如将程序中的t1.join()和t2.join()注释掉,main线程就不会等待t1和t2先执行完毕,运行结果就会不一样。如下:


public static void sleep(long millis)

​ 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。该线程不丢失任何监视器的所属权。

public static void sleep(long millis,int nanos)

​ 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)。该线程不丢失任何监视器的所属权。

​ 线程sleep不会释放资源。线程若在拿到锁资源之后进入了休眠,其它等待该锁资源的线程也需等待,因为该锁资源并没有被释放。

static int count = 0;
public static void main(String[] args) throws Exception {
    new Thread() {
        @Override
        public void run() {
            while(true) {
                synchronized ("锁对象") {
                    System.out.println(Thread.currentThread().getName() + "开始计时:" + count + "s");
                    count ++;
                    try {
                        //线程休眠1秒
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }
    }.start();
    new Thread() {
        @Override
        public void run() {
            while(true) {
                synchronized ("锁对象") {
                    System.out.println(Thread.currentThread().getName() + "开始计时:" + count + "s");
                    count ++;
                    try {
                        //线程休眠1秒
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }
    }.start();
}

​ 上述程序中,两个线程使用了锁对象为字符串常量 :”锁对象” ,两个线程同步打印计时并休眠1秒。运行结果如下,其中该结果中每行打印时间间隔1秒,并没有因为t1休眠,而t2立即抢到资源打印的情况。

​ 在线程的run方法中,sleep()方法需要使用try-catch包裹,即使main方法上已声明异常抛出;若main方法上已声明异常抛出,main方法中调用sleep()方法时,则不用try-catch包裹。


public static void yield()

​ API中对该方法的解释为:暂停当前正在执行的线程对象,并执行其他线程。

​ yiled方法和sleep方法都不释放锁资源,在这里并没有写出合适的程序证明。

​ 这样很容易让人产生误解,觉得调用这个方法就是暂停当前线程,让其他线程执行,这样的理解其实不对的。yield 使当前线程让出 CPU 时间片,线程从运行状态(Running)变为可执行状态(Runnable),处于可执行状态的线程有可能会再次获取到时间片继续执行,也有可能处于等待状态,直到再次获取到时间片。也就是说,后续会有两种情况:

  1. 当前线程让出 CPU 时间片后,又立即获取到 CPU 时间片,进而继续执行当前方法。
  2. 当前线程让出 CPU 时间片后,等待一段时间后获取到 CPU 时间片,进而继续执行当前方法。
public static void main(String[] args) throws Exception {
    Thread t1 = new Thread() {
        @Override
        public void run() {
            while (count < 10) {
                System.out.println("调用yield之前");
                Thread.yield();
                System.out.println(Thread.currentThread().getName() + "执行");
                count++;
            }
        }
    };
    Thread t2 = new Thread() {
        @Override
        public void run() {
            while (count < 10) {
                System.out.println(Thread.currentThread().getName() + "执行");
                count++;
            }
        }
    };
    t1.start();
    t2.start();
}

该程序运行结果如下(多次运行结果不一定一样):

​ 从上述代码及运行结果可推测出:线程调用yiled方法后,让出CPU时间片;所有线程再次抢夺CPU时间片;当再次抢到时间片时再继续执行。观察每次执行的结果,可以发现线程0的在打印 ‘调用yield之前’ 之后,让出了时间片,然后线程0和线程1一起再次抢夺时间片。若线程1抢到时间片之后,执行线程1的run方法,执行完毕之后两个线程再次抢夺时间片。可能是线程1再次抢到再次执行,如第一次运行结果,也可能是线程0抢到,继续执行线程0的run方法(注:是继续执行,并非重新执行),打印 ‘Thread-0执行’ 。

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦