Java–多线程
相关介绍
线程有哪些基本状态
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态,笼统称为运行中 |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态标识当前线程需要等待其他线程做出一些特定的动作(通知或中断) |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,它可以在指定的时间自行返回的 |
TERMINATED | 终止状态,表示当前线程已经执行完毕了 |
wait | 进入等待释放,线程释放持有的锁 | wait()方法返回,线程获取到锁,进入等待队列 |
wait(long) | 进入timeed_waiting状态时,释放当前持有的锁 | wait(long)方法返回,线程获取到锁,进入等待队列 |
notify() | 唤醒一个线程从等待队列进入同步队列中,不会释放当前持有的锁。 | |
notifyAll() | 将等待队列中的所有线程移到同步队列中,不会释放当前持有的锁 | |
Thread.sleep(long) | 进入timeed_waiting状态,不会释放线程持有的锁 | 释放cpu进入等待 |
Thread.yield() | 让当前线程只动放弃执行的时间片(CPU),让其他线程执行,同时当前线程处于runable状态。不会释放持有的锁。 | 释放cpu重新竞争 |
Thread.join(long) | 当线程A执行threadb.join(),只有B线程执行完才会接着执行线程A,如果设置超时时间,规定时间B内完成操作,强制返回并且执行A线程操作 | 释放cpu由指定线程执行,底层使用wait()方法, |
相关问题
说说一下sleep()方法和wait()方法区别和共同点
- 最主要的区别:sleep没有释放锁,wait释放锁
- 两种都可以暂停线程的执行
- wait通常被用于线程之间的交互/通信,sleep通常被用于暂停执行
- wait方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者ontifyAll()方法,sleep方式执行完成后,线程会自动苏醒,或者可以使用wait(long timeout)超时后线程会自动苏醒
说一说对synchroinzed关键字的了解
- 关键字是用来解决多线程之间访问资源的同步性,用synchronized修饰后,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行
- 在早期java中,synchronized属于重量级锁效率低,因为monitor监视器锁是依赖底层os的Mutex lock来实现的,java的线程都是映射到操作系统的原生线程之上,而操作系统的线程之间切换需要从用户态转到内核态,实践成本过高
- 在java6后,在jvm层面对synchronized进行了较大优化,对锁的实现引入了大量的优化,如自旋锁,适应性自旋锁,锁消除,锁粗化,偏向锁,轻量级锁等技术来减少锁操作开销
synchronized怎么用
- 修饰实例方法:对对象实例加锁,进入同步代码前要获得当前对象实例的锁
- 修饰静态方法:给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员(static表明这是该类的一个静态资源,不管new了多少个对象,只有一份)
- 如果一个线程a调用一个实例对象的非静态synchronized方法,而线程b需要调用这个实例对象所属类的静态synchronized方法,是允许迭代,不会互斥。因为访问静态synchronized方法占用的是当前类的锁,而访问非静态synchronized方法占用的锁是当前实例对象的锁
- 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
- 总结:synchronized关键字加到static静态方法和synchronized(class)代码块上都是给class类上锁。synchroinzed关键字加到实例方法上是对象实例上锁。尽量不要使用使用synchronized(String a)因为JVM中,字符串常量池具有缓存功能。
单例模式,解释一下双重检验锁方式实现单例模式的原理?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public 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();
,这段代码其实是分为三步执行:- 为uniqueInstance分配内存空间
- 初始化uniqueInstance
- 将uniqueInstance指定分配的内存地址
但是由于JVM具有指令重排的特性,执行顺序有可能变成1->3->2,指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。线程T1执行了1和3,此时T2调用getUniqueInstance()后发现uniqueInstance不为空,因此返回uniqueInstance,但此时uniqueInstance还没被初始化
祝:使用volatile可以禁止JVM的指令重排,保证在多线程环境下也能正常运行