(重点)线程、多线程

线程的生命周期:新建、就绪、运行、阻塞、死亡

1、实现线程是继承Thread类好,还是实现Runnable接口好?

由于类不支持多重继承,如果要继承其他类,最好是实现线程用Runnable

2、Thread类的start()方法和run()方法的区别

start被用来启动新创建的线程,内部调用了run方法,这跟直接调用run方法的效果是不一样的。当你调用run方法的时候,只会在原来的线程中调用,没有新的线程启动,start方法才会启动新线程。

3、Java的Runnable和Callable有什么不同

Callable的call()方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能

4、Java的volatile变量是什么?

volatile是一个特殊的修饰符,只有成员变量才能使用它,保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了这个变量的值,这个新值对于其他线程来说,是立即可见的。

5、Java中notify和notifyAll的区别

notify()方法不能唤醒某个具体的线程,只有当一个线程是等待的时候,它才能用;

notifyAll()方法唤醒所有线程并允许他们争夺锁,这样确保了至少有一个线程是能继续运行的;

5.1、wait()、notify()、notifyAll()方法为什么不在Thread类里面?

这几个方法是Object类的方法。Java提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得;

wait()、notify()、notifyAll()都属于锁级别的操作,所以把它们定义在Object中,因为锁属于对象;

5.2 wait()和notify()方法为什么要在同步块中调用?

Java API强制要求这样做,否则会抛出IllegalMonitorStateException异常,另一个原因是避免wait和notify之间产生竞态条件

5.3 为什么应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。

因此当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间,它可能会改变

6、Java中堆和栈对于线程的作用

每个线程都有自己的栈内存,用于存储本地变量、方法参数、和栈调用。一个线程中存储的变量对其他线程是不可见的;

堆是所有线程共享的公共内存区域,对象都在堆里创建,为了提升效率,线程会从堆里弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,此时volatile变量就可以发挥作用,它要求线程从主存中读取变量的值。

 

7、FutureTask是什么?

在Java并发程序中FutureTask表示一个可以取消的异步运算

它有启动和取消运算查询运算是否完成取回运算结果等方法;

只有当运算完成的时候结果才能取回,若运算未完成,get方法将会阻塞

一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口,所以它可以提交给Executor来执行。

8、Java中interrupted和isInterrupted方法的区别

主要区别就是interrupted()方法会将中断状态清除,而isInterrupted()方法不会

Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()方法来提示线程应该中断了,并设置中断标识为true。当中断线程调用静态方法Thread.interrupted()方法来检查中断状态时,发现中断标识为true则抛出InterruptedException异常,并将中断状态清零。而非静态方法isInterrupted()用来查询其他线程的中断状态且不会改变中断状态标识。

 

9、如何避免死锁

死锁指的是两个或以上进程在执行过程中,因争夺资源而造成的一种相互等待的现象;

死锁发生的四个必要条件:

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

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

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

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

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

如何进行死锁诊断?当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack

jps命令查看运行的线程,找到当前Java程序运行的进程id;

通过jstack来查看这个进程id,就能展示出来死锁的问题,并且,可以定位代码的具体行号范围,我们再去找到对应的代码进行排查就行了
 

10、如何检测一个线程是否拥有锁?

Thread.holdsLock()方法,返回true ,则说明当前线程拥有某个具体对象的锁。

11、Java中synchronized和ReentrantLock有什么不同?

synchronized关键字来实现互斥,但是不能扩展锁之外的方法或块边界,尝试获取锁时不能中途取消;

Java 5提供Lock接口来解决这些问题,ReentrantLock类实现了Lock接口,拥有与synchronized相同的并发性和内存语义,还具有可扩展性:

ReentrantLock是一个可重入锁:,调用 lock 方 法获取了锁之后,再次调用 lock,是不会再阻塞,内部直接增加重入次数 就行了,标识这个线程已经重复获取一把锁而不需要等待锁的释放。
ReentrantLock是属于juc包下的类,属于api层面的锁,跟synchronized一样,都是悲观锁。通过lock()用来获取锁,unlock()释放锁。
它的底层实现原理主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似
构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高。
  • 线程来抢锁后使用cas的方式修改state状态,修改状态成功为1,则让exclusiveOwnerThread属性指向当前线程,获取锁成功
  • 假如修改状态失败,则会进入双向队列中等待,head指向双向队列头部,tail指向双向队列尾部
  • 当exclusiveOwnerThread为null的时候,则会唤醒在双向队列中等待的线程
  • 公平锁则体现在按照先后顺序获取锁,非公平体现在不在排队的线程也可以抢锁  
  • synchronized和Lock有什么区别
    第一,语法层面
    • synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现,退出同步代码块锁会自动释放
    • Lock 是接口,源码由 jdk 提供,用 java 语言实现,需要手动调用 unlock 方法释放锁
    第二,功能层面
    • 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
    • Lock 提供了许多 synchronized 不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量,同时Lock 可以实现不同的场景,如 ReentrantLock, ReentrantReadWriteLock
    第三,性能层面
    • 在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖
    • 在竞争激烈时,Lock 的实现通常会提供更好的性能
    统合来看,需要根据不同的场景来选择不同的锁的使用。
     

12、Java中的ReadWriteLock是什么?

读写锁是用来提升并发程序性能的锁分离技术。

ReadWriteLock维护一对关联锁,一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程持有。写锁是独占的,可使用ReentrantReadWriteLock来实现。

 

13、volatile变量和atomic变量的区别?

volatile变量保证写操作发生在后续的读操作之前,但不能保证原子性

AtomicInteger类提供的atomic方法可以让这种操作具有原子性,如getAndIncrement()方法会原子性的进行增量操作。

 

14、若同步块内的线程抛出异常会发生什么?

会释放锁。(其实无论同步块是正常还是异常退出,线程都会释放锁,在finally块里实现释放锁)

 

15、导致并发程序出现问题的根本原因是什么?

Java并发编程有三大核心特性,分别是原子性、可见性和有序性
首先,原子性指的是一个线程在CPU中的操作是不可暂停也不可中断的,要么执行完成,要么不执行。比如,一些简单的操作如赋值可能是原子的,但复合操作如自增就不是原子的。为了保证原子性,我们可以使用synchronized关键字或JUC里面的Lock来进行加锁。
其次,可见性是指让一个线程对共享变量的修改对另一个线程可见。由于线程可能在自己的工作内存中缓存共享变量的副本,因此一个线程对共享变量的修改可能不会立即反映在其他线程的工作内存中。为了解决这个问题,我们可以使用synchronized关键字、volatile关键字或Lock来确保可见性。
最后,有序性是指处理器为了提高程序运行效率,可能会对输入代码进行优化,导致程序中各个语句的执行先后顺序与代码中的顺序不一致。虽然处理器会保证程序最终执行结果与代码顺序执行的结果一致,但在某些情况下我们可能需要确保特定的执行顺序。为了解决这个问题,我们可以使用volatile关键字来禁止指令重排。

posted on 2020-03-16 17:23  黑子菜园  阅读(105)  评论(0)    收藏  举报

导航