多线程

Keywords: Thread类、synchronized关键字、同步代码块、同步函数、死锁、多线程间通信、等待/唤醒机制、停止线程、守护线程、线程的内部类体现

多线程

基本概念

进程: 是一个正在进行中的程序(直译),每一个进程执行都有一个执行的顺序,该顺序就是一个执行路径,或者叫一个控制单元
线程: 就是进程中一个负责程序执行的控制单元(执行路径)

一个进程中至少要有一个线程,开启多个线程是为了同时运行多部分代码。每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

多线程好处:解决了多部分同时运行的问题。

多线程的弊端:线程太多会导致效率的降低。

一个栗子:

JVM启动时就启动了多个线程,至少有两个线程可以分析的出来。
1. 执行main函数的线程,该线程的任务代码都定义在main函数中
2. 负责垃圾回收的线程

线程的创建方式

通过对api的查找,java已经提供了对线程这类事物的描述——Thread类。

创建线程方式一:继承Thread类

步骤:

  1. 定义一个类继承Thread类。
  2. 覆盖Thread类中的run方法。目的:将自定义的代码存储在run方法中,让线程运行。
  3. 直接创建Thread的子类对象创建线程。
  4. 调用start方法开启线程并调用线程的任务run方法执行。

一个栗子:(day13\ThreadDemo1.java)

class Demo extends Thread
{
    public void run()
    {
        for(int x=0; x<10; x++)
        {
            //for(int y=-9999999; y<999999999; y++){}
            System.out.println("Demo run");
        }
    }
}
class ThreadDemo1
{
    public static void main(String[] args) 
    {
        Demo d = new Demo();
        d.start();//开启线程并执行该线程的run方法
        //d.run();//仅仅是对象调用方法,而线程创建了,并没有执行
        for(int x=0; x<10; x++)
        {
            System.out.println("main run");
        }   
    }
}

多线程的随机性: 发现每次运行结果都不同,因为多个线程多获取cpu的执行权,cpu执行到谁,谁就运行。但在某一时刻,只能由一个程序在运行(多核除外)。cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行行为看作在抢夺cpu的执行权。

为什么要覆盖run方法?
Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。简言之:Thread类中的run方法用于存储线程要运行的代码

线程的状态图:
没有执行资格:冻结状态
由执行资格没有执行权:临时堵塞状态
既有执行资格又有执行权:运行状态
[图片上传失败...(image-8e484d-1589686906648)]

注:线程都有自己的默认名称Thread-编号,该编号从0开始,用getName()方法获取

static Thread currentThread():获取当前线程对象
getName():获取线程名称

设置线程名称:setName()或者构造函数

创建线程方式二:实现Runnable接口

简单的买票程序:多个窗口同时卖票。(day13\TicketDemo.java)

步骤:

  1. 定义类实现Runnable接口
  2. 覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中
  3. 通过Thread类建立线程对象
  4. Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。为什么:自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该方法所属的对象。
  5. 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法

两种方式(实现方式和继承方式)的比较:

实现方式好处:

  1. 避免了单继承的局限性,在定义线程时建议使用实现方式
  2. 将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将任务的封装成对象。

两种方式的区别:
继承方式:线程代码存放在Thread类的子类run方法中
实现方式:线程代码存放在Runnable接口的子类run方法中

同步

多线程中的安全问题

导致线程出现的原因: 1.多个线程访问出现延迟;2.线程随机性
线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来,导致共享数据的错误

注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的

解决方法: 对多条操作共享数据的语句,只能让一个线程先执行完,在执行过程中,其他线程不可以参与执行。同步

同步代码块

格式:

synchronized(对象)
{
    需要被运行的代码
}

一个栗子:

Object obj = new Object();
synchronized(obj)
{
    需要被运行的代码
}

对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。eg.火车上的卫生间= =。必须保证同步中只有一个线程在运行。

同步的前提:

  1. 必须要有两个或者两个以上的线程
  2. 必须是多个线程使用同一个锁

好处: 解决多线程的安全问题
弊端: 多个线程都需要判断锁,较为消耗资源

应用: day13\BankDemo.java

需求:储户,两个,每个都到银行存钱每次存100,各存三次
目的:该程序是否有安全问题,如果有,怎么解决?

如何找问题:
1. 明确哪些代码是多线程运行代码
2. 明确共享数据
3. 明确多线程运行代码中哪些语句是操作共享数据的

同步函数

两种表现形式:同步代码块(eg.上面的栗子);同步函数(eg.public synchronized void 函数名(参数)

同步函数的锁: 函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this。如果同步函数被static修饰后,使用的锁不是this,静态函数进内存后,内存中没有本类对象,但一定有该类对应的字节码文件对象类名.class,该对象的类型是class,故静态同步方法使用的锁是该方法所在类的字节码文件对象类名.class。(day14\StaticLockDemo.java)

单例设计模式

饿汉式:

class Single
{
    private Single(){}
    private static Single s = new Single();
    public static Single getInstance()
    {
        return s;
    }
}

懒汉式:

class Single
{
    private static Single s = null;
    private Single(){}
    public static /*synchronized*/ Single getInstance()
    {
        if(s==null)
        {
            synchronized(Single.class)
            {
                if(S == null)
                    s = new Single();
                return s;
            }
        }
    }
}

懒汉式和饿汉式的区别: 懒汉式的特点是实例的延迟加载,多线程访问时会出现安全问题,用同步函数和同步代码块都可以解决安全问题,但效率相对低下,使用双重判断可提高效率。加同步时使用的锁是该类所属的字节码对象。

死锁

(day14\DeadLockTest.java)

多线程间通信

多个线程在操作同一个资源,但是操作的动作不同。(day14\ResourceDemo.java)

等待/唤醒机制

涉及的方法:(day14\ResourceDemo2.java,简化后day14\ResourceDemo3.java)

  1. wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中
  2. notify():唤醒线程池中一个线程(任意)
  3. notifyAll():唤醒线程池中的所有线程

都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁的概念。

为什么这些操作线程的方法要定义在Object类中?
因为这些方法在操作同步中的线程时,都必须要标识他们所操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。也就是硕,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。

一个栗子:

生产者和消费者
day14\ProducerConsumerDemo(0-3).java

JDK5.0升级版

jdk1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。

Lock接口:替代了同步代码块或者同步函数。将同步的隐式锁操作变成现实锁操作,同时更为灵活。可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,通常需要定义finally代码块中。

Condition接口:替代了Object中的wait notify notifyAll方法,将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
await():相当于wait()
signal():相当于notify()
signalAll():相当于notifyAll()

停止线程

  1. stop方法(已经过时)

  2. run方法结束

如何控制线程的任务结束?
任务中都会有循环结构,只要控制住循环就可以结束任务,控制循环通常就用定义标记来完成。

但是如果线程处于冻结状态,无法读取标记。如何结束?
当没有指定的方式让冻结的线程恢复到运行状态时,需要对冻结进行清除,可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格,当时强制动作发生时会产生异常InterruptedException,记得要处理。

线程类的其他方法

守护线程: 在程序运行的时候在后台提供一种通用服务的线程,守护线程随主线程一起销毁。将线程设置为守护线程:setDaemon(boolean b)(day14\StopThreadDemo.java)
非守护线程: 也叫用户线程,由用户创建,非守护线程和主线程互不影响

join():当A线程执行到了B线程的.join()方法时,A就会等待,等B线程都执行完,A才会执行。join()可以用来临时加入线程执行。

形象比喻:排队打饭,主线程在队列中遇到了A线程的join,就把自己的位置让给A,自己站在对外等A线程打完饭,主线程再回到队列中和其他线程抢打饭的机会。

setPriority(int num):设置优先级

toString():返回此线程的字符串表示,包括线程的名称,优先级和线程组

开发中线程的内部类体现:

三个线程同时执行:(day14\ThreadTest.java)

class ThreadTest 
{
    new Thread()
    {
        public void run()
        {
            for(int x=0; x<50; x++)
            {
                System.out.println(Thread.currentThread().getName()+"....x="+x);
            }
        }
    }.start();

    for(int x=0; x<50; x++)
    {
        System.out.println(Thread.currentThread().getName()+"....y="+x);
    }

    Runnable r = new Runnable()
    {
        public void run()
        {
            for(int x=0; x<50; x++)
            {
                System.out.println(Thread.currentThread().getName()+"....z="+x);
            }
        }
    };
    new Thread(r).start();
    }
}

一个面试题:

class ThreadTest 
{
    //面试题:
    public static void main(String[] args) 
    {
        new Thread(new Runnable()
        {
            public void run()
            {
                System.out.println("runnable run");
            }
        })
        {
            public void run()
            {
                System.out.println("subThread run");
            }
        }.start();
    }
}

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