Java多线程
Java多线程
线程与进程
进程是所有线程的集合,每一个线程是进程中的一条执行路径。
多线程应用场景
多线程提高程序效率。
如迅雷多线程下载、分批发送短信等。
多线程创建方式
- 继承Thread类
1 |
|
使用方式:
1 |
|
- 实现Runable接口
1 |
|
使用方式:
1 |
|
- 继承Thread类好还是实现Runable接口好?
接口好,接口可以实现多个,而继承只能继承一个
- 使用匿名内部类方式
1 |
|
注:在run方法中不能抛出异常
线程常用API
方法 | 参数 | 说明 | 示例 |
---|---|---|---|
sleep | 毫秒 | 让当前线程从运行状态变为休眠状态,到时间再变为运行状态。不能释放锁,多线程之间实现同步 | Thread.sleep(1000); |
getId | 获取当前线程id | ||
getName | 获取当前线程名称,在实现runable接口的线程类中使用Thread.currentThread()来调用 | ||
join | 让其他线程等待,只有当前线程执行完毕才会释放资格 |
线程生命周期
线程从创建、运行到结束总是处于五个状态之中:新建状态、就绪状态、阻塞状态、运行状态、死亡状态
新建状态:线程实例化但没有调用start()状态;
就绪状态:线程调用了start()方法,但没有得到cpu分配的资源;
运行状态:线程在执行run()方法;
死亡状态:run()方法运行完;一个未捕获的异常终止了run方法导致线程猝死;
阻塞状态:调用sleep()方法;线程调用一个在I/O上被阻塞的操作;线程试图得到一个锁,而该锁正被其他线程持有;线程在等待某个触发条件;
线程安全
- 为什么会有线程安全问题?
当多个线程同时写一个共享全局变量时,可能会发生数据冲突问题,也就是线程安全问题。
-
线程安全问题怎么解决?
-
synchronized
-
同步代码块:将可能发生线程安全问题的代码,给包括起来(有问题的写操作)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* @description 同步代码块
* @param
* @return void
* @author zhuweitung
* @date 2020/9/5
*/
private void synchronizedCodeBlock() {
while (count > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//synchronized包裹的代码一次只能有一个线程执行
synchronized (lock) {
if (count > 0) {
log.info("threadName={},count={}", Thread.currentThread().getName(), count);
count--;
}
}
}
} -
同步函数:synchronized 关键字修饰在方法上,相当于使用this锁的同步代码块
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/**
* @description 同步函数
* synchronized 关键字修饰在方法上
* @param
* @return void
* @author zhuweitung
* @date 2020/9/5
*/
private synchronized void synchronizedFunction() {
while (count > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("threadName={},count={}", Thread.currentThread().getName(), count);
count--;
}
}
/**
* @description 同步函数相当于使用this锁的同步代码块
* @param
* @return void
* @author zhuweitung
* @date 2020/9/5
*/
private void synchronizedFunction2() {
while (count > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//synchronized包裹的代码一次只能有一个线程执行
synchronized (this) {
if (count > 0) {
log.info("threadName={},count={}", Thread.currentThread().getName(), count);
count--;
}
}
}
} -
静态同步函数:static、synchronized关键字修饰在方法上,相当于使用当前线程类.class锁的同步代码块
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/**
* @description 静态同步函数
* static、synchronized关键字修饰在方法上
* @param
* @return void
* @author zhuweitung
* @date 2020/9/5
*/
private static synchronized void staticSynchronizedFunction() {
while (staticCount > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("threadName={},count={}", Thread.currentThread().getName(), staticCount);
staticCount--;
}
}
/**
* @description 静态同步函数相当于使用当前线程类.class锁的同步代码块
* @param
* @return void
* @author zhuweitung
* @date 2020/9/5
*/
private static void staticSynchronizedFunction2() {
while (staticCount > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//synchronized包裹的代码一次只能有一个线程执行
synchronized (Thread04.class) {
if (staticCount > 0) {
log.info("threadName={},count={}", Thread.currentThread().getName(), staticCount);
staticCount--;
}
}
}
} -
适合场景:单个jvm环境下,集群中不可行
-
-
lock
-
-
多线程死锁:同步中嵌套同步,导致锁无法释放
线程间的通讯
- wait:可以让当前线程由运行状态变为阻塞状态,可以释放锁,与synchronized一起使用,且使用同一个锁
- notify:唤醒另一个线程(由阻塞状态变为运行状态 ),一般和wait一起使用
- Lock接口:配合Condition接口实现等待/通知模式,常用lock()、unlock()方法,常用实现类ReentrantLock
- Condition接口:依赖于Lock对象,常用await()、signal()方法
线程的停止方法:
- 使用退出标志,是线程正常退出,也就是当run方法执行完成后线程终止
- 使用stop()方法强行终止线程(不建议使用stop()方法,因为没有异常处理,可能会出现不可预料的结果)
- 使用interrupt()方法中断线程
守护线程
线程分为两类:用户线程(前台线程)和守护线程(后台线程);
守护线程(deamon)是为我们创建的用户线程提供服务的线程,如jvm的gc等;
- 当用户线程运行结束的时候,daemon线程将会自动退出
线程的三大特性
原子性
即一个或多个操作,要么全部执行并执行的过程不会被任何元素打断,要么就都不执行;
使用同步和lock来确保这个特性。
原子性其实就是保证数据一致,线程安全一部分。
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性
程序执行的顺序按照代码的先后顺序执行。
注:线程安全包括两个方面:可见性,原子性
Java内存模型
共享内存模型就是Java内存模型(简称JMM,JMM决定一个线程对共享变量的写入时,能对另一个线程可见)。线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。
注:与Java内存结构(方法区,堆,栈等)要区分概念
Volatile
作用:变量在多个线程之间可见,强制线程每次读取volatile关键字修饰的变量时都去主内存中取值。
注:不能解决原子性问题,在高并发下i++无法保证原子性,往往会出现问题,所以引入了AtomicInteger类。synchronized不仅保证可见性,而且保证原子性
ThreadLocal
ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,线程之间互不影响。
ThreadLocal底层用Map实现,key:当前线程,value:值。
强引用、软引用、弱引用、虚引用
Java中4种引用的级别由高到低依次为:
强引用 > 软引用 > 弱引用 > 虚引用
他们之间在垃圾回收时的区别:
各引用间的区别:
线程池
为什么使用线程池?
因为启动或停止一个线程非常耗资源,将线程交给线程池来管理可以节约内存。
Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:
- newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收线程,则新建线程
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
- newScheduledThreadPool 创建一个定长的线程池。支持定时及周期性任务执行
- newSingleThreadExecutor 创建一个单线程化线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行