Java多线程

什么是进程?什么是线程?它们之间有什么关系?

进程是一个应用程序。进程是系统进行资源分配和调度的一个独立单位,最小的资源管理单位

线程是一个进程中的执行单元/执行场景。线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,最小的 CPU 执行单元

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

进程与进程之间内存独立不共享。

线程与线程之间可能就会存在资源共享,在java中是堆内存和方法区内存共享,栈内存不共享,栈内存独立,一个线程一个栈。假设启动10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各的,这就是多线程并发。

使用了多线程之后,mian方法结束,是不是有可能程序不会结束。mian方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弾栈。
在这里插入图片描述
分析一个问题,对于一个单核的CPU来说,真的可以做到真正的多线程并发吗?

对多核的CPU电脑,真正的多线程并发是没问题的。4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。

单核CPU表示只有一个大脑,在某一个时间点上只能处理一件事情,不能做到真正的多线程并发,但是CPU处理速度极快,通过时间片轮转的方式多个线程频繁切换执行,可以给人一种多线程并发的感觉。

java实现多线程的三种方式

  • 第一种,编写一个类直接继承Thread类,重写run()方法。

  • 第二种,编写一个类实现Runnable接口,重写run()方法。

  • 第三种,实现Callable接口,重写call()方法。

    ​ (这种方式可以获取线程的返回值,call方法可以抛异常;上面两种方式线程不可以有返回值,run方法不能抛异常;

启动线程
在这里插入图片描述
如果不用start()方法,直接调用run()方法,会怎样?

线程不会启动,不会分配新的分支栈空间。这种方式就相当于单线程,只是一个普通方法调用。
在这里插入图片描述
在这里插入图片描述

线程的生命周期

在这里插入图片描述

sleep()

  • 关于sleep()方法的一个面试题: 判断new的这个线程会休眠5秒吗?不会。

在这里插入图片描述
因为sleep方法是Thread类的一个静态方法,跟谁调没关系(就是不用多态方式,用myThread3 t = new myThread3();去调,是同样的结果),这个方法当前出现在哪就是让当前线程睡眠。

  • **如何中断sleep睡眠?这种方式是使用了线程的异常处理机制,执行interrupt()**时,会让run方法报出异常,catch从而捕获异常,就此try/catch结束,程序继续往下执行。就实现了中断线程睡眠。
  • 在这里插入图片描述
    调用 interrupt() 方法仅仅是在当前线程中打一个停止的标记,并不是真的停止线程。

也就是说,线程中断并不会立即终止线程,而是通知目标线程,有人希望你终止。至于目标线程收到通知后会如何处理,则完全由目标线程自行决定。

  • **如何终止一个线程?**stop(), interrupt(), 设置标记位。

    • 1、stop()方法,已过时不建议使用。因为这个方法存在很大的缺点:容易丢失数据。它是直接将线程杀死了,线程没有保存的数据将会丢失。不建议使用。

    • 2、flag标记位终止。(推荐使用)

    • public class testThread {
      
          public static void main(String[] args) {
              myThread t = new myThread();
              t.start();
              //...
              t.exit = true;//修改标志位,退出线程
          }
      }
      class myThread extends Thread{
          public volatile boolean exit = false;
          @Override
          public void run() {
          /*  while (!exit){
                  System.out.println(Thread.currentThread().getName());
              }*/
              //或者
              if (!exit){
                  System.out.println(Thread.currentThread().getName());
              }else {
                  //return 就结束了,结束之前,有什么没保存的在这里保存。
                  return;
              }
          }
      }
      

线程调度

  1. 常见线程的调度模型有哪些

  2. java中提供了哪些方法是和线程调度有关系的呢?

    在这里插入图片描述

线程安全(重点)

  1. 为什么是重点?

以后在开发中,我们会很少有机会去写一个线程类,因为我们项目都是运行在服务器中的,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现了。这些代码是不需要我们编写的。

最重要是:我们要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点)

  1. 什么时候数据在多线程并发的环境下会存在安全问题呢?

    三个条件:(满足这三个条件就会发生线程安全问题

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

    怎么解决这个个问题呢?(使用线程同步机制

    • 线程排队执行(不能并发)。用排队执行解决线程安全问题。这种机制被称为“线程同步机制”。

      线程同步实际上就是线程排队了,不并发了,线程必须排队执行。线程排队会牺牲一部分效率,没办法,数据安全第一位!

  2. 异步编程模型&同步编程模型

    • 异步编程模型:两个线程各自执行各的,互不干扰,其实就是多线程并发(效率较高)。异步就是并发
    • 同步编程模型:线程执行的时候,一个线程执行,其他必须等待,线程之间发生了等待关系,线程排队执行。同步就是排队
  3. synchronized实现线程同步

    • 同步代码块:synchronized(共享对象){同步代码块}

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

    • 在实例方法上使用synchronized修饰。表示共享对象一定是this,并且同步代码块是整个方法体。这种方式不灵活。

    • 使用在静态方法上。表示类锁。只有一个,就算new100各对象,也只有一把类锁。

    在这里插入图片描述

  4. java中有三大变量:

    • 实例变量:在堆中。
    • 静态变量:在方法区。
    • 局部变量:在栈中。
    • 以上三个变量中,局部变量永远不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)局部变量在栈中,所以局部变量永远不会共享。实例变量在堆中,堆只有一个,静态变量在方法区中,方法区只有一个。堆和方法区都是多线程共享的,所以可能存在线程安全问题。
  5. 死锁:

    假设两个线程t1和t2分别取锁两个对象o1和o2。t1先锁o1再锁o2,t2先锁o2再锁o1。这时候,t1就锁不上o2(因为已经被t2锁了),t2就锁不上o1(因为已经被o1锁了),两个线程就会僵持在那里,造成死锁现象。

    死锁代码要会写,一般面试官会要求你写,只有会写才会在以后的开发中注意,因为死锁很难调试。

    public class DeadLock{
    	 public static void mian(String[] args){
             Object o1 = new Object();
             Object o2 = new Object();
             
             Thd1 t1 = new Thd1(o1,o2);
             Thd1 t2 = new Thd2(o1,o2);
             
             t1.start();
             t2.start();
         }
    }
    class Thd1 extends Thread{
        Object o1;
        Object o2;
        public Thd1(Object o1,Object o2){
            this.o1 = o1;
            this.o2 = o2;
        }
        public void run(){
            synchronized(o1){
                try{
                    Thread.sleep(1000)
                }catch(Exception e){
                    e.printStackTrace();
                }
                synchronized(o2){
    			}
            }
        }
    }
    class Thd2 extends Thread{
        Object o1;
        Object o2;
        public Thd2(Object o1,Object o2){
            this.o1 = o1;
            this.o2 = o2;
        }
        public void run(){
             synchronized(o2){
                try{
                    Thread.sleep(1000)
                }catch(Exception e){
                    e.printStackTrace();
                }
                synchronized(o1){
    			}
            }
        }
    }
    

    这就是一个死锁程序,运行后,不会报错,也不会停止,一致这样僵持下去。因此,在开发中synchronized最好不要嵌套使用,一不小心就容易造成死锁。

  6. 以后我们开发中要怎么解决线程安全问题?

    是一上来就选择线程同步吗?synchronized。不是。synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得以的情况下再选择线程同步机制。

    第一种方案:尽量使用局部变量代替“实例变量和静态变量”

    第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)

    第三种方案:如果不能使用局部变量,对象也不能创建多个,这时候就只能选择synchronized了。线程同步机制。

  7. 守护线程

    Java中线程分为两大类:用户线程;守护线程(后台线程)其中最有代表性的就是垃圾回收线程(守护线程)。

    守护线程的特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束(用户线程没了,就没必要继续守护了)。主线程mian方法是一个用户线程。

    使用很简单,只需要在线程start前调用 Daemon()方法即可。

  8. 定时器

    定时器的作用:间隔特定时间,执行特定程序。

    在开发中,每隔多久执行一段特定的程序,这种需求时很常见的。

在这里插入图片描述

  1. Callable接口,第三种线程实现方式

在这里插入图片描述

  1. 关于Object类中wait和notify方法

  2. 生产者和消费者模式

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值