Java并发之线程基础知识

0. 序言

  • Java并发,既然重点,亦是难点。
  • 并发,即多线程,指多个线程同一时间去做多个事情,示例:炒菜哥,一人同时炒7个菜。
  • 并发相对的是单线程,指一件事情做完才做其他事情,示例:炒完一个菜再去炒第二个菜。
  • 并发不同于并行,并行指的是两个或更多任务同时进行,而并发指的两个任务都请求执行,而处理只能接受一个任务,就把两个任务安排轮流执行,由于时间间隔较短,使人感觉两个任务都在运行。
  • 本片博文的主要内容有:
    • 创建线程常用的三种方式
    • 线程的生命周期
    • 线程的加入
    • 线程的中断
    • 线程的礼让
    • 线程的优先级

1. 创建线程常用的三种方式

  • 继承Thread类
public class Main {

    public static void main(String[] args) {
        new ThreadTest().start();
    }

    public static class ThreadTest extends Thread{
        private int count = 10;
        public void run(){
            while (true){
                System.out.print(count+" ");
                if (--count==0){
                    return;
                }
            }
        }
    }
}

说明:步骤:
① 定义类继承Thread
② 重写run方法
③ 把新线程要做的事写在run方法中
④ 创建线程对象,并自动执行run方法。

  • 实现Runnable接口
    如果需要继承其他类(非Thread类),而且还要使当前类实现多线程,那么可以通过Runnable接口来实现。
public class Main {

    public static void main(String[] args) {
        Runnable runnable = new ThreadTest();
        Thread thread = new Thread(runnable);
        thread.start();
    }

    public static class ThreadTest implements Runnable{
        private int count = 10;
        public void run(){
            while (true){
                System.out.print(count+" ");
                if (--count==0){
                    return;
                }
            }
        }
    }
}

说明:步骤:
① 定义类实现Runnable接口
② 重写run方法
③ 将要写的代码写在run方法中
④ 使用参数为Runnable对象的构造方法创建Thread实例
⑤ 调用start方法启动线程

  • 实现Callable接口
    Callable接口属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要表现为以下3点:
    ① Callable可以在任务结束后提供一个返回值,Runnable无法提供这个功能。
    ② Callable中的call()方法可以抛出异常,而Runnable的run()方法不能抛出异常。
    ③ 运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下就可以使用Future来监视目标线程调用Call()方法的情况。但调用Future的get()方法以获取结果时,当前线程就会阻塞,直到Call()方法返回结果。
public class Test implements Callable {

    public static void main(String[] args) {
        Test test = new Test();
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future submit = executorService.submit(test);
        try {
            Object o = submit.get();
            System.out.println(o);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }finally {
            executorService.shutdown();
        }
    }

    @Override
    public Boolean call() throws Exception {
         return true;
    }
}
true

说明:这里的情况还需要后续的文章进行补充,通过这个小例子,直到通过Callable接口可以在任务结束后得到一个返回值即可。

2. 线程的生命周期

  • 出生状态:New
Thread thread1 = new Thread(instance);
  • 就绪状态(可执行状态):Ready
thread1.start();

说明:用户调用start()方法后,线程就处于就绪状态。此时并没有得到系统资源。

  • 运行状态:Runnable
    说明:当线程得到系统资源后就进入运行状态。
  • 等待状态:Wating
    try {
            thread1.wait();
        } catch (InterruptedException e) {
            e

说明:等待状态的线程必须调用Thread类中的nofify()方法才能被唤醒;notifyAll()方法是将所有处于等待状态下的线程唤醒。

  • 休眠状态(超时等待状态):Time waiting
Thread.sleep(5000);
  • 阻塞状态:Blocked
    说明:当一个线程在运行状态下发出输入/输出请求,该线程进入阻塞状态;在其等待输入/输出结束时线程进入就绪状态,对于阻塞的线程来说,即使系统资源空闲,线程依然不能回到运行状态。
  • 终止状态(死亡状态):Terminated
    说明:有两种情况,分别是
    ① 当线程的run方法执行完毕时。
    ② 因为一个没有捕获的异常而终止了run方法。
  • 使线程处于就绪状态的方法
    ① 调用sleep()方法
    ② 调用wati()方法
    ③ 等待输入输出完成
  • 使线程处于运行状态的方法
    ① 线程调用notify()方法
    ② 线程调用notifyAll()方法
    ③ 线程调用interrupt()方法
    ④ 线程的休眠时间结束
    ⑤ 输入/输出结束

3. 线程的加入

  • 举例说明:
public class Test_Join {

    private static int A = 0;
    private static int B = 0;
    private  Thread thread01;
    private  Thread thread02;

    public Test_Join() {
         thread02 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                if (B == 99) {
                    System.out.println("B==99");
                }
                B++;
            }
        });

          thread01 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                if (A == 99) {
                    System.out.println("A == 99");
                }
                A++;
//                try {
//                    thread02.join();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }
        });
    }

    private Thread getThread01() {
        return thread01;
    }

    private Thread getThread02() {
        return thread02;
    }

    public static void main(String[] args) {
        new Test_Join().getThread01().start();
        new Test_Join().getThread02().start();
    }
}

说明:
① 两个线程分别给A或B++的操作,线程01先执行,应该是线程01先到99。
② 添加thread02.join();以后,线程1在执行了一个A++以后,线程2就开始执行,直到执行结束,线程1才执行。

4. 线程的中断

  • 布尔值标记的运用
public class Test_Interrupt implements Runnable {

    private static boolean isContinue = false;
    private static int num = 0;
    static Test_Interrupt runnable = new Test_Interrupt();

    @Override
    public void run() {
        while (true){
            if (isContinue){
                break;
            }else{
                num++;
            }
        }
    }

    private static void setContinue(){
        isContinue = true;
    }

    public static void main(String[] args) {
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(50);
            setContinue();
            System.out.println(num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

说明:以往有的时候会使用stop()方法来停止线程,但现在的JDK早就废除了stop()方法,不建议使用。现在提倡在run()方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止。

  • interrupt()方法的运用
    如果线程是因为使用了sleep()或wait()方法进入了就绪状态,可以使用Thread类的interrupt()方法使线程离开run()方法,同时结束线程,但程序会抛出InterruptedException异常,用户可以在处理该异常时完成线程的中断业务处理,如终止while循环。在项目中经常在这里执行关闭数据库连接和关闭Socket连接等操作。
public class Test_Interrupt implements Runnable {

    private static boolean isContinue = false;
    private static int num = 0;
    static Test_Interrupt runnable = new Test_Interrupt();

    @Override
    public void run() {
        while (true) {
            if (isContinue) {
                break;
            } else {
                num++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("线程被中断");
                    setContinue();
                }
            }
        }
    }

    private static void setContinue() {
        isContinue = true;
    }

    public static void main(String[] args) {
        Thread thread = new Thread(runnable);
        thread.start();
        thread.interrupt();
    }
}

说明:由于调用了interrupt()方法,所以抛出了InterruptedException异常,在异常中我们关闭while循环。

5. 线程的礼让

  • Thread类提供了一共礼让方法yield()表示,不过它只是给当前正处于运行状态下的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅仅只是一个暗示,没有任何一种机制保证当前线程会将资源礼让。
  • 对于支持多任务的操作系统来说,不需要调用yield()方法,因为操作系统会为线程自动分配CPU时间片来执行。

6. 线程的优先级

  • 现在操作系统采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也决定了线程使用处理器资源的多少,而线程的优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。
  • 在Java线程中,通过整型成员变量priority来控制优先级,范围是1~10,线程创建的时候用setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置浇地的优先级,确保处理器不会被独占。
  • 然而在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。所以程序正确性不能依赖线程的优先级高低。

7. 后续

如果大家喜欢这篇文章,欢迎点赞!
如果想看更多 Java并发 方面的技术,欢迎关注!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,037评论 0 23
  • 单任务 单任务的特点是排队执行,也就是同步,就像再cmd输入一条命令后,必须等待这条命令执行完才可以执行下一条命令...
    Steven1997阅读 1,126评论 0 6
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,783评论 3 53
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,922评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,377评论 1 15