前言
Java多线程程序设计到的知识:
-
对同一个数量进行操作
-
对同一个对象进行操作
-
回调方法使用
-
线程同步,死锁问题
-
线程通信
…
多线程的常见应用场景
- 后台任务,例如:定时向大量(100w以上)的用户发送邮件;
- 异步处理,例如:发微博、记录日志等;
- 分布式计算
多线程使用的主要目的在于
-
吞吐量:你做WEB,容器帮你做了多线程,但是他只能帮你做请求层面的。简单的说,可能就是一个请求一个线程。或多个请求一个线程。如果是单线程,那同时只能处理一个用户的请求。
-
伸缩性:也就是说,你可以通过增加CPU核数来提升性能。如果是单线程,那程序执行到死也就利用了单核,肯定没办法通过增加CPU核数来提升性能。
在java中,每一个线程有一块工作内存区,其中存放着被所有线程共享的主内存中的变量的值的拷贝。当线程执行时,它在自己的工作内存中操作这些变量。
为了存取一个共享的变量,一个线程通常先获取锁定并且清除它的工作内存区,这保证该共享变量从所有线程的共享内存区正确地装入到线程的工作内存区,当线程解锁时保证该工作内存区中变量的值协会到共享内存中。
当一个线程使用某一个变量时,不论程序是否正确地使用线程同步操作,它获取的值一定是由它本身或者其他线程存储到变量中的值。例如,如果两个线程把不同的值或者对象引用存储到同一个共享变量中,那么该变量的值要么是这个线程的,要么是那个线程的,共享变量的值不会是由两个线程的引用值组合而成。
一个变量时Java程序可以存取的一个地址,它不仅包括基本类型变量、引用类型变量,而且还包括数组类型变量。保存在主内存区的变量可以被所有线程共享,但是一个线程存取另一个线程的参数或者局部变量时不可能的,所以开发人员不必担心局部变量的线程安全问题。
业务需求
电影院新片首映,观影人数大量增加,为提高日营业额,线下售票窗口由原单窗口调整为3窗口,设计一段简单的程序模拟该售票过程。
程序设计
多线程场景下需考虑线程安全的问题,避免多个线程争抢同一个资源导致业务逻辑出现错误。实现线程安全的方式有很多,这里使用Java Lock 接口中的方法实现。
代码示例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试类
*/
public class DemoTest {
public static void main(String[] args) {
//窗口01
new Thread(() -> {
while (true) {
//售票并获取售票后的当前票余量
int currentTickets = TicketSource.saleTickets();
//模拟售票员卖出一张票用时1秒
waitProcess();
//票已卖完
if (currentTickets <= 0) break;
}
}, "01").start();
//窗口02
new Thread(() -> {
while (true) {
int currentTickets = TicketSource.saleTickets();
waitProcess();
if (currentTickets <= 0) break;
}
}, "02").start();
//窗口03
new Thread(() -> {
while (true) {
int currentTickets = TicketSource.saleTickets();
waitProcess();
if (currentTickets <= 0) break;
}
}, "03").start();
}
private static void waitProcess() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 电影票资源类
*/
class TicketSource {
//当前电影票余量
private static int currentTickets = 30;
//加锁确保多线程场景下的线程安全
private static final Lock lock = new ReentrantLock();
/**
* 卖票
*
* @return 当前电影票余量
*/
public static int saleTickets() {
lock.lock();
try {
if (currentTickets > 0) {
//模拟卖票
currentTickets--;
if (currentTickets == 0) {
//票余量为 0 停止售卖
System.out.println(
Thread.currentThread().getName() + "窗口出票成功!"
+ "当前票余量:" + currentTickets
+ " 今日票已卖完!");
} else {
System.out.println(
Thread.currentThread().getName() + "窗口出票成功!"
+ "当前票余量:" + currentTickets);
}
} else {
//票余量为 0 停止售卖
System.out.println(Thread.currentThread().getName() + "窗口:今日票已卖完!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return currentTickets;
}
}
运行结果
D:\installPath\Java\jdk1.8.0_121\bin\java.exe "-javaagent:D:\installPath\IntelliJ IDEA 2019.1.4\lib\idea_rt.jar=64339:D:\installPath\IntelliJ IDEA 2019.1.4\bin"
01窗口出票成功!当前票余量:29
02窗口出票成功!当前票余量:28
03窗口出票成功!当前票余量:27
01窗口出票成功!当前票余量:26
03窗口出票成功!当前票余量:25
02窗口出票成功!当前票余量:24
03窗口出票成功!当前票余量:23
01窗口出票成功!当前票余量:22
02窗口出票成功!当前票余量:21
02窗口出票成功!当前票余量:20
03窗口出票成功!当前票余量:19
01窗口出票成功!当前票余量:18
01窗口出票成功!当前票余量:17
02窗口出票成功!当前票余量:16
03窗口出票成功!当前票余量:15
02窗口出票成功!当前票余量:14
01窗口出票成功!当前票余量:13
03窗口出票成功!当前票余量:12
01窗口出票成功!当前票余量:11
03窗口出票成功!当前票余量:10
02窗口出票成功!当前票余量:9
03窗口出票成功!当前票余量:8
02窗口出票成功!当前票余量:7
01窗口出票成功!当前票余量:6
03窗口出票成功!当前票余量:5
02窗口出票成功!当前票余量:4
01窗口出票成功!当前票余量:3
01窗口出票成功!当前票余量:2
03窗口出票成功!当前票余量:1
02窗口出票成功!当前票余量:0 今日票已卖完!
01窗口:今日票已卖完!
03窗口:今日票已卖完!
Process finished with exit code 0
总结
代码块锁是一个防止数据发生错误的一个重要手段。
对象的统一性是非常重要的,这要想到对象的传入问题,
要操作的对象只能new一次,其他的操作都是对这个传入的对象进行的,
才能保证数据一致性,完整性和正确性。