java线程

目录

什么是线程?什么是进程?

创建线程的方式和实现:

用Runnable还是Thread? 

Thread 类中的start() 和 run() 方法有什么区别?

wait() 和 sleep()方法有什么不同?

线程的生命周期,线程有那些状态

为什么要用线程池?线程池的创建

阿里开发规范为什么不允许使用Executors

线程池-ThreadPoolExecutor

死锁的产生条件,怎样避免死锁?

线程间通信的方式

1.wait(),notify().

2.CountDownLatch是什么?和wait,notify用法区别。

3.基于volatile关键字

线程同步(堵塞队列)的方法有哪些

同步和异步的区别,什么情况下使用哪个

volatile关键字是什么,有什么作用

现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

在Java中Lock接口相比synchronized块的优势是什么?

什么是自旋锁?


什么是线程?什么是进程?

线程是操作系统能够进行运算调度的最小单位,线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。就好比我们的淘宝软件就是一个进程,而淘宝里面的一个个功能就是线程。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。

创建线程的方式和实现:

  1. 继承 Thread 类(优点: 代码简单 ; 缺点: 该类无法集成别的类);
  2. 实现 Runnable 接口(优点: 继承其他类,同一实现该接口的实例可以共享资源;缺点: 代码复杂) ;
  3. 实现 Callable 接口(优点: 可以获得异步任务的返回值);
  4. 线程池方式(实现自动化装配, 易于管理, 循环利用资源);

用Runnable还是Thread? 

JAVA不支持类的多继承,但是可以实现多个接口,所以如果要继承别的类,就实现Runnable接口,其他情况可以继承Thread类,因为代码简单。

 

Thread 类中的start() run() 方法有什么区别?

通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,Run方法运行结束,此线程随即终止。而run方法只是Thread类中的一个普通方法调用,还是在主线程里执行。

 

wait() 和 sleep()方法有什么不同?

Sleep()方法不会释放锁,仅仅释放cpu资源,不需要主动唤醒。

Wait()会使线程进入等待状态,在被唤醒前它会释放锁。需要主动唤醒。

 

线程的生命周期,线程有那些状态

新建状态(new Thread)  当创建 Thread 类的一个实例(对象)时,此线程进入新建状态(未被启动)。

 例如:Thread t1=new Thread();

就绪状态(runnable)  线程已经被启动, 正在等待被分配给 CPU 时间片, 也就是说此时线程正在就绪队列中排队等候得到 CPU 资源。 例如: t1.start();

运行状态(running)  线程获得 CPU 资源正在执行任务(run()方法), 此时除非此线程自动放弃 CPU 资源或者有优先级更高的线程进入, 线程将一直运行到结束。

死亡状态(dead)  当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行 ;

  • 自然终止:正常运行 run()方法后终止;
  • 异常终止:调用 stop()方法让一个线程终止运行;

堵塞状态(blocked)  由于某种原因导致正在运行的线程让出 CPU 并暂停自己的执行, 即进入堵塞状态。 正在睡眠:用 sleep(long t) 方法可使线程进入睡眠方式。 一个睡眠着的线程 在指定的时间后可进入就绪状态;

正在等待:调用 wait()方法。 (调用 motify()方法回到就绪状态);

 

为什么要用线程池?线程池的创建

创建线程要花费昂贵的资源和时间,如果每次任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。用 Executors 创建线程池。常见的线程池有:

newSingleThreadExecutor()线程池中每次只有一个线程工作,单线程串行执行任务。

newFixedThreadExecutor(n):固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行。

newCacheThreadExecutor():可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。

newScheduledThreadPool():线程池支持定时以及周期性执行任务,创建一个corePoolSize为传入参数,最大线程数为整形的最大数的线程池。

 

阿里开发规范为什么不允许使用Executors

1)newFixedThreadPool和newSingleThreadExecutor:

默认队列使用了 new LinkedBlockingQueue<Runnable>() 理论上可以无限添加任务到线程池。

主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

2)newCachedThreadPool和newScheduledThreadPool:

主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
 

线程池-ThreadPoolExecutor

7个核心参数:

// 参数一:核心线程数量

// 参数二:最大线程数

// 参数三:空闲线程最大存活时间

// 参数四:时间单位

// 参数五:任务队列

// 参数六:创建线程工厂

// 参数七:任务的拒绝策略

拒绝策略:

CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大。

AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。

DiscardPolicy - 直接丢弃,其他啥都没有。

DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入。

 

死锁的产生条件,怎样避免死锁?

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

线程间通信的方式

1.wait(),notify().

这两个方法必须放在synchronized方法中,因为这两方法要求在调用时线程已经获得了对象的锁。

一个线程用notify方法去唤醒一个正在等待wait状态的线程。要注意notify方法不释放锁,即使唤醒了b线程,依旧会先执行完自己的全部代码,才会开始执行b线程。

2.CountDownLatch是什么?和wait,notify用法区别。

CountDownLatch是java.util.concurrent包下的工具类

它有两个核心方法

countDownLatch.countDown();

countDownLatch.await();

比如有三个线程,a线程必须等其他两个线程逻辑全部处理完后a线程才可以运行,在此之前会等待

其他线程处理完成后调用countDown()方法,a线程用await()监听到,然后就会开始运行。整个过程中没有释不释放锁这一回事。而wait,notify方法这两个方法都必须写在synchronized里,直接写会崩,因为wait是个释放锁的方法,只有先锁住了才有锁,还有notify后不是立马释放锁的,synchronized方法块要走完。

3.基于volatile关键字

大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候,线程能够感知并执行相应的业务。

static volatile boolean flag = false。

a线程把flag的值改成true

b线程while true 循环监听flag的值,发现变成true,就开始执行自己的逻辑。

线程同步(堵塞队列)的方法有哪些

1.同步方法

2.同步代码块

3.wait与notify

wait使一个线程处于等待状态,释放lock

notify唤醒一个处于等待状态的线程,如果有多个等待线程,由jvm决定唤醒哪个线程

notifyAll唤醒所有处于等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们去竞争锁资源。

4.volatile关键字

volatile不会提供任何原子操作,也不能用于修饰final变量

5.局部变量ThreadLocal管理变量

同步和异步的区别,什么情况下使用哪个

同步:a线程b线程同时对一个数据进行操作,当b线程准备操作数据时,发现这个资源正在被a使用,同步机制就会让线程b一直等待下去,直到a线程使用完毕,释放资源后,b线程才能去操作这个数据

同步机制能够保证资源的安全,最简单的实现方式是同步方法,同步代码块

异步:每一个线程都包含了运行时自身锁需要的数据,因此在处理时不必关心其他线程的状态或者行为。

比如:用户调用了一个方法,这个方法运行很久才能运行完,我们不希望方法全部运行完才给用户返回结果,就可以先讲用户请求放入消息队列,然后就返回用户结果。然后系统在慢慢去处理。

和交易,钱相关的,比如银行转账,这种就要同步处理。其他不那么重要的,处理时间较长的就可以异步处理。

volatile关键字是什么,有什么作用

一旦一个共享变量被volatile修饰之后,不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。但是不能保证原子性。

现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

thread.Join()把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。 
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。 
join的源码用wait方法实现的。

t.join(); //调用join方法,等待线程t执行完毕 
t.join(1000); //等待 t 线程,等待时间是1000毫秒。

public static void main(String[] args) {
        method01();
        method02();
    }
 
    /**
     * 第一种实现方式,顺序写死在线程代码的内部了,有时候不方便
     */
    private static void method01() {
        Thread t1 = new Thread(new Runnable() {
            @Override public void run() {
                System.out.println("t1 is finished");
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override public void run() {
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2 is finished");
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override public void run() {
                try {
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3 is finished");
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
 
 
    /**
     * 第二种实现方式,线程执行顺序可以在方法中调换
     */
    private static void method02(){
        Runnable runnable = new Runnable() {
            @Override public void run() {
                System.out.println(Thread.currentThread().getName() + "执行完成");
            }
        };
        Thread t1 = new Thread(runnable, "t1");
        Thread t2 = new Thread(runnable, "t2");
        Thread t3 = new Thread(runnable, "t3");
        try {
            t1.start();
            t1.join();
            t2.start();
            t2.join();
            t3.start();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

在Java中Lock接口相比synchronized块的优势是什么?

整体上来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操作。另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。

什么是自旋锁?

当线程A想要获取一把自旋锁而该锁又被其它线程锁持有时,线程A会在一个循环中自旋以检测锁是不是已经可用了。

自旋锁需要注意:

  • 由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这就会浪费CPU时间。
  • 持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值