java进阶-第十三讲 多线程

本文详细讲解了Java中的进程与线程概念,探讨了并发与并行的区别,重点介绍了如何通过继承Thread类和实现Runnable接口创建多线程,以及标准的线程操作和面向对象编程的高内聚低耦合原则。通过实例演示了如何模拟火车票销售场景实现并发控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java进阶-第十三讲 多线程

1 进程与线程

进程:
	进程是正在运行的一个个程序。一个程序一般只有一个进程。
	这个进程死了,程序也就结束了。
线程:
	一个进程中有很多线程,java中的main方法也叫作主线程。GC也叫作GC线程。
	我们知道,java中代码是自上而下顺序执行的。比如顺序排列的方法想要同时被执行
	就要涉及到线程。

2 并发和并行

并行:
	并排行驶,相当于两个人排排走。也就是说,两个程序,各走各的,互不干涉。
	多核的CPU有才能做到并行

并发:
	其实是交替执行的,同一时间,只有一个程序在执行。执行到一定时间之后,
	切换到另一个程序执行。
	大部分情况下,单核的CPU就是并发的。
	A运行一段时间,B运行,A等待,B运行一段时间,B等待,A运行,交替执行。
	由于CPU的处理速度很快很快。所以造成了同时执行的感觉。
	问你们:
	你们有没有过一边看书一边听歌的时候?
	你们想想你们看书和听歌实际上是同时被大脑处理了吗?
	对于大脑来说,某一个时间点,只做了一件事,要么看书要么听歌。这就是并发。
	
	对于并发来说,我们是没法控制CPU的执行的,CPU给谁执行权是CPU调度的事情
	这个问题在计算机设计好了之后就已经基本固定了。
	
	比如看电影:
		电影是连续的吗?实际上是静态的一个又一个的画面,只不过画面播放速度很快
		使得影片看上去是连续的。
		
程序中涉及到多线程之后,其执行原理也是这样,谁先执行谁后执行,
在程序员没有做处理的情况下,由CPU说了算。人力不能干涉。

3 线程

java中,我们操作的都是对象,线程也不例外。
我们操作的线程也是对象,对象要有类,线程就是一个类。Thread
Thread:线

线程在java中到底是什么?
java中代码是顺序执行的,这个是不能改变的事实。
为什么是顺序执行的呢?因为栈。
方法在栈中执行,方法执行的时候要压栈弹栈。栈结构就是First in last out
这就是自上而下顺序执行的原理所在。
如何做到并发呢?
	开辟另外的栈。
	这就是多线程的原理。多线程的本质是多个栈内存空间。
	每开一个线程,实际是在JVM栈内存空间中开辟了一个新的栈内存空间。
	main方法所在的线程叫做主线程,main方法是所有程序的入口
	入口只有一个。也就是说主线程只有一个。

比如:
	大学生活,是一个程序,该程序主要功能是学习。这个程序中还有其他的功能比如锻炼、打游戏、谈恋爱、参加社团、学生会等等。
	学习是主线,这要占用一定的时间,时间我们就可以看成是程序中的栈。其他的功能都是并发在做,因为一个时间点只能做一件事,所以是并发。学习就是主线程,其他的就是其他的线程。他们占用不同的时间,也就是说在不同的栈中。
	

4 操作多线程

方式一:一个类继承Thread类,重写run方法
package com.tj.threadTest;

/**
 * @program: Study
 * @description:
 * @author: tianjie
 * @create: 2021-02-19 10:59
 */
    public class ThreadTest03 extends Thread {
        private int num;

    public ThreadTest03() {}

    public ThreadTest03(int num) {
        this.num = num;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程 --> i = " + i);
        }
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "ThreadTest03{" +
                "num=" + num +
                '}';
    }
}

开启线程:
    new 对象,然后调用Thread的start()方法。线程就会被开启。
    记住不是调用run方法,run方法不需要手动调用,是依靠start方法来开启线程
    然后,JVM自动调用run方法。
public class Client {

    public static void main(String[] args) {
        Thread t = new ThreadTest03(10); // 创建一个线程对象
        t.start();// 启动线程
        // 1. 并没有调用run方法,为什么"线程 --> i = "会被打印,run为什么会被执行
        for (int i = 0; i < 1000; i++) {
            System.out.println("main ---> i = " + i);
        }
        // 2. 为什么会在某些时段出现main和线程交替打印的结果
        // 3. 上来就打印main,这是为什么?
        // 4. 程序每次执行的结果都不一样

    }
}

// 开启线程不能直接调用run方法,run方法不是手动调用的,是JVM自动调用的
// 开启线程要调用Thread类的start方法。
为什么是这样?
    

在这里插入图片描述

  • 第二种方式:
第一种方式有什么缺陷?
	class A extends Thread {}//A类的资源要被很多线程访问
	使用继承的方式就是最大的缺陷。因为java只能单继承,不能多继承。一旦继承了
	Thread类,A类无法继承其他类。
	
	class Dog extends Animal{}
	Dog类要并发,extends Thread,这时候还能做吗?
	
这种缺陷怎么弥补?
	Thread类实现了Runnable接口,那么我们可以避开Thread类,直接实现Runnable接口
	Runnable接口没有start方法,它是函数式接口,只有run方法
    如何启动线程?启动线程只能是start方法。没有第二条路。记住!
    Thread(Runnable target) 这是Thread的构造法方法,它可以放一个
    Runnable的实例,Runnable是一个接口,没有实例,只能放Runnable的实现类的实例
    Thread(dog)
	
public class Dog implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("dog --->" + i);
        }
    }
}


public class DogClient {

    public static void main(String[] args) {
        Dog dog = new Dog();
        // Runnable接口没有start方法,它是函数式接口,只有run方法
        // 如何启动线程?
        Thread t = new Thread(dog);
        t.start();// 借助Thread类来启动线程,因为start方法只有Thread类中有

        for (int i = 0; i < 100; i++) {
            System.out.println("main --> " + i);
        }

    }
}

5 应用中标准的写法

多线程的操作:
	1.资源类:就是一个java类,这是多线程操作的对象。
	2.线程类:要能够开启线程,就要有线程类,Thread类、Runnable接口
	3.因为Thread类实现了Runnable接口,所以,我们可以使用lambda表达式。
	4.lambda表达式中,就是对资源类的操作。
面向对象编程最核心的思想:
	高内聚低耦合
	高内聚:一个类很单纯,一个方法很单纯。方法只实现特有的功能。
	比如读书,就是读书,不要一边读书一边打游戏一边听歌,这不是高内聚。
	高内聚就是专注度。
	低耦合:就是修改一个类的代码,不需要改动其他类的代码。
	
写多线程的时候:(切记:一定要把资源类和线程剥离开,这就是低耦合)
	1.先写资源类
	2.再创建线程并在线程中实现run方法,run方法操作资源类
    3.开启线程
package com.tj.threadTest.improve;

/**
 * @program: Study
 * @description: 人们要购买长沙到北京的火车票,这时候有3个窗口在买同一趟火车的票。
 *              票数总共100张。不能多个人买同一张票,也不能多卖。
 *              我们先实现3个窗口同时卖票
 * @author: tianjie
 * @create: 2021-02-19 12:02
 */
public class Ticket {
    private int num;

    public Ticket() {
    }

    public Ticket(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "Ticket{" +
                "num=" + num +
                '}';
    }


public void sale() {
    if (num > 0) {
       System.out.println(Thread.currentThread().getName() +"\t 卖出第" + (num--) +"\t 剩余票数:" + num);
        }
    }
}



public class SaleWindow {
    public static void main(String[] args) {
        Ticket ticket = new Ticket(100);

        new Thread(()->{
            for (int i = 0; i < ticket.getNum()*10; i++) {
                ticket.sale();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < ticket.getNum()*10; i++) {
                ticket.sale();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < ticket.getNum()*10; i++) {
                ticket.sale();
            }
        },"C").start();

    }
}

上述代码的演化过程:
    1 先有局部内部类实现Runnable,重写run方法,调用资源类中的sale()
    	这叫做:操作资源类
public class SaleWindow {
    public static void main(String[] args) {
        Ticket ticket = new Ticket(100);

        class A implements Runnable {

            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
    	// 这叫做:操作资源类
                    ticket.sale();
                }
            }
        }

        Thread t = new Thread(new A(),"A");
        t.start();
    }
}



     // 匿名内部类,多态的机制,父接口的引用指向实现类的实例
        // new Runnable()实际上是new的Runnable实现类的对象
        // 因为是匿名内部类,所以没有名字,用Runnable来代替
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {

                    ticket.sale();
                }
            }
        };

        Thread t = new Thread(r,"B");
        t.start();


// 最终是要将Runnable的引用r传入到Thread()构造方法中的
// 那还不如在直接在构造方法的参数列表中写
// 如下:
 Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {

                    ticket.sale();
                }
            }
        }, "B");
        t.start();

// 再简化。
// 已经创建了实例,就直接可以调用start()来启动线程
// 因为start()返回值为void,所以前面的引用可以不要了
new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {

                    ticket.sale();
                }
            }
        }, "B").start();

// 终极简化 lambda表达式
new Thread(
    () -> {
        for (int i = 0; i < 100; i++) ticket.sale();
    }
    , "B").start();

lambda表达式:
    1. 函数式接口
    2. ()->{}
    3. {}中实现函数式接口中的抽象方法,只要写方法体
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值