1.Java 实现线程得方式有几种
继承Thread类 (无返回值)
实现Runnable接口 (无返回值)
实现Callable接口,同时通过FutureTask包装器来创建线程(有返回值)
(所有线程启动必须用start(),run方法是不会创建新线程得,它只是一个普通方法,注意看源码)
2.如何停止一个线程
调用Thread中得stop(),suspend(),resume()(不推荐使用这种方法,这三个方法本身也是过期得,因为是强制停止,所以内存啊,数据啥的都可能有毛病。)
正确应该使用interrupt() 或者 在线程中Boolean判断;
上代码,演示一下几种情况:
第一种:
class Thr1 extends Thread{
@Override
public void run(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上边代码中,除了可以用sleep外还能用wait,join 这些方法都需要捕获InterruptedException(中断异常),此时Thr1线程在运行中可以直接调用 interrupt()方法终端。
第二种
Thread t3 = new Thread(new Runnable() {
public void run() {
while(true) {
System.err.println("这样子写的话就不能直接用interrupt中断了");
//所以这里需要手动判断一下
if(Thread.interrupted()) {
break;
}
}
}
});
线程中没用用到sleep等方法时就需要用到上面这种形式了,或者第三种方法(上面的方法有几点需要注意,中断的3个相关方法是Thread种的方法,其中interrupted() 是静态方法,区别下边再讲)。
第三种
class THrq extends Thread{
//如果考虑多线程,想要保证isboolean的可见性,可以考虑加上volatile()
public boolean isboolean = false;
public void run() {
while(true) {
if(isboolean) {
break;
}
}
}
}
3.notify()和notifyAll()的区别 (需要注意的是wait,notify,notifyAll 这三个方法大都搭配synchronized使用,ReentrantLock 有别的方式)
notify() 和 notifyAll() 都是搭配wait使用的,意在唤醒沉睡的线程。它们的调用对象必须是synchronized对象锁(
注意不是类锁)否则会报错。
notify()唤醒随机一个线程
notifyAll()唤醒所有线程
总结了一下网上靠谱的说法:
锁池和等待池(属于对象锁)
锁池:等待获取锁的线程(调用了notify,notifyAll被唤醒的线程)
等待池:调用了wait方法,处于暂停等待状态的线程
这篇文章讲的比较好可以参考一下:
https://blog.csdn.net/wangmx1993328/article/details/80703971
4
、
sleep()
和
wait() 和join() 和 yieId()
有什么区别?
对于
sleep(),join(),yieId()
方法,我们首先要知道该方法是属于
Thread
类中的。而
wait()
方法,则是属于
Object
类中
的。
sleep():暂停当前线程,不释放已获得的锁,使当前线程进入阻塞状态,最小阻塞时间1毫秒
wait():在其他线程调用同一对象锁的notify或notifyAll方法前,阻塞当前线程,并释放当前锁占有的锁(是指定的锁哦),调用
notify或
notifyAll的线程也必须持有相同的对象锁。
yieId():暂停当前正在执行的线程,使其回到可执行状态,与其他可执行状态的线程共同争夺资源,也就是说调用yieId暂停后可能马上又开始执行。
yieId只能使同优先级或更高优先级的线程有执行机会。
join(): 等待调用线程终止。例如:main主线程中 a线程调用join()方法,则主线程需要等待a线程执行完毕才能继续执行。
5
、
volatile
是什么
?
可以保证有序性吗
?
volatile是一个修饰符,只能用来修饰变量。
volatile修饰的变量能保证其可见性(一个线程修改它,立马别的线程就能看到。)
volatile不是原子性操作。
volatile 禁止进行指令重排,可保证部分有序(例如abc,b是volatile变量,则在编译运行时abc的这个组合的位置不能变动,但是a和c之外的部分它就管不着了。)
使用
Volatile
一般用于 状态标记量 和 单例模式的双检锁
6
、
Thread
类中的
start()
和
run()
方法有什么区别?
不论是run() 还是 call()(这个Callable需要实现的方法) 都只是普通方法,跟线程没毛关系,开启一个线程必须调用start()方法。
7
、为什么
wait, notify
和
notifyAll
这些方法不在
thread
类里面?
我们都知道
wait
相关的3个方法都是用在多线程交互上的,那么他们为什么不是Thread中的方法,而是Object中的方法那,真实原因是因为wait相关方法主要是针对对象锁(对象归Object),暂停和唤醒都是针对锁来的,如果在thread中那它如何识别应该唤醒那些需要指定锁的线程那。
8
、为什么
wait
和
notify
方法要在同步块中调用?
因外wait和notify必须基于锁才能协同运行。(wait可以单独使用不需要放在同步块)
如果不这么做notify编译时不会报错,但运行时会报
IllegalMonitorStateException
避免wait和notify产生竞态条件(多线程同一时间需要同一资源,synchronized独占锁避免这一情况)
9
、
Java
中
interrupted
和
isInterruptedd
方法的区别?
interrupted():获取当前线程的中断状态,并重置为false,
此方法是静态方法,特别注意是当前线程
isInterrupted();获取调用线程的中断状态,不重置,此方法是实例方法。
10
、
Java
中
synchronized
和
ReentrantLock
有什么不同?
synchronized和 reentrantlock都是阻塞型同步。
synchronized:是java得关键字,需要jvm得实现。synchronized 对于代码块和方法得具体做法不同,详细咨询百度。
reentrantlock:是jdk提供得一个api层面得互斥锁,用lock()和unlock()配合使用。可以定时中断线程(
持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于
Synchronized
来说可以避免出现死锁的情况。)
可以实现公平(Reentrantlock lock = new Reentrantlock();)
11
、有三个线程
T1,T2,T3,
如何保证顺序执行?
这里我们可以运用join()(等待指定线程执行完毕)方法实现,join()得底层实现是使用isAlive()判断线程活动状态,用wait来暂停线程。
12
、
SynchronizedMap
和
ConcurrentHashMap
有什么区别?
synchronizedMap 是Colletions为我们提供得一种同步Map,其与HashTable相似,都是用的synchronized全数据加锁,
ConcurrentHashMap 虽然也用到了synchronized,但它不是锁定了全部数据,而是指锁定指定得桶(hashmap是数组加链表得,链表那里也可称为桶),相较于前边两种显然concurrentHashMap效率更高。
13
、什么是线程安全
线程安全就是说多线程访问同一代码,不会产生不确定得结果。
15
、
Java
线程池中
submit()
和
execute()
方法有什么区别?
submit() 可以接收一个Futuer对象得返回值
execute() 不能接收返回值。
16
、
synchronized关键字得三种使用方式?
修饰实例方法:synchronized持有得锁是当前实例对象。
修饰静态方法:synchronized持有得锁是当前类
修饰代码块: 指定加锁对象
synchronized 得锁对象不要使用字符串(字符串常量池有缓存功能,顺便说一下 Integer 127~-128 也有缓存功能,参考Integer.valueof())
17
、
Vector
是一个线程安全类吗?
vector是一个线程安全得类,它的底层数据结构与arraylist相似,但arraylist是线程不安全的
18
、volatile关键字的作用?
volatile关键字只能修饰变量
volatile修饰的变量对线程可见(可见性,a线程修改了,b和c线程能立马看到),但不能保证原子性
volatile不会造成线程阻塞
volatile标记的变量不会被编译器优化,禁止指令重排
20
、常用的线程池有哪些?
newSingleThreadExecutor
:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照
任务的提交顺序执行。
newFixedThreadPool
:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达
到线程池的最大大小。
newCachedThreadPool
:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池
大小完全依赖于操作系统(或者说
JVM
)能够创建的最大线程大小。
newScheduledThreadPool
:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任
务的需求。
newSingleThreadExecutor
:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务
的需求。
不建议直接使用
Executors创建线程池,应该自己使用ThreadPoolExecutor创建(阿里巴巴开发手册上也是这么要求的哦)
21
、简述一下你对线程池的理解
线程池能合理控制线程的数量,避免创建大量线程
线程池能合理复用线程,避免大量线程创建和销毁浪费资源
线程池能提高响应速度,当任务到达时,任务可以不需要的等到线程创建就能立即执行
22,
单例模式了解吗?来给我手写一下!
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
另外,需要注意
uniqueInstance
采用
volatile
关键字修饰也是很有必要。
uniqueInstance
采用
volatile
关键字修饰也是很有必要的,
uniqueInstance = new Singleton();
这段
代码其实是分为三步执行:
1.
为
uniqueInstance
分配内存空间
2.
初始化
uniqueInstance
3.
将
uniqueInstance
指向分配的内存地址
但是由于
JVM
具有指令重排的特性,执行顺序有可能变成
1->3->2
。指令重排在单线程环境下不会出先
问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程
T1
执行了
1
和
3
,
此时
T2
调用
getUniqueInstance()
后发现
uniqueInstance
不为空,因此返回
uniqueInstance
,但此
时
uniqueInstance
还未被初始化。
使用
volatile
可以禁止
JVM
的指令重排,保证在多线程环境下也能正常运行
25
、 讲一下
synchronized
关键字的底层原理
synchronized
关键字底层原理属于
JVM
层面。
通过
JDK
自带的
javap
命令查看
SynchronizedDemo
类的相关字节码信息:首先切换到类的对应目录
执行
javac SynchronizedDemo.java
命令生成编译后的
.class
文件,然后执行
javap
-
c
-
s
-
v
-
l
SynchronizedDemo.class
。
①
synchronized
同步语句块的情况
synchronized
同步语句块的实现使用的是
monitorenter
和
monitorexit
指令,其中
monitorenter
指令指向同步代码块的开始位置,
monitorexit
指令则指明同步代码块的结束位置。
当执行
monitorenter
指令时,线程试图获取锁也就是获取
monitor(monitor
对象存在于每个
Java
对象
的对象头中,
synchronized
锁便是通过这种方式获取锁的,也是为什么
Java
中任意对象可以作为锁的
原因
)
的持有权
.
当计数器为
0
则可以成功获取,获取后将锁计数器设为
1
也就是加
1
。相应的在执行
monitorexit
指令后,将锁计数器设为
0
,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等
待,直到锁被另外一个线程释放为止。
②
synchronized
修饰方法的的情况
synchronized
修饰的方法并没有
monitorenter
指令和
monitorexit
指令,取得代之的确实是
ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法,
JVM
通过该
ACC_SYNCHRONIZED
访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用
26
、 实现
Runnable
接口和
Callable
接口的区别
如果想让线程池执行任务的话需要实现的
Runnable
接口或
Callable
接口。
Runnable
接口或
Callable
接
口实现类都可以被
ThreadPoolExecutor
或
ScheduledThreadPoolExecutor
执行。两者的区别在于
Runnable
接口不会返回结果但是
Callable
接口可以返回结果。
备注:
工具类
Executors
可以实现
Runnable
对象和
Callable
对象之间的相互转换。
(
Executors.callable
(
Runnable task
)
或
Executors.callable
(
Runnable task
,
Object
resule
)
)。