java入门多线程一文通

一、面试经典

1.为什么使用多线程及其重要

为了使用户体验更好,服务的相应速度更快。现如今硬件不断发展,软件要求也逐渐提高,都是为了一个字:快。

2.进程、线程、管程(monitor 监视器)

3.多线程并行和并发的区别

①并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行(需要多核CPU)
②并发是指两个任务都请求运行,而处理器只能接收一个任务,就是把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行(秒杀系统)

4.wait和sleep的区别?

wait是Object类中的方法,表示释放该线程执行权,需用Object中的notify|notifyAll唤醒。而sleep只是“阻塞”当前线程指定时间,不释放执行权,是Thread类中的方法。

5.synchronized和lock的区别?

(1). 原始构成
 a. synchronized是关键字属于JVM层面
 monitor对象,每个java对象都自带了一个monitor,需要拿到monitor对象才能做事情
 monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖monitor对象,
 只能在同步块或方法中才能调用wait/notify等方法),进入
 monitorexit:退出
 b. lock是api层面的锁,主要使用ReentrantLock实现
(2). 使用方法
 a. synchronized不需要用户手动释放锁,当synchronized代码完成后系统会自动让线程释放
对锁的占用
 b. ReentrantLock则需要用户手动释放锁若没有主动释放锁,就有可能会导致死锁的现象
(3). 等待是否可中断?
 a. synchronized不可中断,除非抛出异常或者正常运行完成
 b. ReentrantLock可中断
 (设置超时时间tryLock(long timeout,TimeUnit unit),调用interrupt方法中断)
(4). 加锁是否公平
 a. synchronized非公平锁
 b. ReentrantLock两者都可以,默认是非公平锁,构造方法可以传入boolean值,true为公平锁,
 false为非公平锁
(5). 锁绑定多个Condition
 a.synchronized没有
 b.ReentrantLock用来实现分组唤醒需要唤醒线程们,可以精确唤醒,而不是像synchronized要么
 随机唤醒一个\要么多个

6.多线程的实现方式

①基础Tread类

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("我是线程"+Thread.currentThread().getName()+"输出功能:"+i);
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我是线程"+Thread.currentThread().getName()+"输出功能:"+i);
        }
    }
}

②实现Runnable接口

public class RunnableDemo {
    public static void main(String[] args) {
        MyThreadRunnable myThreadRunnable = new MyThreadRunnable();
        Thread t = new Thread(myThreadRunnable);
        t.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("我是线程"+Thread.currentThread().getName()+"输出功能:"+i);
        }
    }

}

class MyThreadRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我是线程"+Thread.currentThread().getName()+"输出功能:"+i);
        }
    }
}

两种实现多线程方式的区别

	(1).查看源码
	a.继承Thread:由于子类重写了Thread类的run(),当调用start()时,直接找子类的run()
	方法
    b.实现Runnable:构造函数中传入了Runnable的引用,成员变量记住了它,start()调用
 run()方法时内部判断成员变量Runnable的引用是否为空,不为空编译时看的是Runnable的run(),
 运行时执行的是子类的run()方法
    (2).继承Thread
    a.好处是:可以直接使用Thread类中的方法,代码简单
    b.弊端是:如果已经有了父类,就不能用这种方法
    (3).实现Runnable接口
    a.好处是:即使自己定义的线程类有了父类也没有关系,因为有了父类可以实现接口,而且接口
可以多现实的
    b.弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,
代码复杂

③Callable接口

(1)Callable接口中的call方法和Runnable接口中的run方法的区别
  • 是否有返回值(Runnable接口没有返回值 Callable接口有返回值)
  • 是否抛异常(Runnable接口不会抛出异常 Callable接口会抛出异常)
(2)Future接口概述
  • FutureTask是Future接口的唯一的实现类
  • FutureTask同时实现了Runnable、Future接口。它既可以作为Runnable被线程执行,又可以作为Futrue得到Callable的返回值
public class ThreadCallDemo {
    public static void main(String[] args) {
        CallThread callThread = new CallThread();
        FutureTask futureTask = new FutureTask(callThread);
        new Thread(futureTask).start();
        try {
            Object sum = futureTask.get();
            System.out.println(sum);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class CallThread implements Callable{

    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            sum += i;
        }
        return sum;
    }
}

(3) 注意
  • get( )方法建议放在最后一行,防止线程阻塞(一旦调用了get( )方法,不管是否计算完成都会阻塞)
  • 一个FutureTask,多个线程调用call( )方法只会调用一次
  • 如果需要调用call方法多次,则需要多个FutureTask

④线程池

此文不做记录,见后续学习笔记。

二、线程基本设置

1.设置线程名

  • void setName(String name):将此线程的名称更改为等于参数 name
  • String getName( ):返回此线程的名称
  • 通过构造函数设置线程名称:
    -Thread(String name):通过带参构造进行赋值/Thread(Runnable target , String name)
  • static Thread currentThread​( )返回对当前正在执行的线程对象的引用
  • 注意:要是类没有继承Thread,不能直接使用getName( ) ;要是没有继承Thread,要通过Thread.currentThread得到当前线程,然后调用getName( )方法
      //FileWriter
      MyThread my1 = new MyThread();
      MyThread my2 = new MyThread();

      //void setName(String name):将此线程的名称更改为等于参数 name
      my1.setName("线程1");
      my2.setName("线程2");
      my1.start();
      my2.start();

2.线程优先级(setPriority)

线程有两种调度模型:

  • 分时调度模式:所有线程轮流使用CPU的使用权,平均分配每个线程占有CPU的时间片
  • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些 [ Java使用的是抢占式调度模型 ]

Thread类中设置和获取线程优先级的方法

  • public final void setPriority(int newPriority):更改此线程的优先级
  • public final int getPriority():返回此线程的优先级
  • a. 线程默认优先级是5;线程优先级范围是:1-10; b. 线程优先级高仅仅表示线程获取的CPU时间的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
    例子:下面不一定是线程2先执行,只是先执行得
      ThreadPriority tp1 = new ThreadPriority();
      ThreadPriority tp2 = new ThreadPriority();
      ThreadPriority tp3 = new ThreadPriority();

      tp1.setName("线程1");
      tp2.setName("线程2");
      tp3.setName("线程3");
      //设置正确的优先级
      tp1.setPriority(5);
      tp2.setPriority(10);
      tp3.setPriority(1);

      tp1.start();
      tp2.start();
      tp3.start();

3.线程控制(sleep、join、setDeamon)

①static void sleep(long millis):使当前正在执行的线程停留(暂停执行)指定的毫秒数 (休眠线程)

② void join():当前线程暂停,等待指定的线程执行结束后,当前线程再继续 (相当于插队加入)

③void join(int millis):可以等待指定的毫秒之后继续 (相当于插队,有固定的时间)

④void yield():让出cpu的执行权(礼让线程)

⑤void setDaemon​(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出(守护线程)(相当于象棋中的帅,要是帅没了,别的棋子都会没用了)

说明:

  • 守护线程是区别于用户线程哈,用户线程即我们手动创建的线程,而守护线程是程序运行的时候在后台提供一种通用服务的线程。垃圾回收线程就是典型的守护线程
  • 守护线程拥有自动结束自己生命周期的特性,非守护线程却没有。如果垃圾回收线程是非守护线程,当JVM 要退出时,由于垃圾回收线程还在运行着,导致程序无法退出,这就很尴尬。这就是为什么垃圾回收线程需要是守护线程
  • t1.setDaemon(true)一定要在start( )方法之前使用

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值