多线程

多线程

什么是进程?线程?

进程是一个应用程序
线程是进程的一个执行单元(执行场景)
一个进程可以启动多个线程

进程A和进程B的内存独立不共享
线程A和线程B,在Java语言中,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈
启动10个线程,就有10个栈空间,每个栈与每个栈之间互不干扰,各自执行各自的,这就是多线程并发

多线程机制,目的就是为了提高程序处理的效率

在这里插入图片描述

使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束
main方法结束了,只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈,还在执行

对于单核的CPU来说,不能做到多线程并行,但是能做到多线程并发,给人一种并行的感觉
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。

实现线程的方式

编写一个类,继承java.lang.Thread,重写run方法,new实例,调start

在这里插入图片描述
在这里插入图片描述

启动线程:调用线程的start方法
start方法的作用是:在JVM中开辟一个栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了
启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部
run和main方法是平级的

如果直接调用myThread.run()会有什么区别?
如果直接调用run方法,不会开辟新的栈空间,所以等于还是在一个线程里面,不能并发

编写一个类,实现java.lang.Runnable接口,实现run方法

注意,现在这并不是一个线程类,只是一个可运行的类,还不是一个线程
怎么让它编程线程?
Thread有一个构造方法,参数就是一个Runnable对象
在这里插入图片描述
现在才是
在这里插入图片描述

使用匿名内部类

在这里插入图片描述

两种方法对比

第二种方式,实现接口比较常用,因为要面向接口编程

实现Callable接口

这种方式实现的线程可以获取线程的返回值
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
思考:get()方法会不会让当前进程阻塞?
会的,必须等待get()方法结束,因为get方法是为了拿另一个线程的执行结果,而另一个线程执行是需要时间的

关于Object类中的wait和notify方法
在这里插入图片描述

在这里插入图片描述

线程生命周期

新建状态 -> 就绪状态 <->运行状态 ->死亡状态
遇到阻塞事件(接受键盘输入、sleep方法等)进入阻塞状态,阻塞状态的线程会放弃之前占有的时间片
当阻塞解除,会回到就绪状态,抢夺cpu时间片

  • 刚new出来的线程对象是新建状态
  • 调用start方法进入就绪状态
  • 就绪状态又叫可运行状态,表示当前线程具有抢夺cpu时间片的权利(cpu时间片就是执行权)
  • 当一个线程抢夺到cpu时间片之后,就开始执行run方法
  • run方法的开始执行标志着线程进入运行状态
    在这里插入图片描述

获取线程对象

获取、设置线程的名字

默认是Tread-0、Thread-1递增
在这里插入图片描述
在这里插入图片描述

获取当前线程对象

该静态方法的返回值就是当前线程

static Thread currentThread()
Thread currentThread = Thread.currentThread();

sleep方法

static void sleep(llong millis)

静态方法
参数是毫秒
作用:让当前进程进入休眠, 进入阻塞状态,放弃占有cpu时间片,让给其它线程使用
Thread.sleep() 方法,可以做到:间隔特定的时间,去执行特定的方法,隔多久执行一次

一个面试题:
线程t是否会进入休眠状态
在这里插入图片描述
答案是:不会
因为sleep方法跟对象没关系
sleep方法是个静态方法!!
看上去调的是t1的sleep
其实调用的是Thread的静态方法sleep
在这里插入图片描述

怎么叫醒正在睡眠的进程

在这里插入图片描述
这里的sleep异常,只能try catch,不能抛出,这是为什么?
在这里插入图片描述
因为子类重写父类的方法,子类方法不能比父类方法抛出更宽泛的异常
而父类方法的run里面没有抛出异常,所以子类方法也不能抛异常

但是在run里面调用其它方法,其它方法可以throws,只不过要在方法里try catch
在这里插入图片描述

那如何叫醒一个正在睡眠的线程?
注意:不是终断线程的执行,而是中止线程的睡眠

方式一:异常处理机制

这种终端睡眠的方式依靠了java的异常处理机制

在这里插入图片描述
在这里插入图片描述
线程会进入到catch里面,try里面的结束

在这里插入图片描述
执行的话,会报异常,但还会继续执行下去,打印t—>end

终止线程的执行

方式一:结束进程: stop (过时)
在这里插入图片描述
不是终止睡眠,是直接结束掉线程
缺点:容易丢失数据。因为这种方式是直接将线程杀死了
线程没有保存的数据会丢失

打一个布尔标记
在这里插入图片描述
在这里插入图片描述

线程调度

java中提供的关于线程调度的实例方法

void setPriority(int newPriority)  设置线程的优先级
int getPriority() 获取线程的优先级
static void yield() 静态方法,暂停当前正在执行的方法,不是阻塞方法,让当前线程让位,让运行状态回到就绪状态
void join()  合并线程

yield() 静态方法,暂停当前正在执行的方法,不是阻塞方法,让当前线程让位,让运行状态回到就绪状态。回到了就绪状态之后,还有可能会抢到。
最低优先级是1
默认优先级是5
最高优先级是10

在这里插入图片描述

线程优先级

最低优先级是1
默认优先级是5
最高优先级是10

yield示例

在这里插入图片描述

join示例

join合并线程并不是意味着只有一个栈了,不是栈合并,是栈协调
在这里插入图片描述

多线程并发环境下数据的安全问题

什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:

  • 多线程并发
  • 有共享数据
  • 共享数据有修改的行为

怎么解决线程安全问题?
线程排队执行,不能并发,用排队执行解决线程安全问题,这种方式叫做线程同步机制,会牺牲一部分效率

同步编程模型:
线程之间发生了等待关系,线程排队执行,效率较低

异步编程模型:
线程各自之间谁也不用等谁,效率较高

线程同步机制的语法:

synchronized(共享对象(会被加上锁)){
	//线程同步代码块
}

或者在实例方法上加synchronized
但是synchrnized出现在实例方法上锁的一定是this,而且同步的一定是整个方法体,效率降低
所以这种方式不灵活
但是优点是代码少了
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
下面的也可以,因为"abc"在字符串常量池中,会被加一个锁
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
注意,锁池不是一种状态,不属于状态模型,可以理解为一种阻塞

在这里插入图片描述
常量没有线程安全问题:因为常量不会被修改

如果使用局部变量的话,建议使用StringBuilder,因为不存在线程安全问题

在这里插入图片描述

面试题:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

答案是不需要,因为doOhter根本就没加锁,虽然synchronized锁的是对象,但是只锁了doSome方法

在这里插入图片描述
如果MyClass编程这样,问,doOhter要不要等待doSome

答案是要
因为两个方法都在争夺this对象锁

如果main方法变成了这样,还要不要等待?
在这里插入图片描述

答案是不要,因为抢的不是同一个对象的this对象锁,有两个对象,两把锁

现在,如果MyClass变成了现在这样,问,doOther要不要等doSome?
在这里插入图片描述

答案是要,因为synchronized出现在了类方法上,两个方法现在抢的是类锁,不管创建了几个对象,都只有一个类锁

死锁

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

synchronized 在开发中最好不要嵌套使用,可能导致死锁

在这里插入图片描述

守护线程

java语言中的线程可以分为用户线程和守护线程(后台线程)
比如垃圾回收线程就是个守护线程
所有的用户线程结束了之后,守护线程自动结束
守护线程一般是一个死循环

守护线程用在什么地方?

  • 比如每天00:00的时候系统数据自动备份

在这里插入图片描述
在这里插入图片描述

定时器

间隔特定的时间,执行特定的程序
实现方式:
在这里插入图片描述
SpringTask底层的原理也是Timer

在这里插入图片描述
注意这是一个抽象类,需要被继承,然后实现run方法
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

wait 和 notify

这两个方法是Object类中自带的
wait方法作用:
Object o = new Object();
o.wait();
表示:让正在o对象上活动的线程进入等待状态,无期限等待
在这里插入图片描述

notify方法作用
Object o = new Object();
o.notify();
o.notifyAll();
表示唤醒正在o对象上等待的线程
在这里插入图片描述
在这里插入图片描述


public class CusProducer {
    public static void main(String[] args) {
        int[] num = new int[]{1};
        Thread t1 = new Thread(new SingleNum(num));
        Thread t2 = new Thread(new DoubleNum(num));
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();

    }




}



class SingleNum implements Runnable{
    int[] num;
    public SingleNum(int[] num){
        this.num = num;
    }
    @Override
    public void run() {
        while(true){
            synchronized (num){
                if(num[0] % 2 == 0) {
                    try {
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "--> " + num[0]);
                num[0]++;
                num.notifyAll();
            }

        }
    }
}


class DoubleNum implements Runnable{
    int[] num;
    public DoubleNum(int[] num){
        this.num = num;
    }
    @Override
    public void run() {
        while(true){
            synchronized (num){
                if(num[0] % 2 == 1) {
                    try {
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "--> " + num[0]);
                num[0]++;
                if(num[0] > 100)break;
                num.notifyAll();
            }

        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值