JAVASE进阶篇-----线程

进程:计算机中特定功能的程序在数据集上的一次运行
线程:线程是进程的一个单元
多线程:一个进程中允许多个线程同时运行

线程的创建方式

1.继承Thread类--->子类要重写run方法--->通过start方法来启动线程

package com.hut;

/**
 * @author 茶茶
 * @date 2020-04-05 21:21 新建
 */
public class MyThread extends Thread{

    private String name;

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

    /**
     * 这是线程执行的逻辑体
     */
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(name + "下载了" + i + "%");
        }
    }

}
package com.hut;

/**
 * @author 茶茶
 * @date 2020-04-05 21:25 新建
 */
public class ThreadTest {

    /**
     * 主方法本身就是一个线程
     * @param args
     */
    public static void main(String[] args) {
        //创建一个线程对象
        MyThread myThread = new MyThread("肖申克的救赎");
        //启动该线程用start方法,然后会自己调用run方法
        myThread.start();

        MyThread myThread1 = new MyThread("当幸福来敲门");
        myThread1.start();
    }

}

2.实现Runnable接口--->重写run方法--->通过start()方法启动线程

package com.hut;

/**
 * @author 茶茶
 * @date 2020-04-05 21:39 新建
 */
public class MyThread1 implements Runnable{

    private String name;

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

    @Override
    public void run() {
        for (int i = 0; i <=100 ; i++) {
            System.out.println(name + "下载了" + i + "%");
        }
    }

}
package com.hut;

/**
 * @author 茶茶
 * @date 2020-04-05 21:43 新建
 */
public class ThreadTest1 {

    public static void main(String[] args) {
        //创建线程对象
        Thread thread = new Thread(new MyThread1("肖申克的救赎"));
        Thread thread1 = new Thread(new MyThread1("当幸福来敲门"));
        //启动线程
        thread.start();
        thread1.start();
    }

}

两者的区别在于:
如果MyThread继承Thread类:创建对象:MyThread thread = new MyThread();
如果MyThread实现Runnable接口:创建对象:Thread thread = new Thread(new MyThread());

注意:实现Runnable接口这种方式,因为Runnable接口里就只有一个run的抽象方法,其他方法都没有,所以实现了Runnable接口,不能用实现类直接创建实例来调用start方法,因为实现类根本就没有start方法,要借用Thread类来调用start方法,Thread类重载了构造器,在实例Thread对象的时候,就可以把实现了Runnable类的实例作为参数传递进去,调用Thread对象的start方法,就可以执行run方法里的逻辑体了。

线程的执行原理

线程的并发执行通过多个线程不断的切换CPU的资源,这个速度非常快,我们感知不到,我们能够感知到的就是多个线程在并发的进行。


CPU在三个线程之间不停的切换

这里解释两个概念:
线程同步:线程排队,先来的先执行,安全
线程异步:线程同时进行,不安全

线程的生命周期

1.新建new
2.准备就绪start
3.运行run
4.阻塞sleep,wait,唤醒阻塞的状态称为准备就绪状态:休眠时间到,notify
5.销毁:线程的对象执行完毕变成垃圾,被回收


线程的生命周期

静态属性

static

假如四个窗口同时卖票,票共有100张,票是共享资源,卖完停止
继承Thread类

package com.hut;

/**
 * 这个类是卖票的窗口,四个实例就是四个窗口
 * @author 茶茶
 * @date 2020-04-05 22:25 新建
 */
public class SaleTicketsThread extends Thread{

    //用户共享的票:100张
    static int tickets = 100;

    //创建一个锁对象,是所有线程共享的,synchronized是把锁,要锁住对象,这里锁的是线程对象,当该对象在执行的时候,其他的对象就不能使用该共享资源
    static Object obj = new Object();

    //窗口名
    private String name;

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

    @Override
    public void run() {
        //买票的动作是持续的,有票就卖
        while (true){
            synchronized (obj){
                if (tickets > 0){
                    System.out.println(name + "卖出的座位是:" + tickets-- + "号");
                }else {
                    break;
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(name + "卖票结束");
    }

}
package com.hut;

/**
 * @author 茶茶
 * @date 2020-04-05 22:34 新建
 */
public class SalesTest {

    public static void main(String[] args) {

        SaleTicketsThread s1 = new SaleTicketsThread("窗口1");
        SaleTicketsThread s2 = new SaleTicketsThread("窗口2");
        SaleTicketsThread s3 = new SaleTicketsThread("窗口3");
        SaleTicketsThread s4 = new SaleTicketsThread("窗口4");

        s1.start();
        s2.start();
        s3.start();
        s4.start();
    }

}

结果


image.png

图解:


image.png

由于Java只支持类的单继承,接口的多实现,所以以实现接口优先原则
实现Runnable接口

package com.hut;

/**
 * 模拟实现Runnable接口创建多线程类,用来卖票
 * @author 茶茶
 * @date 2020-04-06 10:45 新建
 */
public class SaleTicketsThread1 implements Runnable{

    static int tickets = 100;//定义共享资源100张票

    static Object obj = new Object();//定义一个锁对象,是共享的,锁住共享资源,其他线程就会被阻塞

    private String name;

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

    @Override
    public void run() {
        //有票就卖
        while (true){
            synchronized (obj){
                if (tickets > 0){
                    System.out.println(name + "卖出座位:" + tickets-- + "号");
                }else {
                    break;
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //跳出循环体之后,就相当于票卖完了
        System.out.println(name + "票卖完了");
    }

}
package com.hut;

/**
 * @author 茶茶
 * @date 2020-04-06 10:50 新建
 */
public class SalesTest1 {

    public static void main(String[] args) {

        Thread thread1 = new Thread(new SaleTicketsThread1("窗口1"));
        Thread thread2 = new Thread(new SaleTicketsThread1("窗口2"));
        Thread thread3 = new Thread(new SaleTicketsThread1("窗口3"));
        Thread thread4 = new Thread(new SaleTicketsThread1("窗口4"));

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }

}

结果


image.png

图解:


image.png

实现Runnable接口的第二种方式

package com.hut;

/**
 * @author 茶茶
 * @date 2020-04-06 13:20 新建
 */
public class SaleTicketsThread implements Runnable{

    int tickets = 100;//100张票

    Object obj = new Object();//锁对象

    @Override
    public void run() {
        //有票就卖
        while (true){
            synchronized (obj){
                if (tickets > 0){
                    System.out.println(Thread.currentThread().getName() + "卖出的座位:" + tickets-- + "号");
                }else {
                    break;
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "卖票结束");
    }

}
package com.hut;

/**
 * @author 茶茶
 * @date 2020-04-06 13:25 新建
 */
public class SaleTest {

    public static void main(String[] args) {

        SaleTicketsThread st = new SaleTicketsThread();//创建一个共享对象,共享给四个线程

        Thread t1 = new Thread(st,"窗口1");//后面为为t1命名
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");
        Thread t4 = new Thread(st,"窗口4");

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

    }

}

图解:


image.png

在执行这一段代码块的时候,锁着该对象的obj将对象,如果CPU切换线程,调用run方法时,就会去判断obj是否可用,如果obj被锁住了,其他线程就被阻塞,无法执行,当CPU再次切换回来时,执行完这一段代码块,释放锁着的obj对象,这个时候CPU再切换线程,切换到哪个线程,哪个线程就被执行run方法,并再次将obj锁住,知道该线程执行完这段代码块后,接着是释放obj对象,就是这样一个循环反复的过程,加锁能保证数据安全,但是执行效率变低。

synchronized (obj){
    if (tickets > 0){
        System.out.println(Thread.currentThread().getName() + "卖出的座位:" + tickets-- + "号");
    }else {
          break;
    }
}

除了锁住obj对象,锁住代码块之外,还可以将代码块当都放在一个方法里。
改写SaleTicketsThread

package com.hut;

/**
 * @author 茶茶
 * @date 2020-04-06 13:20 新建
 */
public class SaleTicketsThread implements Runnable{

    int tickets = 100;//100张票

    Object obj = new Object();//锁对象

    @Override
    public void run() {
        //有票就卖
        while (true){
            if (finishSale()){//返回isFinish,如果为真,代表卖票结束,跳出循环体
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "卖票结束");
    }

    /**
     * 卖票逻辑体
     * @return
     */
    public boolean finishSale(){
        synchronized (obj){
            boolean isFinish = false;//为true代表卖完了
            if (tickets > 0){
                System.out.println(Thread.currentThread().getName() + "卖出的座位:" + tickets-- + "号");
            }else {
                //tickets == 0
                isFinish = true;
            }
            return isFinish;
        }
    }

}

图解:


image.png

再次改写SaleTicketThread方法
如果方法里全是要锁的代码,就可以改写,将synchronized加到方法上,这个时候就不需要obj对象了,那这又是锁住了什么对象呢?

package com.hut;

/**
 * @author 茶茶
 * @date 2020-04-06 13:20 新建
 */
public class SaleTicketsThread implements Runnable{

    int tickets = 100;//100张票

    @Override
    public void run() {
        //有票就卖
        while (true){
            if (finishSale()){//返回isFinish,如果为真,代表卖票结束,跳出循环体
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "卖票结束");
    }

    /**
     * 卖票逻辑体
     * @return
     */
    public synchronized boolean finishSale(){
        boolean isFinish = false;//为true代表卖完了
        if (tickets > 0){
            System.out.println(Thread.currentThread().getName() + "卖出的座位:" + tickets-- + "号");
        }else {
            //tickets == 0
            isFinish = true;
        }
        return isFinish;
    }

}

图解:


image.png

切记:第一synchronized锁住的是对象,第二该对象必须是一个共享的对象,要么创建一个锁对象(static Object obj = new Object()),存放在数据共享区中,要么就用本身对象,但是多线程操作的线程对象操作的对象就必须是本身对象,就像st对象一样,

比如下面的分析,如果下面用synchronized(this),就错了,锁this的话,就锁的new SaleTicketThread()对象了,该对象一共有四个,并且都是调用自己run方法,执行的都是自己run方法里synchronized的代码块,互不相关,锁this就没有意义了,其实下面run方法后应该画出每个对象一个synchronized代码块的,但是位置不够了,就指到一起了,应该每个obj指向数据共享区的那个obj

image.png

上面图片更改:


image.png

总结:

由于局限性,接口实现优先于继承类,所以最好用实现Runnable接口,
实现Runnable接口,最好就定义一个实现类对象,分别给多个线程共享着使用,节约资源嘛,分别给多个线程不同的名字就好了
给名字:Thread t1 = new Thread(实现类对象,"名字");
然后保证数据的安全性,由于这种实现方式操作的永远是一个对象,所以这种方式用synchronized锁代码块可以,锁方法也可以,
锁代码块的时候,用synchronized(obj)可以,用synchronized(this)也可以,再次说明,因为操作的一直一直是同一个对象,是一个共享对象,所以锁obj可以,锁this也可以。


image.png

image.png

如果用继承Thread类,就必须锁obj对象哦,因为是多个子类对象操纵多个run方法,执行多个锁代码块,所以锁的条件就必须是静态obj对象哦。


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

推荐阅读更多精彩内容

  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,454评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,957评论 1 18
  • 1 多线程 1.1 多线程介绍   学习多线程之前,我们先要了解几个关于多线程有关的概念。  进程:进程指正在运行...
    圣堂刺客_x阅读 353评论 0 0
  • 文章来源:http://www.54tianzhisheng.cn/2017/06/04/Java-Thread/...
    beneke阅读 1,480评论 0 1
  • 1.1 多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念。 进程:进程指正在运行的程序。确切的来说...
    Pecksniff1994阅读 1,556评论 0 2