1)Object中
public class NotifyTest {
public static void main(String[] args) {
byte[] lock = new byte[0];
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
System.out.println("t1 started");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
System.out.println("t2 started");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t3 = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(1000);
System.out.println("t3 notify");
lock.notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.setPriority(1);
t2.setPriority(3);
t3.setPriority(2);
t1.start();
t2.start();
t3.start();
}
}
2)Thread中
public class SleepTest {
public static void main(String[] args) {
final byte[] lock = new byte[0];
new Thread(() -> {
synchronized (lock) {
System.out.println("start");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end");
}
}).start();
new Thread(() -> {
synchronized (lock) {
System.out.println("need lock");
}
}).start();
}
}
新的thread无法异步执行,被迫等待锁,跟着sleep。
public class YieldTest {
public static void main(String[] args) throws InterruptedException {
final byte[] lock = new byte[0];
//让出执行权,但是锁不释放
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("start");
Thread.yield();
System.out.println("end");
}
});
//可以抢t1,但是拿不到锁,白费
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("need lock");
}
});
//不需要锁,可以抢t1的执行权,但是能不能抢得到,不一定
//所以多执行几次,会看到不同的结果……
Thread t3 = new Thread(() -> {
System.out.println("t3 started");
});
t1.start();
t2.start();
t3.start();
}
}
分析:
t3会插队抢到执行权,但是t2不会,因为t2和t1共用一把锁而yield不会释放
t3不见得每次都能抢到。可能t1让出又抢了回去
public class JoinTest implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I am sub");
}
public static void main(String[] args) throws InterruptedException {
Thread sub = new Thread(new JoinTest());
sub.start();
// sub.join();
System.out.println("I am main");
}
}
分析:如果不join,main先跑完;如果join,main必须等待sub之后才输出
1) 现象,互相等待对方释放锁
public class DeadLock {
byte[] lock1 = new byte[0];
byte[] lock2 = new byte[0];
void f1() throws InterruptedException {
synchronized (lock1) {
System.out.println("持有lock1,准备获取lock2");
Thread.sleep(1000);
synchronized (lock2) {
System.out.println("f1");
}
}
}
void f2() throws InterruptedException {
synchronized (lock2) {
System.out.println("持有lock2,准备获取lock1");
Thread.sleep(1000);
synchronized (lock1) {
System.out.println("f2");
}
}
}
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
new Thread(() -> {
try {
deadLock.f1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
deadLock.f2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
2)死锁的条件
互斥使用,即资源只能独享,一个占了其他都必须等。
不可抢占,资源一旦被占,就只能等待占有者主动释放,其他线程抢不走。
贪婪占有,占着一把锁不释放,同时又需要申请另一把。
循环等待,即存在等待环路,A → B → C → A。
3)排查
jdk自带工具
1)概念:一个线程因为 CPU 时间全部被其他线程抢走而始终得不到 CPU 运行时间。
public class HungryThread {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
void write() {
readWriteLock.writeLock().lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
readWriteLock.writeLock().unlock();
}
void read() {
readWriteLock.readLock().lock();
System.out.println("read");
readWriteLock.readLock().unlock();
}
public static void main(String[] args) {
HungryThread hungryThread = new HungryThread();
Thread t1 = new Thread(() -> {
//不停去拿写锁,拿到后sleep一段时间,释放
while (true) {
hungryThread.write();
}
});
Thread t2 = new Thread(() -> {
//不停去拿读锁,虽然是读锁,但是...看下面!
while (true) {
hungryThread.read();
}
});
t1.setPriority(9);
//优先级低!
t2.setPriority(1);
t1.start();
t2.start();
}
}
结果分析:
read几乎不会出现,甚至一直都拿不到锁。处于饥饿状态
StampedLock的乐观读锁
public class StampedThread {
StampedLock lock = new StampedLock();
void write() {
long stamp = lock.writeLock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock(stamp);
}
void read() {
//乐观读
long stamp = lock.tryOptimisticRead();
//判断是否有写在进行,没占用的话,得到执行,打印read
if (lock.validate(stamp)) {
System.out.println("read");
}
}
public static void main(String[] args) {
StampedThread stampedThread = new StampedThread();
Thread t1 = new Thread(() -> {
while (true) {
stampedThread.write();
}
});
Thread t2 = new Thread(() -> {
while (true) {
stampedThread.read();
}
});
t1.setPriority(9);
t2.setPriority(1);
t1.start();
t2.start();
}
}
结果分析:
read间隔性打出,提升了读操作的并发性
注意,StampedLock的使用有局限性!
2) 饥饿线程产生原因
3) 解决饥饿问题的方案
1)定时扫表:写定时任务轮询扫订单表,挨个比对时间,超时的更新掉
具体采取哪种延迟手段,根据企业实际情况,临时性的场合(比如某个抢购活动),可以采用方案一DelayQueue,系统化的订单取消,比如电商系统默认30分钟不支付取消规则,2号方案消息队列居多。
本次实现采用DelayQueue
public class OrderDto implements Delayed {
private int id;
private long invalid;
public OrderDto(int id, long delayTime) {
this.id = id;
this.invalid = delayTime * 1000 + System.currentTimeMillis();
}
public int getId() {
return id;
}
public long getInvalid() {
return invalid;
}
//倒计时,降到0时队列会吐出该任务
@Override
public long getDelay(TimeUnit unit) {
return invalid - System.currentTimeMillis();
}
@Override
public int compareTo(Delayed o) {
OrderDto o1 = (OrderDto) o;
return invalid - o1.getInvalid() > 0 ? 1:-1;
}
}
@Component
public class OrderMonitor {
@Autowired
private OrdersMapper ordersMapper;
//延时队列
final DelayQueue<OrderDto> delayQueue = new DelayQueue<>();
//任务池
ExecutorService service = Executors.newFixedThreadPool(3);
//投放延迟订单
public void put(OrderDto orderDto) {
delayQueue.put(orderDto);
}
// 在构造方法中添加守护线程
public OrderMonitor() {
new Thread(()->{
// 一直从delayQueue中获取超时的订单
while (true) {
try {
OrderDto dto = delayQueue.take();
// 放入线程池执行订单取消操作
service.execute(new Task(dto));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
// 执行取消超时订单
class Task implements Runnable {
private OrderDto orderDto;
public Task(OrderDto orderDto) {
this.orderDto = orderDto;
}
@Override
public void run() {
Orders orders = new Orders();
orders.setId(orderDto.getId());
orders.setUpdatetime(new Date());
orders.setStatus(-1);
ordersMapper.updateByPrimaryKeySelective(orders);
}
}
}
@PostMapping("/add")
@Transactional
@ApiOperation("下订单")
public int add(@RequestParam String name){
Orders order = new Orders();
order.setName(name);
order.setCreatetime(new Date());
order.setUpdatetime(new Date());
//超时时间,取10-20之间的随机数(秒)
order.setInvalid(new Random().nextInt(10)+10);
order.setStatus(0);
mapper.insert(order);
//事务性验证
// int i = 1/0;
monitor.put(new OrderDto(order.getId(),order.getInvalid()));
return order.getId();
}
1)rabbitmq异步排队:使用rabbitmq先排队,请求到来时直接入队,界面显示排队中,消费端逐个消费,同时扣减库存,界面轮询查询结果。可能会出现排队半天抢完的情况。
2)库存预热:使用缓存或内存变量,活动开始前从db中提取库存值初始化,请求到来时直接扣减,及时提醒。可能出现一种感觉,活动刚开始就抢没了……
实际企业秒杀场景下,方案1居多,为学习多线程,本次采用2
@RestController
@RequestMapping("/promotion")
@Api(value = "多线程库存demo")
public class PromotionController {
@Autowired
ProductMapper productMapper;
@Autowired
RabbitTemplate template;
//思考:不用ConcurrentHashMap可以吗?
Map<Integer,AtomicInteger> products = new HashMap();
//热加载数据
@GetMapping("/load")
@ApiOperation(value = "热加载库存")
//bean初始化后,立刻热加载
@PostConstruct
public Map load(){
products.clear();
List<Product> list = productMapper.selectByExample(null);
list.forEach(product -> {
products.put(product.getId(),new AtomicInteger(product.getNum()));
});
return products;
}
}
//抢购
@GetMapping("/go")
@ApiOperation(value = "抢购")
public void go(int productId){
for (int i = 0; i < 10; i++) {
new Thread(()->{
// 每个用户抢到的数量
int count = 0;
long useId = Thread.currentThread().getId();
while (products.get(productId).getAndDecrement()>0) {
count++;
template.convertAndSend("promotion.order",productId+","+useId);
}
System.out.println(Thread.currentThread().getName()+"抢到:"+count);
}).start();
}
}
@Component
@RabbitListener(queues = "promotion.order")
public class PromotionMonitor {
@Autowired
private ProductMapper productMapper;
@Autowired
private FlashorderMapper flashorderMapper;
@RabbitHandler
@Transactional
public void receive(String msg) {
if (msg != null && msg.length()!=0){
System.out.println("get it! "+msg);
String[] ids = msg.split(",");
Flashorder flashorder = new Flashorder();
flashorder.setProductid(Integer.valueOf(ids[0]));
flashorder.setUserid(Integer.valueOf(ids[1]));
//加入抢购订单表
flashorderMapper.insert(flashorder);
//db减库存
productMapper.decr(Integer.valueOf(ids[0]));
}
}
}
1)直接数据库sort,这种最典型
2)redis缓存zset获取,在商品列表缓存,web网站排序场景中常见
3)内存排序,有时候,需要复杂的运算和比较逻辑,sql sort操作表达不出来时,必须进入内存运算
本次使用方案3,规则模拟按价格排序
1)针对内存排序,首先想到的是实现Comparable接口,在多线程知识背景下,可以运用所学的ForkJoin实现归并排序。
2)ForkJoinTask,任务实现算法
public class SortTask extends RecursiveTask<List<Product>> {
private List<Product> list;
public SortTask(List<Product> list){
this.list = list;
}
@Override
//分拆与合并
protected List<Product> compute() {
if (list.size() > 2){
//如果拆分的长度大于2,继续拆
int middle = list.size() / 2 ;
//拆成两个
List<Product> left = list.subList(0,middle);
List<Product> right = list.subList(middle+1,list.size());
//子任务fork出来
SortTask task1 = new SortTask(left);
task1.fork();
SortTask task2 = new SortTask(right);
task2.fork();
//join并返回
return mergeList(task1.join(),task2.join());
}else if (list.size() == 2 && list.get(0).getPrice() > list.get(1).getPrice()){
//如果长度达到2个了,但是顺序不对,交换一下
//其他如果2个且有序,或者1个元素的情况,不需要管他
Product p = list.get(0);
list.set(0,list.get(1));
list.set(1,p);
}
//交换后的返回,这个list已经是每个拆分任务里的有序值了
return list;
}
//归并排序的合并操作,目的是将两个有序的子list合并成一个整体有序的集合
//遍历两个子list,依次取值,两边比较,从小到大放入新list
//注意,left和right是两个有序的list,已经从小到大排好序了
private List<Product> mergeList(List<Product> left,List<Product> right){
if (left == null || right == null) return null;
//合并后的list
List<Product> total = new ArrayList<>(left.size()+right.size());
//list1的下标
int index1 = 0;
//list2的下标
int index2 = 0;
//逐个放入total,所以需要遍历两个size的次数之和
for (int i = 0; i < left.size()+right.size(); i++) {
//如果list1的下标达到最大,说明list1已经都全部放入total
if (index1 == left.size()){
//那就从list2挨个取值,不需要比较直接放入total
total.add(i,right.get(index2++));
continue;
}else if (index2 == right.size()){
//如果list2已经全部放入,那规律一样,取list1
total.add(i,left.get(index1++));
continue;
}
//到这里说明,1和2中还都有元素,那就需要比较,把小的放入total
//list1当前商品的价格
Float p1 = left.get(index1).getPrice();
//list2当前商品的价格
Float p2 = right.get(index2).getPrice();
Product min = null;
//取里面价格小的,取完后,将它的下标增加
if (p1 <= p2){
min = left.get(index1++);
}else{
min = right.get(index2++);
}
//放入total
total.add(min);
}
//这样处理后,total就变为两个子list的所有元素,并且从小到大排好序
System.out.println(total);
System.out.println("------------------");
return total;
}
}
3)调用过程
@RestController
@RequestMapping("/sort")
@Api(value = "多线程排序测试demo")
public class SortController {
@Autowired
ProductMapper mapper;
@GetMapping("/list")
List<Product> sort() throws ExecutionException, InterruptedException {
//查商品列表
List<Product> list = mapper.selectByExample(null);
//线程池
ForkJoinPool pool = new ForkJoinPool(2);
//开始运算,拆分与合并
Future<List<Product>> future = pool.submit(new SortTask(list));
return future.get();
}
}