Java 多线程

目录

1. 认识线程

2.创建线程

3.Thread 类及常见方法

3.1

3.2启动一个线程

3.3中断一个线程

3.4等待一个线程

3.5线程休眠:

4. 线程的状态

5.线程通信

wait() 和 notify():

6.小结:

7.相关面试题:

1. 认识线程

进程VS线程

  1. 进程实习是系统分配资源的最小单位;线程是系统调度的最小单位
  2. 一个进程中可以包含多个线程。
  3. 进程的实际执行单位是线程。
  4. 一个进程里面最少包括一个线程,线程的存在必须依托于进程。
  5. 进程不可以资源共享,线程可以。

多线程的优势-增加运行速度

主线程:主要执行业务的线程

子线程:在主线程中创建的线程是子线程

2.创建线程

方法1-继承 Thread

public class ThreadDemo {
    static class MyThread extends Thread{
        @Override
        public void run() {
            //打印当前线程的名称
            System.out.println("子线程名"+Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        //1.继承Thread类
        MyThread myThread =new MyThread();
        //启动线程
        myThread.start();

        System.out.println(Thread.currentThread().getName());
    }
}

缺点:因为Java语言的设计是单继承的,当我继承了Thread类之后,就不能继承其他类

方法2-实现Runnable接口

public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread=new Thread(){
            public void run(){
                System.out.println("当前线程名称"+Thread.currentThread().getName());
            }

        };
        thread.start();
    }
}

方法3-实现Callable 有返回值,重写call

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 创建一个有返回值的线程
 */
public class ThreadDemo {
    // 创建线程
    static class MyCallable implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            // 产生随机数
            int num = new Random().nextInt(10);
            System.out.println(String.format("线程:%s,生产了随机数:%d",
                    Thread.currentThread().getName(), num));
            return num;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1.创建 Callable 子对象
        MyCallable callable = new MyCallable();
        // 2.使用 FutrueTask 接收 Callable
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 3.创建线程并设置任务
        Thread thread = new Thread(futureTask);
        // 执行线程
        thread.start();
        // 得到线程的执行结果
        int num = futureTask.get();
        System.out.println("线程返回结果:" + num);
    }
}

方法4-使用匿名类创建Thread子类对象

// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Thread 子类对象");
   }
};

方法5-使用匿名类创建Runable子类对象

// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Runnable 子类对象");
   }
});

方法6-使用lambda表达式创建Runable子类对象(JDK1.8之后才支持)

// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {
    System.out.println("使用匿名类创建 Thread 子类对象");
});

如果报错或者线程无法启动: 

  1. 点击项目属性,选择Modules->Jdk项目设置为1.8;
  2. 点击file->settings->build->Compiler->Java Compiluer 中将target bytecode version 改为1.8

高频面试题:

1.线程创建的数量是不是越多越好?

   答:不是。

2.创建多少个线程合适?

  答:要看实际情况。

         任务类型:

              1.计算密集型的任务(CPU使用密集型任务)线程数量=CPU核数最好;

              2.读写文件任务 对于读写文件操作,理论上来讲线程数量越多越好。

3.Thread 类及常见方法

3.1

线程优先级默认为:5
线程最小优先级为1 表示(CPU执行权重)权重最低

守护线程是为用户线程服务的

守护线程需要注意的事项:

  1. 守护线程的设置必须放在开启线程(start())之前
  2. 在守护线程中创建的线程默认是守护线程

守护线程的经典使用场景:

  1. Java垃圾回收器
  2. 监控检测任务

3.2启动一个线程

1.run()方法是一个对象的普通方法,它使用的是主线程来执行任务的

2.start()方法是线程的开启方法,他使用新的线程来执行任务

run方法可以多次执行,start方法只能一次

3.3中断一个线程

目前常见的有以下两种方式:

  1. 通过共享的标记来进行沟通
  2. 调用 interrupt() 方法来通知

示例-1

public class ThreadDemo {
   private static class MyRunnable implements Runnable {
       public volatile boolean isQuit = false;
       @Override
       public void run() {
           while (!isQuit) {
               System.out.println(Thread.currentThread().getName()
                       + ": 别管我,我忙着转账呢!");
               try {
                   Thread.sleep(1000);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
           System.out.println(Thread.currentThread().getName()
                   + ": 啊!险些误了大事");
      }
  }
   public static void main(String[] args) throws InterruptedException {        
       MyRunnable target = new MyRunnable();
       Thread thread = new Thread(target, "李四");
       System.out.println(Thread.currentThread().getName()
               + ": 让李四开始转账。");
       thread.start();
       Thread.sleep(10 * 1000);
       System.out.println(Thread.currentThread().getName()
               + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
       target.isQuit = true;
  }
}

示例-2

public class Thread2 {
   private static class MyRunnable implements Runnable {
       @Override
       public void run() {
           while (!Thread.interrupted()) {
           //while (!Thread.currentThread().isInterrupted()) {
               System.out.println(Thread.currentThread().getName()
                       + ": 别管我,我忙着转账呢!");
               try {
                   Thread.sleep(1000);
              } catch (InterruptedException e) {
                   e.printStackTrace();
                   System.out.println(Thread.currentThread().getName()
                           + ": 有内鬼,终止交易!");
                   break;
              }
          }
           System.out.println(Thread.currentThread().getName()
                   + ": 啊!险些误了大事");
      }
  }
   public static void main(String[] args) throws InterruptedException {
       MyRunnable target = new MyRunnable();
       Thread thread = new Thread(target, "李四");
       System.out.println(Thread.currentThread().getName()
               + ": 让李四开始转账。");
       thread.start();
       Thread.sleep(10 * 1000);
       System.out.println(Thread.currentThread().getName()
               + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
       thread.interrupt();
  }
}

重点说明下第二种方法:

1.通过 thread 对象调用 interrupt() 方法通知该线程停止运行

2. thread 收到通知的方式有两种:

         1. 如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式 通知,清除中断标志

         2. 否则,只是内部的一个中断标志被设置,thread 可以通过

               1. Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志

               2. Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

第二种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到

两者的区别:

使用系统的Intrrput()可以及时的终止进程,而使用自定义局部变量的方式,比较温柔,不能立即终止进程

示例-3

public class ThreadDemo {

    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.interrupted());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "t1");
        thread.start();
        thread.interrupt();
    }
}

运行结果:

thread.interrupt() 第一次接收到终止状态是true,之后会将状态复位

示例-4

public class ThreadDemo {

    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().isInterrupted());
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "t1");
        thread.start();
        thread.interrupt();
    }
}

运行结果:

全部是 true,没有复位

3.4等待一个线程-join()

public class Thread2 {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() 
                                       + ": 我还在工作!");
                    Thread.sleep(1000);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
            System.out.println(Thread.currentThread().getName() + ": 我结束了!");
       };
        Thread thread1 = new Thread(target, "李四");
        Thread thread2 = new Thread(target, "王五");
        System.out.println("先让李四开始工作");
        thread1.start();
        thread1.join();
        System.out.println("李四工作结束了,让王五开始工作");
        thread2.start();
        thread2.join();
        System.out.println("王五工作结束了");
   }
}

3.5线程休眠:

方式1:


    public static void main(String[] args) throws InterruptedException {
        String content="你喜欢海风咸咸的气息\n" +
                "踩着湿湿的沙砾\n" +
                "你说人们的骨灰应该撒进海里\n" +
                "你问我死后会去哪里\n" +
                "有没有人爱你";
        for(char item:content.toCharArray()){
            System.out.print(item);
            Thread.sleep(300);
        }

    }

方法2:

    public static void main(String[] args) throws InterruptedException {
       String content="你喜欢海风咸咸的气息\n" +
                "踩着湿湿的沙砾\n" +
                "你说人们的骨灰应该撒进海里\n" +
                "你问我死后会去哪里\n" +
                "有没有人爱你";
        for(char item:content.toCharArray()){
            System.out.print(item);

            TimeUnit.SECONDS.sleep(1);//休眠1秒
            TimeUnit.HOURS.sleep(1);//休眠1小时
        }

    }

方法3:

public static void main(String[] args) throws InterruptedException {
        String content="你喜欢海风咸咸的气息\n" +
                "踩着湿湿的沙砾\n" +
                "你说人们的骨灰应该撒进海里\n" +
                "你问我死后会去哪里\n" +
                "有没有人爱你";
        for(char item:content.toCharArray()){
            System.out.print(item);
            Thread.sleep(TimeUnit.SECONDS.toMillis(1));
        }
    }

4. 线程的状态

线程的状态是一个枚举类型 Thread.State

public class ThreadState {
    public static void main(String[] args) {
        for (Thread.State state : Thread.State.values()) {
            System.out.println(state);
        }
    }
}

运行结果:

NEW:创建了线程但是还没有工作

RUNNABLE:运行

BLOCKED:阻塞

WAITING:等待

TIMED_WAITING:超时等待

TERMINATEO:终止

线程常用方法:yield【用来让出CPU】

     yield 分配执行权不一定成功,要看CPU的选择,但总体来说基本符合预期。

5.线程通信

wait【休眠线程】/ notify【唤醒一个线程】/ notifyAll【唤醒所有的线程】

wait() 和 notify():

 public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1");
        t1.start();
    }

运行结果:

发现会发现报监视器异常,所以在使用时有以下注意事项

  1. 需要在使用 wait() 和 notify() 之前加锁,配合 synchronized 一起使用 
  2. wait() 和 notify() 在配合 synchronized 使用的时候需要使用同一个锁
  3. wait() 和 notify() 需要操作同一把锁
  4. wait() 和 notify() 配合时不能唤醒指定线程
public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    try {
                        System.out.println("wait 之前");
                        lock.wait();
                        System.out.println("wait 之后");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"t1");
        t1.start();
    
        Thread.sleep(500);

        System.out.println("主线程唤醒t1");
        //在主线程中唤醒t1
        //唤醒操作之前加锁
        synchronized (lock) {
            lock.notify();
        }
    }

运行结果:

6.小结:

  1. wait () 的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,当前线程被唤醒(进入就绪状态”)

  2. wait (long timeout) 让当前线程处于超时等待状态直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量,当前线程被唤醒(进入就绪状态”)。当调用wait 不传参数时,其底层也是调用了wait (0)
  3. notify () 和 notifyAll () 的作用,则是唤醒当前对象上的等待线程;notify() 是唤醒单个线程,而 notifyAll() 是唤醒所有的线程。

7.相关面试题:

1.wait() 和 sleep()(面试重点

相似点:

  1. wait 和 sleep 都会使线程进入休眠状态
  2. wait 和 sleep 在执行的过程中都可以接收到终止线程执行的通知

区别:

  1.  wait 必须配合 synchronized 一起使用,wait 执行时会先释放锁,等被唤醒时再重新请求锁。这个锁是 wait 对象上的 monitor lock
  2. sleep 是无视锁的存在的,即之前请求的锁不会释放,没有锁也不会请求
  3.  wait Object 的方法, sleep Thread(线程) 的静态方法
  4. 默认情况下 wait 不传递任何参数,wait 会进入 waiting 状态,sleep 会进入 timed_waiting 
  5. 使用wait 时可以主动唤醒线程,使用 sleep 不能主动唤醒线程,只能等待结束时间之后进行唤醒

2.sleep(0)和 wait(0)的区别:

  1. sleep(0)表示过0毫秒之后继续执行,而wait 一直休眠
  2. sleep(0)表示重新触发一次 CPU 竞争

3.为什么 wait 会释放锁,而 sleep 不会释放锁?

答:sleep 必须传递一个最大等待时间,也就是说sleep在时间层面来讲是可控的,而 wait 是可以不传递参数的,从设计层面来讲,让 wait 这个没有超时等待时间机制不释放锁的话,那么线程可能会一直阻塞,而sleep不存在这个问题。

4.为什么wait 是 Object 的方法,而 sleep 是 Thread 的方法?

答:因为 wait 需要操作锁,而锁是属于对象级别的(所有的锁是放在对象头中),它不是线程级别的,一个线程中可以拥有多把锁的,为了灵活起见,所以就将wait 放在  Object 当中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值