Java多线程

多线程

进程?线程?

  • 进程是一个应用程序(1个进程是一个软件)
    • 资源管理的最小单元
    • 独立的内存空间
    • 包含一个或者多个线程
  • 线程是一个进程的执行场景/执行单元
    • 程序执行的最小单元
    • 拥有独立的栈空间

一个进程可以启动多个线程。

Eg

java程序中至少有两个线程并发

  1. 垃圾回收线程
  2. 执行main方法的主线程

进程和线程是什么关系

进程A和进程B的内存独立不共享

线程A和线程B是什么关系

在java语言中:

线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个进程一个栈

Eg

假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

java中之所以用多线程机制,目的就是为了提高处理效率。

Think

  1. 使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束?

main方法结束只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。

​ 2.对于单核CPU来说,真的能做到真正的的多线程并发吗?

单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。

对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给别人的感觉是:多个事情同时在做!!

线程对象的生命周期

  1. 新建状态
  2. 就绪状态
  3. 运行状态
  4. 阻塞状态
  5. 死亡状态

继承java.lang.Thread

编写一个类,直接继承 java,lang.Thread, 重写run方法。

  1. 怎么创建线程对象? new继承线程的类。
  2. 怎么启动线程呢? 调用线程对象的start()方法。
// 定义线程类
public class MyThread extends Thread{
	public void run(){
	
	}
}
// 创建线程对象
MyThread myThread = new MyThread();
// 启动线程。
myThread.start();

注意

  • myThread.run() 不会启动线程,只是普通的调用方法而已。不会分配新的栈。(这种方式还是单线程)
  • myThread.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个全新的栈空间
    • 这段代码的任务只是为了开启一个新的栈空间,子要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
    • 启动成功的线程回自动调用run方法,并且run方法在分支栈的栈底。
      • run方法在分支栈的栈底
      • main方法在主栈的栈底
      • run和main是平级的

实现 java.lang.Runnable

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

  1. 怎么创建线程对象? new线程类传入可运行的类/接口。
  2. 怎么启动线程呢? 调用线程对象的 start() 方法。
// 定义一个可运行的类
public class MyRunnable implements Runnable {
	public void run(){
	
	}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();

线程类 Thread

获取当前线程对象、获取线程对象名字、修改线程对象名字

方法名作用
static Thread currentThread()获取当前线程对象
String getName()获取线程对象名字
void setName(String name)修改线程对象名字

线程的sleep方法

方法名作用
static void sleep(long millis)让当前线程休眠millis秒
  1. 静态方法:Thread.sleep(1000);
  2. 参数是毫秒
  3. 作用:让当前线程进入休眠,进入“阻塞状态”, 放弃占用CPU时间片,让给其他线程使用。
方法名作用
void interrupt()终止线程的睡眠

为什么run()方法只能try…catch…不能throws?

因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。

强制终止一个线程执行

不推荐使用

  1. 容易丢失数据;
  2. 这种方式直接将线程杀死了,线程没有保存的数据将会丢失。

合理结束一个进程的执行

常用

public class ThreadTest10 {
    public static void main(String[] args) {
        MyRunable4 r = new MyRunable4();
        Thread t = new Thread(r);
        t.start();

        // 模拟5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 终止线程
        // 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。
        r.run = false;
    }
}

class MyRunable4 implements Runnable {

    // 打一个布尔标记
    boolean run = true;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            if(run){
                ...
            }else{
                ...
            }
        }
    }
}

java进程的优先级

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

常量

常量名备注
static int MAX_PRIORITY最高优先级(10)
static int MIN_PRIORITY最低优先级(1)
static int NORM_PRIORITY默认优先级(5)

方法

方法名作用
int getPriority()获取线程优先级
void setPriority(int newPriority)设置线程优先级

线程安全问题

满足三个条件:

  1. 多线程并发
  2. 有共享数据
  3. 共享数据有修改行为

满足以上三个条件之后,就会存在线程安全问题。

解决线程安全问题

线程排队执行。(不能并发)用排队执行解决线程安全问题。

这种机制称为:线程同步机制

异步编程模型

线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。

其实就是:多线程并发(效率较高。)

异步就是并发。

同步编程模型

线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。

效率较低。线程排队执行。

同步就是排队。

线程安全

synchronized-线程同步

语法

synchronized(){
	// 线程同步代码块。
}

synchronized后面小括号() 中传的这个“数据”是相当关键的。这个数据必须是 多线程共享 的数据。才能达到多线程排队。

Eg

假设t1、t2、t3、t4、t5,有5个线程,你只希望t1 t2 t3排队,t4 t5不需要排队。

你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。

  • 这里的共享对象是:账户对象。
  • 账户对象是共享的,那么this就是账户对象!!!
  • ()不一定是this,这里只要是多线程共享的那个对象就行。

Java中三大变量

  • 实例变量:在堆中。

  • 静态变量:在方法区中

  • 局部变量:在栈中

堆和方法区都是多线程共享的,所以可能存在线程安全问题。

局部变量

局部变量永远不会存在线程安全问题。

  • 局部变量不共享。(一个线程一个栈)
  • 局部变量在栈中。所以局部变量永远都不会共享。

死锁

是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。

产生的原因

资源竞争

  • 系统中的资源可以分为两类
    • 可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
    • 另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
  • 产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
  • 产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁

进程间推荐顺序非法

  • 若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
  • 例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁

产生的4个必要条件

  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

解决死锁的基本方法

预防死锁:

  • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
  • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
  • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

避免死锁:

  • 预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
  • :首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。

解除死锁:

当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

  • 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
  • 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

Object类

方法名作用
void wait()让活动在当前对象的线程无限等待(释放之前占有的锁)
void notify()唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁)
void notifyAll()唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁)

wait()

Object o = new Object();
o.wait();

让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。

notify()

Object o = new Object();
o.notify();

唤醒正在o对象上等待的线程。

notifyAll()

Object o = new Object();
o.notifyAll();

的线程无限等待(释放之前占有的锁) |
| void notify() | 唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁) |
| void notifyAll() | 唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁) |

wait()

Object o = new Object();
o.wait();

让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。

notify()

Object o = new Object();
o.notify();

唤醒正在o对象上等待的线程。

notifyAll()

Object o = new Object();
o.notifyAll();

这个方法是唤醒o对象上处于等待的所有线程

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值