Android小知识-Java多线程的基础知识了解下

1,639 阅读9分钟

本平台的文章更新会有延迟,大家可以关注微信公众号-顾林海,包括年底前会更新kotlin由浅入深系列教程,目前计划在微信公众号进行首发,如果大家想获取最新教程,请关注微信公众号,谢谢!

十月份离职,在家修养一个多月,这一个多月做了很多事,自己的微信公众号开通了,博客也换了一种风格,在简书和掘金分享一些Android方面的小知识,这一个多月看了些书,有技术相关的,也有非技术相关的,突然间觉得的这种生活也挺不错的,这五年买了很多书,加起来最起码有四五箱的书,以前上班忙,只有晚上回来看个一两个小时,现在闲了,想全天看就全天看,读书是一辈子的事,喜欢读书,这样无论在什么时候都会有自己的思考和见地,不会一味地迎合或沉沦,从而失去立场,失去自己。当然在进行自我提升的同时也在看看有没有一些工作机会,目前刚回到上海,希望自己能找到一家与之奋斗的公司,一起成长下去。

闲话就扯到这里,下面进入正文。


在讲到多线程有必要了解下什么是进程,在百度百科上是这么定义进程的:进程是操作系统结构的基础;是一次程序的执行;是一个程序及其数据在处理上顺序执行时所发生的活动;是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。

百度百科对进程的定义比较抽象,举个例子,我们在电脑打开一个程序exe,那这个exe就可以理解成一个进程,进程是受操作系统管理的基本运行单元。那线程又是什么,线程是在进程中独立运行的子任务,比如打开腾讯视频(进程),你一边在看视频,一边在下载视频,同时在看视频时数据的传输等等,这些同时执行的任务都是线程,利用多线程可以同一时间内运行更多不同种类的任务。

public class Client {

    public static void main(String[] args){
        //输出main
        System.out.println(Thread.currentThread().getName());
    }

}

通过currentThread方法获取当前的线程名,上面这个程序在main入口函数中打印当前线程的名称,发现默认就有一个叫做main线程在执行main()方法中的代码。

在Java中实现多线程编程的方式有两种,一种是继承Thread类,另一种是实现Runnable接口,下面这个程序就使用第一种方式继承Thread类:

public class Task extends Thread {

    @Override
    public void run() {
        super.run();
        System.out.println("执行相关任务");
    }
}

Task类继承Thread,run方法中打印一句“执行相关任务”。

public class Client {

    public static void main(String[] args){
       Thread thread=new Task();
       thread.start();
       System.out.println("任务执行完毕!");
    }

}

在main函数中先创建Task实例并执行Task线程,接着打印“任务执行完毕!”,运行下看看什么结果。

任务执行完毕!
执行相关任务

发现先打印“任务执行完毕!”,后打印“执行相关任务”,也就是在使用多线程时,代码的运行结果与代码执行顺序或调用顺序是无关的。线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。

如果我们继承了Thread类,就不能继承其它类了,Java不支持多继承,那怎么办呢?幸好Java提供了Runnable接口,接下来看第二种方式实现Runnable接口来创建线程。

public class Task implements Runnable {
    @Override
    public void run() {
        System.out.println("执行相关任务");
    }
}

很简单,Task类实现了Runnable接口并实现run方法,怎么使用这个Task,和上面的Client一样,代码如下:

public class Client {

    public static void main(String[] args){
       Runnable runnable=new Task();
       Thread thread=new Thread(runnable);
       thread.start();
       System.out.println("任务执行完毕!");
    }

}

在编写多线程时容易遇到数据共享问题,多个线程可以访问一个变量,看下面程序:

public class Task implements Runnable {

    private int mTaskCount=0;

    @Override
    public void run() {
        mTaskCount++;
        System.out.println("执行第"+mTaskCount+"任务");
    }
}

在Task线程中对mTaskCount进行递增,下面是Client代码:

public class Client {

    public static void main(String[] args) {
        Runnable runnable = new Task();
        Thread thread_1 = new Thread(runnable);
        Thread thread_2 = new Thread(runnable);
        Thread thread_3 = new Thread(runnable);
        Thread thread_4 = new Thread(runnable);
        thread_1.start();
        thread_2.start();
        thread_3.start();
        thread_4.start();
        System.out.println("任务执行完毕!");
    }

}

输出如下:

任务执行完毕!
执行第2任务
执行第2任务
执行第3任务
执行第4任务

发现两个线程都打印了mTaskCount为2,产生了“非线程安全”问题,非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,影响程序的执行流程。在某些JVM中,mTaskCount的操作分成3个步骤,第一取得原有mTaskCount值,第二计算mTaskCount+1,第三对mTaskCount进行赋值;在这3个步骤中,如果遇到多个线程同时访问,会出现指令重排序的问题,也就是非线程安全问题。那怎么解决呢?可以在run方法前加上synchronized关键字:

public class Task implements Runnable {

    private int mTaskCount=0;

    @Override
   synchronized public void run() {
        mTaskCount++;
        System.out.println("执行第"+mTaskCount+"任务");
    }
}

这样输出时mTaskCount是依次递增的,在run方法前加上synchronized关键字,使多个线程在执行run方法时,以排队的形式进行处理。当一个线程试图调用run方法前,先判断run方法有没有上锁,如果上锁了,说明有其他线程在执行run方法,必须等其他线程执行完run方法,加锁的这段代码称为“互斥区”或“临界区”。一个线程想要执行同步方法里的代码时,需要先获取锁,如果获取不到锁,需要不断的尝试拿这把锁,直到能够拿到为止。

接着了解下Thread常用的几种方法:

  • isAlive()方法用于判断当前的线程是否处于活动状态,活动状态就是线程已经启动且尚未终止,线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。

  • sleep()方法的作用是在指定的毫秒数内让当前“正在执行的线程”休 眠(暂停执行)。

  • getId()方法的作用是获取线程的唯一标识。

线程的开启是如此的简单,但我们有时需要在满足一定条件后关闭线程,这时如何去做呢?

可以通过interrupt()方法来停止线程,但interrupt()方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。在Java的SDK中,Thread提供了两种方法用于判断线程的状态是不是停止,分别是interrupted()方法,用于测试当前线程是否已经中断,还有一个就是isInterrupted()方法,用于测试线程是否已经中断。

先看Thread.interrupted()方法的使用:

public class Task implements Runnable {

    @Override
   synchronized public void run() {
        for (int i=0;i<1000;i++){
            System.out.println("i="+i);
        }
    }
}

在Task线程中通过for循环打印0到999。

public class Client {

    public static void main(String[] args) {
        Runnable runnable = new Task();
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(10);
            thread.interrupt();
            System.out.println(Thread.interrupted());
            System.out.println(Thread.interrupted());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

启动Task线程后,执行暂停10毫秒后调用interrupt()方法,最后打印出Thread.interrupted()方法两次,我们看打印结果:

i=0
i=1
i=2
...
i=248
false
false
i=249
...
i=998
i=999

通过interrupt()方法并不能停止Task线程,而执行Thread.interrupted()方法,输出两次都为false,也就是说Thread.interrupted()方法是用于测试当前线程是否已经中断,这个当前线程指的是main线程,它从未中断过,所以打印的结果是两个false,这里先看如何使main线程产生中断效果,看下面代码:

public class Client {

    public static void main(String[] args) {
        Thread.currentThread().interrupt();
        System.out.println(Thread.interrupted());
        System.out.println(Thread.interrupted());
    }

}

打印:

true
false

通过Thread.currentThread().interrupt()给当前main线程打上停止的标志,那为什么第二次输出Thread.interrupted()方法时是false呢?官方文档对interrupted()方法的解释如下:测试当前线程是否已经中断。线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回false。也就说interrupted()方法具有清除状态的功能。

isInterrupted()方法与interrupted()方法相比,isInterrupted()方法并不具有清除状态,也就是我们给Task线程执行interrupt()方法后,Task线程就被打上了中断状态,不管执行多少次isInterrupted()方法都会返回true。

既然知道了interrupt()的作用,如果先执行task线程的interrupt()方法,这时Task线程被打上中断状态,然后再在Task的run方法中通过判断Thread.interrupted()是否为true,如果为true就退出循环,代码如下:

public class Task implements Runnable {

    @Override
   synchronized public void run() {

        for (int i=0;i<1000;i++){
            if(Thread.interrupted()){
                break;
            }
            System.out.println("i="+i);
        }
    }
}

Client代码如下:

public class Client {

    public static void main(String[] args) {
        Runnable runnable = new Task();
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(10);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

打印:

i=0
i=1
i=2
...
i=284
i=285
i=286
i=287
i=288

这样不就可以在外部中断了Task线程,这种方式虽然可以停止了Task线程,但如果在for语句下打印一句话,代码:

public class Task implements Runnable {

    @Override
   synchronized public void run() {

        for (int i=0;i<1000;i++){
            if(Thread.interrupted()){
                break;
            }
            System.out.println("i="+i);
        }
        System.out.println("不应该打印");
    }
}

打印:

i=0
i=1
i=2
...
i=223
i=224
i=225
不应该打印

发现for循环语句下面的的println还是打印出来了,这时可以在判断Thread.interrutped()语句中通过抛出异常来退出,代码如下:

public class Task implements Runnable {

    @Override
    synchronized public void run() {
        try {
            for (int i = 0; i < 1000; i++) {
                if (Thread.interrupted()) {
                    throw new InterruptedException();
                }
                System.out.println("i=" + i);
            }
            System.out.println("不应该打印");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

打印:

i=0
i=1
i=2
...
i=106
i=107
i=108
java.lang.InterruptedException
    at com.book.demo.demo01.Task.run(Task.java:10)
    at java.lang.Thread.run(Thread.java:745)

这种方式叫做异常法退出。

当然也可以通过return来退出线程:

public class Task implements Runnable {

    @Override
    synchronized public void run() {
        for (int i = 0; i < 1000; i++) {
            if (Thread.interrupted()) {
                return;
            }
            System.out.println("i=" + i);
        }
        System.out.println("不应该打印");

    }
}

关于多线程的相关知识后面还有很多相关文章,等不及的可以在微信公众号上查看,谢谢!


838794-506ddad529df4cd4.webp.jpg