Java线程

面试--线程

知乎--java中的多线程究竟在什么情况下使用?

知乎--多线程有什么用??

菜鸟教程--多线程编程

Java线程的概念

Java内置支持多线程编程(multithreaded programming),多线程程序包含2条或者2条以上并发运行的部分,程序中每个这样的部分叫做线程(thread),
每个线程都有独立的运行路径。因此多线程任务是多任务处理的一种特殊形式。

进程和线程的比较

  • 进程:程序执行时所占用的所有的资源的总称或者说是容器
  • 线程:是基本执行单元


    image

简单总结: 进程是线程的容器。

进程中的主线程

主线程的重要性体现在:

  • 他是产生其他子线程的线程
  • 通常它必须是最后完成执行,因为它将执行各种关闭操作

主线程是运行程序时候自动创建的,但是其也是一个Thread类的实例对象

一般由主线程创建其他子线程,然后由主线程惯例其他线程。

关于Thread类的基本使用

Java的多线程系统建立于Thread类和Runable接口. Thread类定义了好几种方法来帮助管理线程:

方法 意义
getName 获取线程名称
getPriority 获取线程优先级
isAlive 判定线程是否仍在执行
join 等待一个线程终止
run 线程的入口
sleep 在一定时间内挂起线程
start 通过调用运行方法来启动线程

继承Thread类创建新的线程

  • 我们可以通过继承Thread类,Override run()方法,然后创建该类的实例来创建除了主线程之外的其他线程,并且在run()方法中赋予属于这个线程的代码逻辑,这个run()方法是新线程的入口。
  • 这个新建的Thread的子类,会继承Thread类的一个start()方法,调用start()方法时,会自动生成一个线程,然后调用run()方法执行该线程的逻辑。
package com.DeeJay.ThreadDemo;

public class ThreadDemo extends Thread{ // 自定义线程类
//    运行的代码逻辑和主线程不同
    public ThreadDemo() {
//        创建一个新的子线程
        super("Child Thread");
//        显式的开始执行这个线程 ===> 做一些基本的初始化操作 ===> run()
        this.start();
    }

//    新的子线程的入口!
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i ++) {
                System.out.println("Child Thread: " + i);
                Thread.sleep(1000);
            }
        }catch (Exception e) {
            System.out.println("Child Thread Error!");
        }
    }
}

package com.DeeJay;

import com.DeeJay.ThreadDemo.ThreadDemo;

public class Main {
    public static void main(String[] args) {
//        创建新的子线程!
        ThreadDemo t = new ThreadDemo();
        // 需要注意的是此处代码执行并不会像单线程一样阻塞,而是会立即执行下面语句
//        调用完构造函数和start()之后, 子线程开始执行,主线程返回到Main(),开始自己的逻辑
//        主线程逻辑
        try {
            for (int i = 0; i < 5; i ++) {
                System.out.println("Main Thread: " + i);
                Thread.sleep(1000);
            }
        }catch (Exception e) {
            System.out.println("Main Thread Error!");
        }
    }
}

此处在定义新的线程类的时候,也可以不在构造函数内部直接执行start(),可以等新的线程类的实例对象创建后手动执行start()开始子线程逻辑。

实现Runable接口来创建新的线程

package com.DeeJay.ThreadDemo;

public class ThreadDemo implements Runnable{ // 改为实现Runnable接口
    private Thread t;

    public ThreadDemo(String name) {
//        选用带target的构造函数, 将this传过去,本质上就是把run()方法传递过去
        t = new Thread(this, name);
        t.start();
    }

    //    一样的  实现run() 作为线程的入口
    @Override
    public void run() {
        for (int i = 0; i < 5; i ++) {
            System.out.println("Runnable Thread: " + i);
        }
        System.out.println(t.getName() + " existing");
    }
}

package com.DeeJay;

import com.DeeJay.ThreadDemo.ThreadDemo;

public class Main {
    public static void main(String[] args) {
        new ThreadDemo("Child Thread");
    }
}

线程同步

当多个线程需要共享资源,他们就需要某种方法来确定资源在某一刻仅被一个线程占用着。

达到上述目的的过程就叫做同步(synchronization)

synchronized方法的使用

当一个线程在一个synchronized方法的内部,所有试图调用该方法(或者其他synchronized方法)的同一个实例对象的线程必须等待。为了退出管程,并释放对对象的控制权给其他等待的线程,拥有管程的线程仅需从synchronized方法返return即可。

一般如果一个对象的方法可能被多线程使用,则用synchronized关键字修饰

来看一个具体的使用synchronized方法的例子:
Callme.java

package com.DeeJay;

public class Callme {
    public void call(String msg) {
        System.out.print("[" + msg);
        try {
            Thread.sleep(1000);
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.print("]");
    }
}

Caller.java

package com.DeeJay;

public class Caller implements Runnable{
    private String msg;
    Thread t;
    Callme target;

    public Caller(String msg, Callme target) {
        this.msg = msg;
        this.t = new Thread(this);
        this.target = target;
        t.start();
    }

    @Override
    public void run() {
        target.call(this.msg);
    }
}

Main

package com.DeeJay;

public class Main {
    public static void main(String[] args) {
        Callme target = new Callme(); // 共享的资源

        Caller c1 = new Caller("hello", target);
        Caller c2 = new Caller("world", target);
        Caller c3 = new Caller("synchronized", target);

        try {
            c1.t.join();
            c2.t.join();
            c3.t.join();
        }catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("\n");
        System.out.println("Main Thread End!");
    }
}

上述例子中,主线程中new出来的target即为几个子线程公用的资源。当调用三个子线程时,三个子线程会并发执行,并不会等到一个执行完再执行下一个,所以看到的输出结果为:

[hello[world[synchronized]]]

我们想达到的效果是在一个子线程占用公用资源target时,不允许其他线程也使用该资源,为了达到这个效果,就可以给将target的这个call方法用synchronized修饰:
Callme.java

package com.DeeJay;

public class Callme {
    public synchronized void call(String msg) {
        System.out.print("[" + msg);
        try {
            Thread.sleep(1000);
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.print("]");
    }
}

之后再运行,可以看到输出的为:

[hello][synchronized][world]

synchronized修饰语句块

对于上述的例子,还可以使用synchronized修饰代码块来达到相同的效果,synchronized修饰的代码块就是同步执行的,我们可以不用在Callme类中修改call方法为synchronized,而是在调用Callme实例对象的Caller类中,将代码块置为synchronized,这样代码块中的逻辑也达到了同步的效果。
Caller.java

package com.DeeJay;

public class Caller implements Runnable{
    private String msg;
    Thread t;
    Callme target;

    public Caller(String msg, Callme target) {
        this.msg = msg;
        this.t = new Thread(this);
        this.target = target;
        t.start();
    }

    @Override
    public void run() {
        synchronized (target) { // synchronized block
            target.call(this.msg);
        }
    }
}

关于join

关于join()方法,join方法的作用是,当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。

关键是理解挂起调用线程,首先举个例子:
子线程:

package com.DeeJay.ThreadDemo;

public class ThreadDemo extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Child Thread End!");
    }
}

Main:

package com.DeeJay;

import com.DeeJay.ThreadDemo.ThreadDemo;

public class Main {
    public static void main(String[] args) {
        ThreadDemo t = new ThreadDemo();
        t.start();
        try {
            t.join(); // 
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Main Thread End!");
    }
}

子线程的逻辑是等待1秒后输出信息,当调用join()后,子线程的调用线程此时是主线程,所以主线程会暂时挂起不继续执行,等待子线程执行完成后,才会继续执行主线程。这时候才输出主线程中的信息。所以此时输出信息应该为:

// 一秒后输出:
Child Thread End!
Main Thread End!

但是如果不调用子线程的join(),此时主线程并不会挂起,会和子线程并发执行,主线程并没有等待,所以会先于子线程执行:
子线程:

package com.DeeJay;

import com.DeeJay.ThreadDemo.ThreadDemo;

public class Main {
    public static void main(String[] args) {
        ThreadDemo t = new ThreadDemo();
        t.start();
        try {
//            t.join(); // 不调用join 在子线程执行的过程中 主线程并不会被挂起
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Main Thread End!");
    }
}

此时输出为:

Main Thread End!  // 马上输出
Child Thread End! // 过一秒后输出

当有多个子线程时,如果前面的子线程先于后面的子线程调用start()之前调用join()的话,此时调用线程还是主线程,主线程依然会挂起,所以后面子线程的start()的逻辑要等到前面子线程执行完后主线程继续开始执行才能执行到。所以会有单线程的表现形式,但是此时其实仍是多线程。

package com.DeeJay;

import com.DeeJay.ThreadDemo.ThreadDemo;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo t1 = new ThreadDemo();
        ThreadDemo t2 = new ThreadDemo();
        t1.start();
        t1.join(); //  此处调用t1的join() 会挂起主线程  t1执行结束后才唤醒主线程继续执行t2.start()
        t2.start();
        t2.join(); // 同理 t2开始执行时挂起主线程 t2执行完后唤醒主线程
        System.out.println("Main Thread End!");
    }
}

输出结果为:

Child Thread End! // 过1秒
Child Thread End! // 过2秒
Main Thread End! // 过2秒 

线程之间的通信

以下方法用来和其他线程进行交流, 并且只能在synchronize修饰的代码块或者方法里调用:

wait( ) 告知被调用的线程放弃管程进入睡眠直到其他线程进入相同管程并且调用notify( )。
notify( ) 恢复相同对象中第一个调用 wait( ) 的线程。
notifyAll( ) 恢复相同对象中所有调用 wait( ) 的线程。具有最高优先级的线程最先运行。

package com.DeeJay.ThreadDemo;

public class Q {
    private int n; // 生产的对象
    private boolean isDataReady = false;

    public int get() throws InterruptedException {
        if(!isDataReady){
            wait(); // 暂停, 等待着put()来设置好这个值只后再读取
        }
        System.out.println("Get " + n);
        isDataReady = false;  // 标记已经取走了值
        notify(); // 通知put()可以设置另外一个值了
        return this.n;
    }

    public void put(int n) throws InterruptedException {
        // 如果这个值已经设置好了
        if (isDataReady) {
            wait(); // 等待get()把值取走
        }
        this.n = n; //设置一个新的值
        isDataReady = true; // 标记已经设置好了
        System.out.println("Put: " + n);
        notify(); // 通知线程get() 这个最新的值
    }
}

关于死锁

死锁发生在当两个线程对一对同步对象有循环依赖关系时

比如说有ABC三个子线程,A依赖于B,B依赖于C,C依赖于A,就有可能出现死锁。

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

推荐阅读更多精彩内容

  • 前言:虽然自己平时都在用多线程,也能完成基本的工作需求,但总觉得,还是对线程没有一个系统的概念,所以,查阅了一些资...
    justCode_阅读 699评论 0 9
  • 【JAVA 线程】 线程 进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者...
    Rtia阅读 2,754评论 2 20
  • Java中的线程(多线程),本篇主要讲一下线程的概念和基本操作以及各个方法的用法等;首先在了解线程前我们必须应该知...
    星星_点灯阅读 359评论 0 0
  • 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让...
    尧淳阅读 1,587评论 0 25
  • 偶然地看到小白营的推文,文字有趣内容入心,便花9块钱推门进来了,没作大的期待,却没成想由此推开了一个精彩的...
    lucy李慧霞阅读 339评论 0 12