目录

一.定时器的模拟实现

1.Timer的简单使用

2.模拟实现TimerTask

二.常见的锁策略

1.悲观锁和乐观锁

2.重量级锁和轻量级锁

3.挂起等待锁和自旋锁

4.普通互斥锁和读写锁

5.可重入锁和不可重入锁

6.公平锁和非公平锁

三.锁的自适应过程

四.锁消除

五.锁粗化


一.定时器的模拟实现

1.Timer的简单使用

Timer是Java标准库中最基础的定时任务工具,适合简单的定时任务调度。

这里的Timer和线程池一样,其中也包含了前台进程,阻止进程结束。

下面采用匿名内部类的写法,实现Timer

public class Demo11 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask(){
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        },3000);//等待3秒

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 2000");
            }
        },2000);//等待2秒

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 1000");
            }
        },1000);//等待1秒
        System.out.println("hello main");
    }
}

由于赋值给schedule,所有主程序先执行,后面根据等待秒数依次执行

2.模拟实现TimerTask

模拟实现思路:

1.创建一个类,表示一个任务           

2.将定时器中,能够管理多个任务的,使用一个集合类管理起来(采用优先级队列)。               

 3.实现schseule方法,把任务添加到队列中。                       

 4.额外创建一个线程,负责执行队列中的任务。(这里和线程池不同,线程池只需要看队列不为     空,就可以立刻执行,而这里则需要看时间,时间到,执行,时间没到,不执行)。


import java.util.PriorityQueue;
import java.util.Timer;
import java.util.TimerTask;

class MyTimerTask implements Comparable<MyTimerTask> {//实现 Comparable 接口,目的是让任务之间能够比较大小,从而在优先队列中自动排序
    private Runnable task;//实际执行时间
    private long time;

    public MyTimerTask(Runnable task, long time) {
        this.task = task;
        this.time = time;
    }
    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);//比较两数字大小,要让时间最小的放在对首
    }
    public long getTime() {
        return time;
    }
    public void run(){
        task.run();
    }
}

class MyTimer {
    private Object object = new Object();
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();//创建一个优先队列(小根堆)

//实现schedule方法
    private void schedule(Runnable task, long delay) {
synchronized(object){
        MyTimerTask timeTask = new MyTimerTask(task, System.currentTimeMillis() + delay);
//System.currentTimeMillis() 为获取当前时间戳
        queue.offer(timeTask);//
        object.notify();//唤醒后台线程。因为可能新加入的任务比当前堆顶任务更早,需要重新计算等待时间
          }
    }

    public MyTimer() {
        synchronized (object) {
            Thread t = new Thread(() -> {
                try {
                    while (true) {
                        while (queue.isEmpty()) {//如果队列为空,就释放锁并阻塞等待,直到有人调用 schedule() 添加任务并 notify() 唤醒
                            object.wait();
                        }
                        MyTimerTask task = queue.peek();
                        if (System.currentTimeMillis() < task.getTime()) {//计算时间差
                            object.wait(task.getTime() - System.currentTimeMillis());
                        } else {
                            task.run();//执行任务
                            queue.poll();
                        }
                    }

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
    }
}

    public class Demo12 {
        public static void main(String[] args) {
            Timer timer = new Timer();
            timer.schedule(new TimerTask(){
                @Override
                public void run() {
                    System.out.println("hello 3000");
                }
            },3000);

            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("hello 2000");
                }
            },2000);

            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("hello 1000");
                }
            },1000);
            System.out.println("hello main");
        }

    }

定时器的优点:提高效率,精准控制,资源优化,任务调度灵活。

定时器的缺点:依赖系统稳定性,调试复杂,资源竞争风险,时间同步问题。

二.常见的锁策略

1.悲观锁和乐观锁

悲观锁和乐观锁描述的是加锁时候的场景

悲观锁:在加锁的时候,预测接下来发生的锁竞争非常激烈,此时就需要对这样激烈的情况做一些额外的工作。

eg:有一把锁,有20个线程同时竞争,每一个线程加锁的频率都很高,此时一个线程加锁,大概率被另一个线程所占用

乐观锁:在加锁的时候,预测接下来发生的锁竞争不激烈,此时就需要不需要做一些额外的工作。

eg:有一把锁,有2个线程同时竞争,每一个线程加锁的频率都很高,此时一个线程加锁,大概率不会被另一个线程所占用

2.重量级锁和轻量级锁

重量级锁和轻量级锁是遇到场景之后的解决方案

重量级锁:在悲观锁的场景下,此时就要付出更多的代价   --------> 更低效

轻量级锁:在乐观锁的场景下,此时付出的代价会更小  --------->  更高效

3.挂起等待锁和自旋锁

挂起等待锁:是重量级锁的典型实现,是操作系统内核级别的,在加锁的时候如果发生竞争,此时该线程就会进行阻塞状态,后续就需要内核进行唤醒。

它获取锁的周期更长,很难做到及时获取,这个过程不消耗cpu。

自旋锁:是轻量级锁的典型实现,是应用程序级别的,一般不会进行阻塞状态,而是进入忙等

它获取锁定周期更短,可以及时获取到锁,但是这个过程会一直消耗cpu。

4.普通互斥锁和读写锁

普通互斥锁:互斥锁是最简单的锁类型,保证同一时间只有一个线程能访问共享资源(java中常用的就是synchronized)。

读写锁:读写锁区分读操作和写操作,允许多个线程并发读,但写操作独占资源。

在服务开发中一般都是读大于写,因此不能给读操作和写操作都加上锁,这样的话会造成所冲突,因此读写锁是确保读锁和读锁之间不互斥(不会产生阻塞),只让读锁和写锁,写锁和写锁之间产生互斥。

5.可重入锁和不可重入锁

可重入锁:可重入锁允许同一个线程多次获取同一把锁,内部通过计数器记录重入次数。每次获取锁时计数器加1,释放锁时计数器减1,直到计数器归零才真正释放锁。

不可重入锁:不可重入锁不允许同一线程重复获取锁,若线程尝试重复获取,会导致死锁或阻塞。

核心要点:

1.锁要记录当前是哪个线程拿到的这把锁。

2.使用计数器,记录当前加锁了多少次,在合适的位置解锁。

6.公平锁和非公平锁

公平锁:线程获取锁的顺序严格按照请求顺序执行,保证等待时间最长的线程优先获得锁。

非公平锁:允许插队,新请求的线程可能直接获取锁,无需考虑等待队列中的顺序。

三.锁的自适应过程

                           无锁 ——> 偏向锁 ——>自旋锁  ——> 重量级锁

偏向锁的过程:进行synchronized ,刚一上来不是真正的加锁,而是简单标记一下(这个标记很轻,相对于解锁来说,效率要高很多),如果没有其他所前来竞争,最终也就是清除标记,如果有其他线程来竞争,那么就会抢先一步执行。

   无锁 ——> 偏向锁:代码进入synchronized的代码块

偏向锁 ——>自旋锁 :发现有其他线程竞争

自旋锁  ——> 重量级锁:发现竞争激烈

四.锁消除

锁消除是一种编译器优化技术,主要用于消除不必要的同步操作。编译器会判定是否真的需要加锁,如果确实不需要,但是有写了synchronized,就会把synchronized给去掉。

优点

减少不必要的同步开销,提升程序性能。

无需修改代码即可由编译器自动优化。

缺点

依赖编译器的逃逸分析能力,优化效果因场景而异。

无法消除真正需要同步的场景。

五.锁粗化

锁的粒度:加锁与解锁之间,包含的代码越多,就认为锁的粒度越粗,反之就认为锁的粒度越细。

锁粗化就是将搬来执行多次加锁的操作,优化成一次加锁解锁~~

synchronized (obj) {  
    // 操作1  
}  
synchronized (obj) {  
    // 操作2  
}  
// 可能被优化为:  
synchronized (obj) {  
    // 操作1 + 操作2  
}  
 

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐