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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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