Java-多线程详解(一)

1.进程与线程

(1).什么是进程?

进程:正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。

(2).什么是线程?

线程:是一个执行路径,一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。

2.线程的生命周期及五种基本状态

08181.jpg

Java线程具有五种基本状态:

(1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

(2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

(3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

(4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

①等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

②同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

③其他阻塞 : 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

(5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

图中的方法解析如下:
Thread.sleep():在指定时间内让当前正在执行的线程暂停执行,但不会释放"锁标志"。不推荐使用。
Thread.sleep(long):使当前线程进入阻塞状态,在指定时间内不会执行。
Object.wait()和Object.wait(long):在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的"锁标志",从而使别的线程有机会抢占该锁。 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常,waite()和notify()必须在synchronized函数或synchronized中进行调用。如果在non-synchronized函数或non-synchronized中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

Object.notifyAll():则从对象等待池中唤醒所有等待等待线程
Object.notify():则从对象等待池中唤醒其中一个线程。
Thread.yield()方法 暂停当前正在执行的线程对象,yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,yield()只能使同优先级或更高优先级的线程有执行的机会。
Thread.Join():把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

3.Java多线程的实现

在Java中,如果要实现多线程的程序,那么必须依靠一个线程的主体类(好比主类的概念一样,表示一个线程的主类),但是这个线程的主体类在定义的时候需要有一些特殊的要求,这个类可以继承Thread类或实现Runnable接口来完成定义

(1)继承Thread类实现多线程

java.lang.Thread是一个负责线程操作的类,任何的类继承了Thread类就可以成为一个线程的主类。既然是主类,必须有它的使用方法,而线程启动的方法需要覆写Thread类中的run()方法才可以。

定义一个线程的主体类:

class TestThread extends Thread{
    String name;

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

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++){
            System.out.println(this.name+i);
        }
    }
}

现在已经有了线程类,并且里面也存在了相应的操作方法,那么就应该产生对象并调用里面的方法,于是编写出了以下的程序:

public class MyClass {
    public static void main(String[] args){
        TestThread t1 = new TestThread("A");
        TestThread t2 = new TestThread("B");
        TestThread t3 = new TestThread("C");

        t1.run();
        t2.run();
        t3.run();

    }
}

运行结果:

A1
A2
A3
A4
A5
B1
B2
B3
B4
B5
C1
C2
C3
C4
C5

我们发现:以上的操作并没有真正的启动多线程,因为多个线程彼此之间的执行一定是交替的方式运行,而此时是顺序执行,每一个对象的代码块执行完之后才向下继续执行。

如果想要在程序之中真正的启动多线程,必须依靠Thread类的一个方法:public void start(),表示真正启动多线程,调用此方法后会间接调用run()方法:

public class MyClass {
    public static void main(String[] args){
        TestThread t1 = new TestThread("A");
        TestThread t2 = new TestThread("B");
        TestThread t3 = new TestThread("C");

        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

A1
B1
C1
B2
A2
B3
C2
B4
B5
A3
A4
A5
C3
C4
C5

此时可以发现:多个线程之间彼此交替执行,但每次的执行结果是不一样。通过以上的代码就可以得出结论:要想启动线程必须依靠Thread类的start()方法执行,线程启动之后会默认调用了run()方法。

在调用start()方法之后,发生了一系列复杂的事情:
(1)启动新的执行线程(具有新的调用栈);
(2)该线程从新状态转移到可运行状态;
(3)当该线程获得机会执行时, 其目标run()方法将运行。
注意:对java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法时合法的,但并不启动新的线程。

(2)Runnable接口实现多线程

使用Thread类的确时可以方便的进行多线程的实现,但是这种方式最大的缺点就是单继承的问题。为此,在java之中也可以利用Runnable接口来实现多线程。这个接口的定义如下:

public interface Runnable{
    public void run();
}

通过Runnbale接口实现多线程:

class TestThread implements Runnable{
    String name;

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

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(name+i);
        }
    }
}

这和之前继承Thread类的方式区别不大,但是有一个优点就是避免了单继承局限。

不过问题来了。之前说过,如果要启动多线程,需要依靠Thread类的start()方法完成,之前继承Thread类的时候可以将此方法直接继承过来使用,但现在实现的是Runnable接口,没有这个方法可以继承了,怎么办?

需要解决这个问题,还是需要依靠Thread类完成。在Thread类中定义了一个构造方法,接收Runnable接口对象:

public Thread (Runnable target);

利用Thread类启动多线程:

public class MyClass {
    public static void main(String[] args){
       TestThread t1 = new TestThread("A");
       TestThread t2 = new TestThread("B");
       TestThread t3 = new TestThread("C");

       new Thread(t1).start();
       new Thread(t2).start();
       new Thread(t3).start();
    }
}

运行结果:

B1
B2
A1
C1
A2
B3
A3
C2
A4
B4
A5
C3
B5
C4
C5

此时,不但实现了多线程的启动,而且没有了单继承局限。

4.Thread类和Runnable接口实现多线程两种方式的区别

Thread类和Runnable接口都可以为同一功能的方式来实现多线程,那么从java的实际开发而言,肯定优先考虑使用Runnable接口,因为可以有效的避免单继承的局限,除了这个,这两种方式是否还有其他联系呢?

我们先来看Thread类的定义结构:

public class Thread extends Object implements Runnable

发现Thread类也是Runnable接口的子类,这样的话,Runnable接口实现多线程序的结构就有了以下形式:


08191.png

除了以上的联系之外,对于Runnable和Thread类还有一个区别:使用Runnable接口可以更加方便的表示出数据共享的概念。(这里的数据共享是指多个线程访问同一个资源的操作)

5.多线程的常用操作方法

(1)currentThread()方法:返回代码段正在被哪个线程调用的信息。

public static Thread currentThread()

实例如下:

public class MyClass {
    public static void main(String[] args){
       System.out.println(Thread.currentThread());

       TestThread t1 = new TestThread();
       new Thread(t1).start();
       new Thread(t1).start();
       new Thread(t1).start();
    }
}

class TestThread implements Runnable{

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

运行结果:

Thread[main,5,main]
Thread[Thread-0,5,main]
Thread[Thread-1,5,main]
Thread[Thread-2,5,main]

(2)setName()/getName()和getId()方法:

setName()/getName():设置/取得线程的名称:

public final void setName(Sring name)
public final void getName(String name)

getId():取得线程的唯一标识

public final void setName(String name)

实例如下:

public class MyClass {
    public static void main(String[] args){
       TestThread testThread = new TestThread();

       Thread thread = new Thread(testThread);
       thread.setName("线程");
       System.out.println(thread.getName()+" "+thread.getId());
    }
}

class TestThread implements Runnable{
    @Override
    public void run() {
        System.out.println();
    }
}

运行结果:

线程 12

————————————————
借鉴:https://blog.csdn.net/wei_zhi/article/details/52787749

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

推荐阅读更多精彩内容

  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,952评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,444评论 1 15
  •   一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺...
    OmaiMoon阅读 1,664评论 0 12
  • 林炳文Evankaka原创作品。转载自http://blog.csdn.net/evankaka 本文主要讲了ja...
    ccq_inori阅读 648评论 0 4
  • 不必强求喜欢,只要心安便可。 不一定要喜欢,不一定要悲伤。 随风而安,一切都会结束的。 生活就是这样的,有痛苦,有...
    庭微阅读 185评论 0 1