JUC基础知识(一)

一:线程、进程,并行、并发

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程是资源分配的最小单位。

线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个
单元执行流。线程是程序执行的最小单位。

串行模式
串行表示所有任务都按先后顺序进行。串行意味着必须先装完一车柴才能
运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步
骤,才能进行下一个步骤。
串行是一次只能取得一个任务,并执行这个任务。

并行模式
并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模
式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列
的长度。并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度上
则依赖于多核 CPU。

并发:同一时刻多个线程在访问同一个资源,多个线程对一个点
例子:春运抢票 电商秒杀…

并行:多项工作一起执行,之后再汇总
例子:泡方便面,电水壶烧水,一边撕调料倒入桶中

二:创建线程的两种方式

1、继承Thread类

//匿名内部类
Thread thread = new Thread() {
     @Override
     public void run() {
         System.out.println("继承Thread类创建线程");
     }
};
thread.start();

2、实现Runnable接⼝

public static void main(String[] args) {
     //匿名内部类
     new Thread(new Runnable() {
         @Override
         public void run() {
             System.out.println("实现Runnable接口创建线程");
         }
     }).start();

     new Thread(()->{
         System.out.println("实现Runnable接口创建线程 : 使用Lambda表达式");
     }).start();
}

三:为什么是 start 方法?

为什么是 start 方法,而不是直接调用 run 方法,这里结合源码来分析一下:

首先来看一下 Thread 类中的 run 方法:

/**
  * If this thread was constructed using a separate
  * <code>Runnable</code> run object, then that
  * <code>Runnable</code> object's <code>run</code> method is called;
  * otherwise, this method does nothing and returns.
  * <p>
  * Subclasses of <code>Thread</code> should override this method.
  *
  * @see     #start()
  * @see     #stop()
  * @see     #Thread(ThreadGroup, Runnable, String)
*/
 @Override
 public void run() {
    if (target != null) {
        target.run();
    }
 }

 /* What will be run. */
 private Runnable target;

翻译一下上面的注释:
如果此线程是使用单独的,Runnable接口 run 对象,然后Runnable接口调用对象的 run 方法;否则,此方法不执行任何操作并返回。Thread 的子类应重写此方法。

Person类实现 run 方法来实现自己的业务逻辑,如果在 main 线程中调用 run 方法,相当于并没有开辟一个线程,run 方法只是一个普通的方法,那么就会把 run 方法执行完毕,才向下执行。

如果是调用 start 方法那么就会启动线程,Runnable接口就会调用执行对象的 run 方法。主线程不会阻塞, 会继续执行,主线程和子线程是交替执行。

来看一下 start 方法
在这里插入图片描述
我们发现其实 start 中真正调用的是 start0 方法;
在这里插入图片描述
在这里插入图片描述
看到这里就很清楚了,run 方法中只是实现了业务代码,真正要实现线程还是要靠 start -> start0

四:什么是JUC?

JUC就是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包,JDK
1.5 开始出现的。
在这里插入图片描述

五:线程的状态

线程状态枚举类 Thread.State
在这里插入图片描述

六:管程

在这里插入图片描述

七:用户线程和守护线程

用户线程:也叫工作线程,一般就是我们用户自定的线程。

守护线程:指在程序运行的时候在后台提供一种通用服务的线程,守护线程是为用户线程服务的。

用户线程和守护线程的区别

二者其实基本上是一样的。唯一的区别在于JVM何时离开。

用户线程:当存在任何一个用户线程未离开时,JVM是不会离开的。

守护线程:如果只剩下守护线程未离开,JVM是可以离开的。
在这里插入图片描述

八:Synchronized

synchronized 是 Java 中的关键字,是一种同步锁。独占锁、悲观锁

它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}
    括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用
    的对象是调用这个方法的对象;

▲虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定
义的一部分,因此,synchronized 关键字不能被继承如果在父类中的某个方
法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这
个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上
synchronized 关键字才可以
。当然,还可以在子类方法中调用父类中相应的方
法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,
子类的方法也就相当于同步了。

  1. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的
    所有对象;
  2. 修改一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用主
    的对象是这个类的所有对象。

synchronized实现同步的基础:
Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
(1)对于普通同步方法,锁是当前实例对象。
(2)对于静态同步方法,锁是当前类的class对象。
(3)对于同步方法块,锁是synchonized括号里配置的对象。

一个典型的卖票案例

class Ticket {
    private int num = 50;

    public synchronized void saleTicket(){
        if (num > 0){
            num--;
            System.out.println(Thread.currentThread().getName() + ":卖出了一张票," + "还剩下" + num + "张票");
        }
    }
}

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

        new Thread(()->{
            for (int i = 0;i < 50;i++)
                ticket.saleTicket();
        },"A售票员").start();


        new Thread(()->{
            for (int i = 0;i < 50;i++)
                ticket.saleTicket();
        },"B售票员").start();

        new Thread(()->{
            for (int i = 0;i < 50;i++)
                ticket.saleTicket();
        },"C售票员").start();
    }
}

在这里插入图片描述

九:Lock接口

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

Lock 和 synchronized 有以下几点不同:

  1. Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内
    置的语言实现;
  2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现
    象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很
    可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁
  3. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用
    synchronized 时,等待的线程会一直等待下去,不能够响应中断
  4. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
  5. Lock 可以提高多个线程进行读操作的效率。
    在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源
    非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于
    synchronized

采用 Lock 接口实现卖票案例:
在这里插入图片描述

十:虚假唤醒问题

场景:有两个线程。
一个线程:当前数值为0时,对当前数值加 1。
另一个线程:当前数值为1时,对当前数值减 1。
要求用线程间通信

class Share {
    private int number = 0;

    //创建Lock
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    //+1
    public void incr() throws InterruptedException {
        //上锁
        lock.lock();
        try {
            //判断
            if (number != 0) {
                condition.await();
            }
            //干活
            number++;
            System.out.println(Thread.currentThread().getName()+" :: "+number);
            //通知
            condition.signalAll();
        }finally {
            //解锁
            lock.unlock();
        }
    }

    //-1
    public void decr() throws InterruptedException {
        lock.lock();
        try {
            if(number != 1) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+" :: "+number);
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}

显示结果:
在这里插入图片描述
根据题目要求,最终显示的结果应该都是 0 或 1 。很明显结果出错了,那么为什么会出错呢?
这就是发生了虚假唤醒的问题。

因为 wait 方法有一个特点是:在哪里睡就会在哪里醒
如果,某一个线程正好在 if 判断语句中等待,那么一旦他被唤醒,它就会跳过 if 判断语句,会直接执行下面的代码。

所以解决办法是把判断语句换成 while 语句,这样即使它在判断语句中被唤醒,它也会继续在 while 语句中进行判断
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小本科生debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值