多线程(重点 :创建线程、同步机制)

  • Lambda表达式
    函数式接口:只包含一个抽象方法的接口,就可以用Lambda表达式

    线程的四种实现方法

  • 1、继承Thread类,不是函数式接口,用不了Lambda表达式,重写run()方法,创建继承类对象,调用start():1.方法开启线程,2.调用当前线程的run()(不推荐)
//注意:开启线程后,线程不一定立即执行,由cpu执行调度
public class MyThread extends Thread{
      /*共享资源要加static,因为这是extends方式,多线程会创建多个MyThread对象,
      如果不加static,那么每个线程都会有一个num*/
      private static int num;
      //重写run()
    @Override
    public void run() {
      //执行体
      for (int i = 0; i < 100; i++) {
            //Thread.currentThread().getName()    得到当前线程名
             System.out.println(Thread.currentThread().getName()+"第"+i+"次");
        }
    }

    public static void main(String[] args) {

      //开启线程,一个线程只能启动一次
      //start()的作用:1.开启线程    2.调用run()
      new MyThread().start();
      new MyThread().start();
    }
}
}
  • 2、实现Runnable接口(函数式接口),实现run(),创建线程对象,调用start()(推荐)
public class MyThread implements Runnable{
      //当多个对象使用同一Runnable接口是,num为共享资源,不用加static,继承方式就要加static
      private int num;
      //重写run()
    @Override
    public void run() {
        //执行体
        for (int i = 0; i < 100; i++) {
        //Thread.currentThread().getName()    得到当前线程名
        System.out.println(Thread.currentThread().getName()+"==>第"+i+"次");
          }
       }

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
          //开启线程,同一个对象t,可以被多个线程同时使用,一个线程只能启动一次
          //设置线程名
          //start()方法开启线程,调用当前线程的run()
        new Thread(t1,"线程一").start();
        new Thread(t1,"线程二").start();

    }
}
//方式二:使用Lambda表达式
public class MyThread{

    public static void main(String[] args) {
        Thread t1 = new Thread(//使用Lambda表达式
                ()->{
                    for (int i = 0; i < 100; i++) {
                        //Thread.currentThread().getName()  得到当前线程名
                        System.out.println(Thread.currentThread().getName()+"==>第"+i+"次");
                    }
                }
        ,"线程1");
        //一个线程只能启动一次
        t1.start();
    }
}

继承Thread和实现Runnable区别

    "继承Thread" : 会有OOP单继承的局限性
    "实现Runnable" : 避免了OOP单继承的局限性,方便同一个对象被多个线程使用
  • 3、实现Callable接口(函数式接口)4、结合线程池
//1.实现Callable接口,需要设置返回值类型
public class MyThread implements Callable<Integer> {
    //2.重写call方法,需要抛出异常
    @Override
    public Integer call() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"==>第"+i+"次");
        }
        return 1;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3.创建目标对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        //4.创建执行服务,创建线程池,并指定线程池容量
        ExecutorService service = Executors.newFixedThreadPool(3);

        //5.传入指定目标线程对象,提交执行
        Future<Integer> result1 = service.submit(t1);
          //service.execute(Runnable run);没有返回值,适用于Runnable
          //service.submit(Callable call);有返回值,适用于Callable
        Future<Integer> result2 = service.submit(t2);
        Future<Integer> result3 = service.submit(t3);

        //获取各线程结果
        Integer integer1 = result1.get();
        Integer integer2 = result2.get();
        Integer integer3 = result3.get();

        //关闭服务
        service.shutdownNow();
    }
}
  • 使用线程池的好处
    (1)提高响应速度(减少了创建新线程的时间)
    (2)降低资源消耗(复用线程)
    (3)利于管理:
          corePoolSize : 核心池的大小
          maximumPoolSize : 最大线程数
          keepAliveTime : 线程空闲多长时间后会终止

线程的五大状态

alt

alt

  • 线程礼让(了解)
//描述:给别的线程再一次竞争cpu的机会,不一定会成功,成功与否看cpu让那条线程执行
//用法,Thread的静态方法yield()
Thread.yield();
  • join插队行为(了解)

    public class MyThread implements Runnable{
      //重写run()
      @Override
      public void run() {
          //执行体
          for (int i = 0; i < 100; i++) {
              System.out.println(Thread.currentThread().getName()+"==> 第"+i+"次");
          }
      }
    
      public static void main(String[] args) throws InterruptedException {
          MyThread t1 = new MyThread();
          Thread vip = new Thread(t1, "VIP线程");
          vip.start();    //一个线程只能启动一次
          for (int i = 0; i < 100; i++) {
              if(i==50){
                  //当main线程执行50次后,VIP线程强行插队啦
                    //main线程被阻塞直到Vip线程执行完毕
                  vip.join();
              }
                  System.out.println("main ==> 第"+i+"次");
          }
      }
    }

alt

模拟延时:放大问题的发生性

  • 获取当前系统时间

    //获取当前系统时间
    public class Mytest {
      public static void main(String[] args) throws InterruptedException {
          //获取当前系统时间毫秒值
          Date times = new Date(System.currentTimeMillis());
    
          //每过一秒打印一次
          while(true){
              System.out.println(new SimpleDateFormat("YYYY年 MM月 dd日  HH:mm:ss").format(times));
              //打印一次更新一次
              Thread.sleep(1000);
              times = new Date(System.currentTimeMillis());
          }
      }
    

}

- set 、 get name/pripority 设置、获取线程名、优先级
- Thread.currentThread().setName()    设置主线程名字
```java
线程对象.setDaemon(true);
用户进程结束了,守护线程也会结束

alt

解决线程安全的三种方法(重点)

"出现线程安全的前提:多个线程操作共享数据"
1.同步代码块(推荐)
2.同步方法(不推荐)
3.Lock锁(超级推荐)

注意:while(true) 不能放进同步代码中,不然永远只有一个进程在独吞锁对象

1、同步代码块,synchronized 可以修饰方法和类

synchronized(锁对象){
    //操作共享资源的代码
}
(1)    锁对象:
            extends Thread :MyThread.class    或    static修饰的任意对象
            implements Runnable :this
(2)    只有implements Runnable方法才可以用this,因为只new一个Runnable实现类,
    而extends Thread方***new多个Thread对象,此时this锁不唯一
(3)    多个线程必须共用同一把锁

注意:while(true) 不能放进同步代码中,不然永远只有一个进程在独吞锁对象
  • 同步代码块局限性
    其他线程必须挂起等待进入同步代码块的线程
    引起性能问题
    引起优先级倒置

2、同步方法

public synchronized void run(){    
      //同步监视器:this,适合implement Runnable的线程
    //同步代码
      注意:while(true) 不能放进同步代码中,不然永远只有一个进程在独吞锁对象
}

public static synchronized void run(){    //static修饰
      //同步监视器:当前所属类.class,适合extends Thread的线程
    //同步代码
      注意:while(true) 不能放进同步代码中,不然永远只有一个进程在独吞锁对象
}

3、Lock 锁

在实现类Runnable里面定义ReentrantLock锁(最好用static修饰),finally里面解锁

alt

线程通信 synchronized

  • Object的三个线程通信方法 :
    只能出现在synchronized中,并且调用三个方法的必须是同步监视器
    同步监视器.wait() :一旦执行此方法,当前线程进入阻塞状态,并释放同步锁
    同步监视器.notify() :一旦执行此方法,就会唤醒一个被wait的高优先级线程
    同步监视器.notifyAll() :一旦执行此方法,就会唤醒所有被wait的线程

面试题

1、synchronized 和 Lock 的异同

相同 :都可以解决线程安全问题
不同 :synchronized会自动释放锁对象
       Lock需要手动上锁lock() 和解锁 unlock()
使用优先级 :Lock => 同步代码块 => 同步方法

2、sleep() 和 wait() 的异同

相同 :都可以使当前的线程进入阻塞状态
不同 :
    (1) 声明的位置不同:Thread类声明了sleep();Object类声明了wait()。
      (2) 调用的要求不同:sleep()可以在任何场景下调用。
                        wait()必须在synchronized的同步监视器调用。
    (3) sleep()不会释放同步监视器,wait()会释放同步监视器。

3、如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

(1) call()可以有返回值
(2) call()可以抛出异常,被外面的操作捕获,获取异常信息
(3) Callable支持泛型

4、手写线程安全的懒汉式/写一个线程安全的单例模式

package com.gwq.pojo;

public class BankTest{

}

//单例模式:只能有一个实例化的对象,构造方法私有
//懒汉式
class Bank{
    //构造方法私有
    private Bank(){}
    private static Bank instance = null;

    public static Bank getInstance(){
        //外加if(instance == null)是为了提高效率,避免每一次都进入锁内判断
        if(instance == null){
            //线程安全
            synchronized(Bank.class){
                if(instance == null){
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}
#java多线程##学习路径#
全部评论
每次面试都会被问道的问题啊
点赞 回复 分享
发布于 2022-02-21 23:39

相关推荐

03-04 19:02
云南大学 Java
Yki_:没挂,只是没人捞,该干啥干啥,等着就好了
点赞 评论 收藏
分享
评论
2
14
分享

创作者周榜

更多
牛客网
牛客企业服务