37.Java之线程(单线程、多线程、并发、并行、继承 Thread 类、实现Runnable接口、线程的常用方法、线程的生命周期、线程的同步机制、互斥锁、线程的死锁与释放锁)

37.1 程序(program)

是为完成特定任务、用某种语言编写的一组指令集合
简单而言:就是自己写的代码

37.2 进程
  1. 进程是指运行中的程序,比如启动迅雷时,就启动了一个进程,操作系统就会为该进程分配内存空间。
  2. 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
37.3 线程
  1. 线程由进程创建的,是进程的一个实体
  2. 一个进程可以拥有多个线程
单线程:同一个时刻,只允许执行一个线程
多线程:同一个时刻,可以执行多个线程
并发:同一个时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单的说,单核CPU实现的多任务就是并发
并行:同一个时刻,多个任务同时执行。多核CPU可以实现并行
37.4 创建线程的两种方式

在 java 中线程来使用有两种方法:

  1. 继承 Thread 类,重写 run 方法
  2. 实现 Runnable 接口,重写 run 方法
    在这里插入图片描述
例子:
继承 Thread 类
public static void main(String[] args) {
    A a = new A(a);
	a.start();//启动线程
	//说明:当main线程启动一个子线程 A,主线程不会阻塞,会继续执行
	//这时 主线程和子线程是交替执行
}
......
class A extends Thread {
/*
	1.当一个类继承了 Thread 类,该类就可以当做线程使用
	2.会重写 run 方法,写上自己的业务代码
	3.run Thread 类 实现了 Runnable 接口的run方法
*/
    @Override
    public void run() {
        super.run();
    }
}

启动线程调用 start() 方法底层是
public synchronized void start() { start0(); }
start0() 是本地方法,是 JVM 调用,底层是 c/c++实现
真正实现多线程的效果,是 start0(),而不是 run()
在这里插入图片描述

实现Runnable接口
  1. java 是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承 Thread 类方法来创建线程显然不可能了
  2. java 设计者们提供了另外一个方式创建线程,就是通过实现 Runnable 接口来创建线程
public static void main(String[] args) {
    B b = new B();
    Thread thread = new Thread(b);
    thread.start();
}
......
class B implements Runnable{
    @Override
    public void run() {
    ...
    }
}
继承Thread vs 实现Runnable的区别
  1. 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
public static void main(String[] args) {
    T t = new T();
    Thread thread01 = new Thread(t);
    Thread thread02 = new Thread(t);
    thread01.start();
    thread02.start();
}
......
class T implements Runnable{
    @Override
    public void run() {
    ...
    }
}
37.5 线程的常用方法
  1. setName():设置线程名称,使之与参数 name 相同
  2. getName():返回该线程的名称
  3. start():使该线程开始执行-java虚拟机底层调用该线程的 start() 方法
  4. run():调用线程对象 run 方法,start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程
  5. setPriority():更改线程的优先级
  6. getPriority():获取线程的优先级
  7. sleep():线程的静态方法,在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt():中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
yield():线程的礼让

让出CPU,让其它线程执行,但礼让的时间不确定,所以也不一定礼让成功

join():线程的插队

插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务

用户线程和守护线程

setDaemon(true) 设置为守护线程

  1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
  2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
  3. 常见的守护线程:垃圾回收机制
37.6 线程的生命周期
  • RUN
    尚未启动的线程处于此状态
  • RUNNABLE
    在Java虚拟机中执行的线程处于此状态
  • BLOCKED
    被阻塞等待监视器锁定的线程处于此状态
  • WAITING
    正在等待另一个线程执行特定动作的线程处于此状态
  • TIMED_WAITING
    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
  • TERMINATED
    已退出的线程处于此状态
    在这里插入图片描述
37.7 线程的同步机制
  1. 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
  2. 线程同步,可以理解为当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
同步具体方法-Synchronized
  1. 同步代码块
synchronized(对象) { //得到对象的锁,才能操作同步代码
	//需要被同步代码;
}
  1. synchronized 还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m (String name) {
	//需要被同步的代码
}
互斥锁
  1. Java 在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  2. 每个对象都对应与一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象
  3. 关键字 synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任意时刻只能由一个线程访问
  4. 同步的局限性:导致程序的执行效率要降低
  5. 同步方法(非静态的)的锁可以是 this,也可以是其他对象(要求是 同一个对象
  6. 同步方法(静态的)的锁为当前类本身
  1. 同步方法如果没有使用 static 修饰:默认锁对象为 this
  2. 如果方法使用 static 修饰,默认锁对象:当前类.class

两个人轮流消费例子:

//当总金额 <=0 时,不能进行消费
public class Thread1 {
    public static void main(String[] args) {
        GetMoney g = new GetMoney();
        Thread a = new Thread(g);
        a.setName("小A");
        Thread b = new Thread(g);
        b.setName("小B");
        a.start();
        b.start();
    }
}
class GetMoney implements Runnable {
    int total = 10000;
    //private boolean loop = true;
    /*public synchronized void getM(){
        if (total <= 0) {
            System.out.println("余额不足...");
            loop = false;
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        total -= 1000;
        System.out.println(Thread.currentThread().getName()+" 余额还剩" + total);
    }*/
    @Override
    public void run() {
        while (true) {
            //getM();
            synchronized (this) {
                if (total <= 0) {
                    System.out.println("余额不足...");
                    //loop = false;
                    break;
                }
                total -= 1000;
                System.out.println(Thread.currentThread().getName()+" 余额还剩" + total);
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
37.8 线程的死锁与释放锁

多个线程都占用了对方的锁资源,但不肯想让,导致了死锁,在编程是一定要避免死锁的发生。

下面的操作会释放锁
  1. 当前线程的同步方法、同步代码块执行结束
  2. 当前线程在同步代码块、同步方法中遇到 break、return
  3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
  4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
下面操作不会释放锁
  1. 线程执行同步代码块或同步方法时,程序调用 Thread.sleep()Thread.yield() 方法暂停当前线程的执行,不会释放锁
  2. 线程执行同步代码块时,其他线程调用了该线程的 suspend() 方法将该线程挂起,该线程不会释放锁

应尽量避免使用 suspend()resume() 来控制线程,该方法已被弃用

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值