juc(2)
1.线程的生命周期
线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。
- 新建:就是刚使用new方法,new出来的线程;
- 就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
- 阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
- 销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
那么处于Running状态的线程能发生哪些状态转变?
被转换成Terminated状态,比如调用 stop() 方法;
被转换成Blocked状态,比如调用了sleep, wait 方法被加入 waitSet 中;
被转换成Blocked状态,如进行 IO 阻塞操作,如查询数据库进入阻塞状态;
被转换成Blocked状态,比如获取某个锁的释放,而被加入该锁的阻塞队列中;
该线程的时间片用完,CPU 再次调度,进入Runnable状态;
线程主动调用 yield 方法,让出 CPU 资源,进入Runnable状态
Blocked状态的线程能够发生哪些状态改变?
被转换成Terminated状态,比如调用 stop() 方法,或者是 JVM 意外 Crash;
被转换成Runnable状态,阻塞时间结束,比如读取到了数据库的数据后;
完成了指定时间的休眠,进入到Runnable状态;
正在wait中的线程,被其他线程调用notify/notifyAll方法唤醒,进入到Runnable状态;
线程获取到了想要的锁资源,进入Runnable状态;
线程在阻塞状态下被打断,如其他线程调用了interrupt方法,进入到Runnable状态;
哪些情况进入终止状态
线程正常运行结束,生命周期结束;
线程运行过程中出现意外错误;
JVM 异常结束,所有的线程生命周期均被结束。
2.死锁问题
什么是死锁
死锁就是有两个或者多个进程由于竞争资源而造成阻塞的现象,如果无外力作用,这种局面就会一直持续下去
死锁产生的条件
死锁产生必须满足四个必要条件:
1、互斥条件:指在一段时间内某资源只能由一个进程占用。
1 只有一副钥匙2、请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,且对自己已获得的其它资源保持不放。
1 拿着红钥匙的人在没有归还红钥匙的情况下,又索要蓝钥匙3、不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
1 只要人不主动归还钥匙,就可以一直占着钥匙4、环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链。即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
1 拿着红钥匙的人在等待蓝钥匙,而拿着蓝钥匙的人又在等待红钥匙如何避免死锁
- 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
- 每个进程提出申请资源前必须释放已占有的一切资源(破坏保持条件)
- 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
- 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
编程中的最佳实践:
- 使用 Lock 的 tryLock(long timeout, TimeUnit unit)的方法,设置超时时间,超时可以退出防止死锁
- 尽量使用并发工具类代替加锁
- 尽量降低锁的使用粒度
- 尽量减少同步的代码块
死锁检测工具(了解)
1、Jstack命令
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
2、JConsole工具
Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。
3.单例模式
单例模式含义
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
单例模式主要解决的问题是一个全局使用的类,不会被频繁的创建和销毁,从而提升代码的整体性能。
如何创建单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 饿汉式
优点:简单,线程安全
缺点:不管有没有使用都会占据空间
public class HungrySingleton {
private static final HungrySingleton SINGLETON = new HungrySingleton();
/**
* 单例模式有一个特点,不允许外部直接创建对象,私有构造不让外部实例化
*/
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return SINGLETON;
}
}
懒汉式
优点: 简单
缺点: 线程不安全,要加锁才能解决
public class LazySingleton {
private static LazySingleton singleton;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(singleton == null){
singleton = new LazySingleton()
return singleton;
}
return singleton;
}
}
加锁后的
public static LazySingleton getInstance() {
if (singleton == null) {
synchronized (LazySingleton.class) {
if (singleton == null) {
singleton = new LazySingleton()
return new singleton();
}
}
}
return singleton;
}
枚举(推荐)
优点:线程安全,防止反射和反序列化
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}在单例里面定义一个全局变量或者类变量的,它线程安全的还是线程不安全的
结论:有写操作的话都是线程不安全的
静态变量即类变量,只初始化一次,位于方法区,为所有对象共享,共享一份内存,一旦静态变量被修改,其他对象均对修改可见,故线程非安全。
全局变量即实例成员变量。如果线程只是读取变量的值,而不会改变变量的值,则无论是单例还是非单例都是线程安全的;如果有修改变量值的操作,则单例模式因为只有一个对象实例singleton存在,多线程同时操作时是不安全的,而非单例模式下多线程操作是安全的。
4.怎么解决高并发问题
1.优化代码
2.设置单独的图片服务器,减少访问请求服务器压力
3.使用缓存
4.使用数据库集群
5.DB优化(索引优化,字段类型恰当)
6.实现负载均衡
7.限流