Java多线程(一)多线程基础

前言

本文主要讲解java多线程的基础,以及一些常用方法。关于线程同步、ExecutorService框架我会放到后续的文章进行讲解。


进程与线程的区别

进程

进程简单的来说就是在内存中运行的应用程序,一个进程可以启动多个线程。
比如在windows中一个运行EXE文件就是一个进程。

线程

同一个线程中的进程共用相同的地址空间,同时共享进程所拥有的内存和其他资源。


线程Demo-继承Thread类

首先我们我们继承java.lang.Thread类来创建线程。

package top.crosssoverjie.study.Thread;

public class TestThread {
    public static void main(String[] args) {
        System.out.println("主线程ID是:" + Thread.currentThread().getId());
        MyThread my = new MyThread("线程1");
        my.start() ;
        
        MyThread my2 = new MyThread("线程2") ;
        /**
         * 这里直接调用my2的run()方法。
         */
        my2.run() ;
    }

}

class MyThread extends Thread {
    private String name;

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

    @Override
    public void run() {
        System.out.println("名字:" + name + "的线程ID是="
                + Thread.currentThread().getId());
    }

}

输出结果:

主线程ID是:1
名字:线程2的线程ID是=1
名字:线程1的线程ID是=9

由输出结果我们可以得出以下结论:

  • my和my2的线程ID不相同,my2和主线程ID相同。说明直接调用run()方法不会创建新的线程,而是在主线程中直接调用的run()方法,和普通的方法调用没有区别。
  • 虽然my的start()方法是在my2的run()方法之前调用,但是却是后输出内容,说明新建的线程并不会影响主线程的执行。

线程Demo-实现Runnable接口

除了继承java.lang.Thread类之外,我们还可以实现java.lang.Runnable接口来创建线程。

package top.crosssoverjie.study.Thread;

public class TestRunnable {
    public static void main(String[] args) {
        System.out.println("主线程的线程ID是"+Thread.currentThread().getId());
        MyThread2 my = new MyThread2("线程1") ;
        Thread t = new Thread(my) ;
        t.start() ;
        
        MyThread2 my2 = new MyThread2("线程2") ;
        Thread t2 = new Thread(my2) ;
        /**
         * 方法调用,并不会创建线程,依然是主线程
         */
        t2.run() ;
    }
}

class MyThread2 implements Runnable{
    private String name ;
    public MyThread2(String name){
        this.name = name ;
    }

    @Override
    public void run() {
        System.out.println("线程"+name+"的线程ID是"+Thread.currentThread().getId());
    }
    
    
}

输出结果:

主线程的线程ID是1
线程线程2的线程ID是1
线程线程1的线程ID是9

notes:

  • 实现Runnable的方式需要将实现Runnable接口的类作为参数传递给Thread,然后通过Thread类调用Start()方法来创建线程。
  • 这两种方式都可以来创建线程,至于选择哪一种要看自己的需求。直接继承Thread类的话代码要简洁一些,但是由于java只支持单继承,所以如果要继承其他类的同时需要实现线程那就只能实现Runnable接口了,这里更推荐实现Runnable接口。

实际上如果我们查看Thread类的源码我们会发现Thread是实现了Runnable接口的:


Thread源码

线程中常用的方法

序号 方法 介绍
1 public void start() 使该线程执行,java虚拟机会调用该线程的run()方法。
2 public final void setName(String name) 修改线程名称。
3 public final void setPriority(int privority) 修改线程的优先级。
4 public final void setDaemon(false on) 将该线程标记为守护线程或用户线程,当正在运行线程都是守护线程时,java虚拟机退出,该方法必须在启动线程前调用。
5 public final void join(long mills) 等待该线程的终止时间最长为mills毫秒。
6 public void interrupt() 中断线程。
7 public static boolean isAlive() 测试线程是否处于活动状态。如果该线程已经启动尚未终止,则为活动状态。
8 public static void yield() 暂停当前线程执行的对象,并执行其他线程。
9 public static void sleep(long mills) 在指定毫秒数内,让当前执行的线程休眠(暂停)。
10 public static Thread currentThread() 返回当前线程的引用。

方法详解- public static void sleep(long mills)

package top.crosssoverjie.study.Thread;

public class TestSleep {

    private int i = 10 ;
    private Object ob = new Object() ;
    
    public static void main(String[] args) {
        TestSleep t = new TestSleep() ;
        MyThread3 thread1 = t.new MyThread3() ;
        MyThread3 thread2 = t.new MyThread3() ;
        thread1.start() ;
        thread2.start() ;
    }
    
    class MyThread3 extends Thread{
        @Override
        public void run() {
            synchronized (ob) {
                i++ ;
                System.out.println("i的值:"+i);
                System.out.println("线程:"+Thread.currentThread().getName()+"进入休眠状态");
                try {
                    Thread.currentThread().sleep(1000) ;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("线程:"+Thread.currentThread().getName()+"休眠结束");
                i++;
                System.out.println("i的值>:"+i);
            }
        }
    }
    
}

输出结果:

i的值:11
线程:Thread-0进入休眠状态
线程:Thread-0休眠结束
i的值>:12
i的值:13
线程:Thread-1进入休眠状态
线程:Thread-1休眠结束
i的值>:14

由输出结果我们可以得出:

  • 当Thread0进入休眠状态时,Thread1并没有继续执行,而是等待Thread0休眠结束释放了对象锁,Thread1才继续执行。
    当调用sleep()方法时,必须捕获异常或者向上层抛出异常。当线程休眠时间满时,并不一定会马上执行,因为此时有可能CPU正在执行其他的任务,所以调用了sleep()方法相当于线程进入了阻塞状态。

方法详解- public static void yield()

package top.crosssoverjie.study.Thread;

public class Testyield {
    public static void main(String[] args) {
        MyThread4 my = new MyThread4() ;
        my.start() ;
    }
}
class MyThread4 extends Thread{
    @Override
    public void run() {
        long open = System.currentTimeMillis();
        int count= 0 ;
        for(int i=0 ;i<1000000;i++){
            count= count+(i+1);
//            Thread.yield() ;
        }
        long end = System.currentTimeMillis();
        System.out.println("用时:"+(end-open)+"毫秒");
    }
}

输出结果:
用时:1毫秒
如果将 Thread.yield()注释取消掉,输出结果:
用时:116毫秒

  • 调用yield()方法是为了让当前线程交出CPU权限,让CPU去执行其他线程。它和sleep()方法类似同样是不会释放锁。但是yield()不能控制具体的交出CUP的时间。并且它只能让相同优先级的线程获得CPU执行时间的机会。
  • 调用yield()方法不会让线程进入阻塞状态,而是进入就绪状态,它只需要等待重新获取CPU的时间,这一点和sleep()方法是不一样的。

方法详解- public final void join()

在很多情况下我们需要在子线程中执行大量的耗时任务,但是我们主线程又必须得等待子线程执行完毕之后才能结束,这就需要用到 join()方法了。join()方法的作用是等待线程对象销毁,如果子线程执行了这个方法,那么主线程就要等待子线程执行完毕之后才会销毁,请看下面这个例子:

package top.crosssoverjie.study.Thread;

public class Testjoin {
    public static void main(String[] args) throws InterruptedException {
        new MyThread5("t1").start() ;
        for (int i = 0; i < 10; i++) {
            if(i == 5){
                MyThread5 my =new MyThread5("t2") ;
                my.start() ;
                my.join() ;
            }
            System.out.println("main当前线程:"+Thread.currentThread().getName()+" "+i);
        }
    }
}
class MyThread5 extends Thread{
    
    public MyThread5(String name){
        super(name) ;
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("当前线程:"+Thread.currentThread().getName()+" "+i);
        }
    }
}

输出结果:

main当前线程:main 0
当前线程:t1 0
当前线程:t1 1
main当前线程:main 1
当前线程:t1 2
main当前线程:main 2
当前线程:t1 3
main当前线程:main 3
当前线程:t1 4
main当前线程:main 4
当前线程:t2 0
当前线程:t2 1
当前线程:t2 2
当前线程:t2 3
当前线程:t2 4
main当前线程:main 5
main当前线程:main 6
main当前线程:main 7
main当前线程:main 8
main当前线程:main 9

如果我们把join()方法注释掉之后:

main当前线程:main 0
当前线程:t1 0
main当前线程:main 1
当前线程:t1 1
main当前线程:main 2
当前线程:t1 2
main当前线程:main 3
当前线程:t1 3
main当前线程:main 4
当前线程:t1 4
main当前线程:main 5
main当前线程:main 6
main当前线程:main 7
main当前线程:main 8
main当前线程:main 9
当前线程:t2 0
当前线程:t2 1
当前线程:t2 2
当前线程:t2 3
当前线程:t2 4

由上我们可以得出以下结论:

  • 在使用了join()方法之后主线程会等待子线程结束之后才会结束。

方法详解- setDaemon(boolean on),getDaemon()

用来设置是否为守护线程和判断是否为守护线程。
notes:

  • 守护线程依赖于创建他的线程,而用户线程则不需要。如果在main()方法中创建了一个守护线程,那么当main方法执行完毕之后守护线程也会关闭。而用户线程则不会,在JVM中垃圾收集器的线程就是守护线程。

优雅的终止线程

有三种方法可以终止线程,如下:

  1. 使用退出标识,使线程正常的退出,也就是当run()方法完成后线程终止。
  2. 使用stop()方法强行关闭,这个方法现在已经被废弃,不推荐使用
  3. 使用interrupt()方法终止线程。

具体的实现代码我将在下一篇博文中将到。。

线程的优先级

在操作系统中线程是分优先级的,优先级高的线程CPU将会提供更多的资源,在java中我们可以通过setPriority(int newPriority)方法来更改线程的优先级。
在java中分为1~10这个十个优先级,设置不在这个范围内的优先级将会抛出IllegalArgumentException异常。
java中有三个预设好的优先级:

  • public final static int MIN_PRIORITY = 1;
  • public final static int NORM_PRIORITY = 5;
  • public final static int MAX_PRIORITY = 10;

参考

java多线程思维图


总结

以上就是我总结的java多线程基础知识,后续会补充线程关闭、线程状态、线程同步和有返回结果的多线程。

个人博客地址:http://crossoverjie.top
GitHub地址:[https://github.com/crossoverJie]

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

推荐阅读更多精彩内容

  • 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要...
    嘟爷MD阅读 7,272评论 21 272
  • 线程概述 线程与进程 进程  每个运行中的任务(通常是程序)就是一个进程。当一个程序进入内存运行时,即变成了一个进...
    闽越布衣阅读 960评论 1 7
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,377评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,922评论 1 18
  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 4,650评论 12 45