Java线程

Java中的线程(多线程),本篇主要讲一下线程的概念和基本操作以及各个方法的用法等;首先在了解线程前我们必须应该知道的几个概念:

进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位;

线程:是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,是进程的一个实体;

两者区别:
1、进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元。
2、一个线程只能属于一个进程,一个进程可以有多个线程,但至少有一个线程。
3、资源分配给进程,同一进程的所有线程共享进程中的所有资源。

线程创建

在java中有两种方式创建线程,一种是继承Thread类,一种是实现Runnable接口;

1、继承Thread类

继承Thread类时比较常用的一种方式(推荐使用Runnable接口,后面会说为什么),接下来看一下代码实现:

public class MyThread extends Thread {
    private String threadName;

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

    @Override
    public void run() {
        //doSomething
    }
}
 MyThread myThread = new MyThread("A");
 myThread.start();

这样就通过继承Thread类来创建了一个线程;

2、实现Runnable接口

public class MyRunnable implements Runnable {
    private String threadName;

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

    @Override
    public void run() {
        //doSomething
    }
}
 Thread myThread = new Thread(new MyRunnable("A"));
 myThread.start();

可以看到在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

注意
1、start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
2、start不能重复调用,否则会出现java.lang.IllegalThreadStateException异常。

Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable比继承Thread的好处:
1、适合多个相同的程序代码的线程去处理同一个资源
2、可以避免java中的单继承的限制
3、增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4、线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

注意:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实习在就是在操作系统中启动了一个进程。

线程状态

首先来看一张线程状态转换图,这个图非常重要!你如果看懂了这个图,那么对于多线程的理解将会更加深刻!


线程转换图

1、新建状态(New):新建了一个线程对象;
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法;该线程进入就绪状态,进入可运行的线程池中,等待获取CPU的使用权;
3、运行状态(Running):就绪状态的线程获取了CPU的使用权,开始执行代码;
4、阻塞状态(Blocked):阻塞状态就是因为某种原因放弃了CPU的使用权,暂时停止运行;阻塞状态分为三种:
(A)等待阻塞:运行线程执行wait()方法,JVM会把该线程放入等待池中。
(B)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(C)其他:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。 当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程调度

1、调度的优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。

Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
注意:
1、每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
2、线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
3、推荐使用Thread类下三个静态常量作为优先级。

2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。

4、线程让步:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。

5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。

常用方法解析

1、sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),该方法不会释放同步锁;

2、join():等待线程结束;
join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
join的使用场景:在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

public class MyRunnable implements Runnable {
    private String threadName;

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

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(threadName + "_线程执行_" + i);
        }
        System.out.println(threadName + "_线程执行_结束");
    }
}

main代码

        Thread myThread = new Thread(new MyRunnable("A"));
        myThread.start();
        try {
            myThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("主线程_线程执行_结束");

执行结果:


执行结果

可以看到,在主线程中启动子线程A,当调用A线程的join方法后,主线程等待A线程结束后才继续执行;

3、yield():暂停当前正在执行的线程对象,并执行其他线程。
yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
注意:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
4、setPriority():设置线程的优先级;

 Thread myThread = new Thread(new MyRunnable("A"));
 myThread.setPriority(Thread.MAX_PRIORITY);
 myThread.start();

5、interrupt():给线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出异常,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!
6、wait():Obj.wait()与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。

异同点总结

sleep()和yield()的区别
1、sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;
2、yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
3、sleep 可以设定线程睡眠的时间yield则不可以;
4、sleep 允许较低优先级的线程获得运行机会,而yield方法执行时,线程仍旧处于可运行状态,不会让较低优先级的线程获得CPU使用权;

wait和sleep区别
共同点
1、他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
2、wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
补充: 如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。 需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。
不同点
1、sleep是Thread类的方法,wait是Object类的方法;
2、在使用同步锁的时候,sleep不会释放同步锁,而wait会;
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 ;
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常;

线程同步

线程同步是多线程开发中非常重要的一环,当多个线程访问同一个方法时,若该方法没有同步,会导致该方法混乱,使我们的程序运算出错,达不到我们想要的效果,因此线程同步尤为关键;
java中用synchronized关键字实现同步
synchronized作用域
1、在某个类对象实例内,synchronized aMethod(){},修饰的是这个类中的普通方法,它的作用域是这个方法;(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)
2、在某个类对象实例内,synchronized static aStaticMethod{} 如果修饰的是这个类中的静态方法的话,它的作用域是整个类;
3、synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
synchronized(this){/区块/},它的作用域是当前对象(this);

注意
1、线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有且仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。

这里只是简单的介绍了一下synchronized关键字的作用域和基本用法,实际上synchronized还有很多细节和值得注意的地方,有兴趣的小伙伴可以自行查阅一下相关的知识;
java中多线程是一个面很广的知识点,仅仅一篇文章是完全不够讲清楚的,本篇也是对自己学习java基础知识的一个总结,都是一些最基础的线程知识,如果小伙伴们还想对线程做更深入的了解,可以自行挖掘。

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

推荐阅读更多精彩内容

  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,946评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,433评论 1 15
  • 【JAVA 线程】 线程 进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者...
    Rtia阅读 2,748评论 2 20
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,328评论 3 87
  • 今年最大收获莫过于一个月亮。 从今以后,2016年将定格在刘若余的出生年。 今年这一年,一直都在为迎接一个小生命准...
    余小头阅读 333评论 0 0