线程2的深度剖析

news2025/1/6 20:57:27

加锁

synchronized

1.修饰方法(普通方法,静态方法)普通方法实际上加到了this上,静态方法加到了类对象上。

2.修饰代码块   手动指定加到那个对象上

明确锁对象针对那个对象加锁,如果两个线程针对同一个对象加锁,就会出现锁竞争,一个线程先能获取到锁,另一个线程阻塞等待,等待上一个线程解锁,它才能获取锁成功

如果两个线程针对不同对象加锁,就不会产生锁竞争,这两个线程都能获取到各自的锁

如果两个线程,一个线程加锁,另一个线程不加锁,这个时候不会有锁竞争

synchronized修饰方法 

class flg
{
    public static int m=0;
    public synchronized  void add1()
    {
        m++;
    }
}

synchronized修饰代码块

class flg
{
    public static int m=0;
    public  void add2()
    {
        synchronized (this)
        {
            m++;
        }
    }
}

 这两种写法本质上是一样的。

一个加锁另一个不加锁的情况

class flg
{
    public static int m=0;
    public  void add2()
    {
        synchronized (this)
        {
            m++;
        }
    }
    public void add()
    {
        m++;

    }
}

当两个线程分别去调用这两个方法是,实际上相当于没加锁

synchronized的力量是jvm提供的,jvm的力量是操作系统提供的,操作系统的力量是CPU提供的,从根本上说,是CPU提供了这样的指令才能让操作系统的API提供给JVM,JVM提供给synchroniz

d。

3.可重入

一个线程针对同一个对象连续连续加锁两次,如果没问题,就叫可重入,如果有问题就叫不可重入

例如这段代码

 public synchronized void add2()
    {
        synchronized (this)
        {
            a++;
        }
    }

 锁对象是this,只要有线程调用add,进入add方法的时候就会先加锁(能够加锁成功),紧接着又遇到了代码块,再次尝试加锁,这两个线程是同一个线程,如果允许这个操作,这个锁是可重入的,如果不允许这个操作(第二次加锁会阻塞等待),就是不可重入的,这个情况会导致死锁

,java把synchronized设定成可重入的了。

java标准库中的线程安全类

如果多个线程操作同一个集合类,就要考虑到线程安全的问题

Arraylist  、Linkedlist 、HashMap 、TreeMap、HashSet、TreeSet、StringBuilder  这些类在多线程代码中要格外注意

Vector、HashTable、ConcurrentHashMap、StrringBuffer 已经内置synchronized加锁,相对来说更安全一点

死锁

1.一个线程,一把锁,连续加锁两次,如果锁是不可重入锁,就会死锁

2.两个线程两把锁,t1和t2各自针对锁A和锁B加锁,再次尝试获取对方的锁

举个例子小明有一个羽毛球,它对这个羽毛球加锁了,小张有一双羽毛球拍,并对它加锁,小明说小张把你的羽毛球拍借给我用几天,而小张对小明说,你把你的羽毛球借我用几天,这不就僵住了吗?

   Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (qiu)
                {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    synchronized (pai)
                    {
                        System.out.println("小明把球和球拍都拿到了");
                    }

                }

        }

Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (pai)
                {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    synchronized (qiu)
                    {
                        System.out.println("小张把球和球拍都拿到了");
                    }
                }


            }
        });

我们执行这个程序的时候,会发现程序僵住了。

我们借助jconsole这样的工具来进行定位,看线程的状态和调用栈,分析代码在哪里死锁。

我们看到线程1此时是阻塞状态,而阻塞发生在17行

我们再来看一下线程2

此时线程2也是阻塞状态,阻塞发生在38行

这也就反映了当两个线程分对锁A、锁B加锁时,再尝试获取对方的锁时,会死锁。

3.多个线程多把锁

像这里有5个人桌子上是5双筷子,每个人都拿左边的筷子,并对其加锁,我们会发现那个人都吃不了饭,每个人都只有一双筷子。

死锁的必要条件:

1.互斥使用:

线程1拿到锁之后,线程2就得等着。

2.不可占用:

线程1拿到锁之后,必须是线程1主动释放,不能说线程2给强行获取到。

3.请求和保持:

线程1拿到锁A之后,再尝试获取锁B,A这把锁还是保持的(不会因为获取锁B,就把A给释放了)

4.循环等待:

线程1尝试获取到锁A和锁B 线程2尝试获取到锁B和锁A.线程1在获取B的时候等待线程2释放B,线程2在获取A的时候等待线程1释放A.

前三个条件是锁的基本特性,循环等待是这四个条件里唯一一个和代码结构相关的,也是我们可以控制的。

如何打破死锁呢?突破口就是循环等待

办法:给锁编号,然后指定一个固定的顺序(比如从小到大)来加锁,让线程遵守这个顺序,此时循环等待自然破除。

我们给筷子编了个号,然后让每个人都拿两边小的那一个, 然后我们发现e此时就可以拿到1和5这个筷子,那么它就可以吃饭了,当它吃完的时候,1和5放下,此时d也可以拿起5、4吃饭了,然后依次类推,每个人都能吃上饭,这样就破除了死锁。

明白了如何破除死锁我们再来看一下球和球拍那个代码,如果此时我们给这两个线程加锁固定个顺序,先加qiu,再加pai

public static void main(String[] args) {
        Object qiu=new Object();
        Object pai=new Object();
        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (qiu)
                {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    synchronized (pai)
                    {
                        System.out.println("小明把球和球拍都拿到了");
                    }

                }

        }


        });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (qiu)
                {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    synchronized (pai)
                    {
                        System.out.println("小张把球和球拍都拿到了");
                    }
                }


            }
        });
        thread1.start();
        thread2.start();


    }

我们看一下运行结果:

死锁的情况也就破除了。

内存可见性问题:

这个情况就是内存可见性问题,这也是一个线程不安全问题,一个线程读,一个线程改。

这里使用汇编来理解,大概就是这两操作,1.load ,把内存中flag的值,读取到寄存器里 2.cmp  把寄存器中的值,和0进行比较,根据比较结果,决定下一步往那个地方执行。由于load执行速度太慢(相当于cmp)来说,再加上反复load的结果都一样,编译器进行了优化,不再真正的重复load了,判定好像没有人改flag值,干脆只读取一次就好。

内存可见性问题:

一个线程针对一个变量进行读取操作,同时另一个线程对这个变量进行修改,此时读取到的值,不一定是修改之后的值,这个读线程没有感知到变量的变化,归根到底是编译器/jvm在多线程环境下优化时产生了误判。

此时我们给flag这个变量加上volatile关键字,意思就是告诉编译器,这个变量是"易变的",每次都要重新读取这个变量的内存内容,不要就行优化。

上述所说的内存可见性 编译器优化问题,也不是始终会出现的(编译器可能存在误判,但也不是100%就误判)

class MyCounter{
    public volatile int flag=0;
}


public class Mycount {

    public static void main(String[] args) {
        MyCounter myCounter=new MyCounter();

        Thread t1=new Thread(() ->{
            while(myCounter.flag==0)
            {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }


            }
            System.out.println("t1循环结束");

        });
        Thread t2=new Thread(() ->{
            Scanner scanner =new Scanner(System.in);
            System.out.println("请输入一个整数");
            myCounter.flag=scanner.nextInt();

        });
        t1.start();
        t2.start();
    }
}

像这段代码,如果我们在while循环里面加上个sleep,此时编译器就没有优化,while循环会终止,

从JMM角度重新表述内存可见性问题:  java程序里有主内存,每个线程还有自己的工作内存,t1线程进行读取的时候,只是读取了工作内存的值,t2线程进行修改的时候,先修改工作内存的值,然后把工作内存的内容同步到主内存中,但是由于编译器优化,导致t1没有重新从主内存同步数据到工作内存,读到的结果就是修改之前的结果,如果把主内存代替成咱们说的"内存"  把工作内存代替成”CPU"寄存器,工作内存,不只含有CPU的寄存器,还可能有CPU的缓存cache。

CPU读取寄存器,速度比读取内存快很多,因此会在CPU内部引入缓存cache  寄存器存储空间小,读写速度快,但是价格贵,中间搞了个cache,存储空间居中,读写速度居中,成本居中,内存存储空间大,读写速度慢,便宜。当CPU要读取一个内存数据时,可能直接读取内存,也可能是读cache,还可能是读取寄存器。

volatile不保证原子性,原子性是靠synchronized来保证的,synchronized和volatile都能保证线程安全,但是不能使用volatile处理两个线程并发++这样的问题。

wait 和notify

线程最大的问题就是抢占式执行,随机调度,于是我们发明了一些东西来控制线程之间的执行顺序,虽然线程在内核里的调度是随机的,但是可以通过一些cpi,让线程主动阻塞,主动放弃CPU,比如t1t2俩线程,希望t1先干活,干的差不多了,再让t2来干活,就可以让t2先wait(阻塞,主动放cpu),等t1干的差不多了,再通过notify通知t2,把t2唤醒,让t2接着干。

上述场景,使用join或者sleep行不行呢?

使用join,则必须让t1彻底执行完,t2才能运行,如果是希望t1先干%50的活,就让t2开始行动,join也无能为力,使用sleep,指定一个休眠时间,但是t1执行这些活,到底需要多少时间,不好估计。

wait进行阻塞,某个线程调用wait方法,就会进入阻塞,此时就处在WAITING,object.wait(),wait不加任何参数就是死等,一直等到其他线程唤醒它。wait加参数,指定了等待的最大时间

wait的带有等待时间的版本,看起来和sleep有点像,其实还是有本质区别的,虽然都能指定等待时间,虽然也能被提前唤醒(wait时使用notify唤醒,sleep是使用interrupt唤醒),但是notify唤醒wait

不会有异常,interrupt唤醒sleep则是出异常了。

wait,notify,notifyall  这几个方法,都是Object类的方法。

wait sleep区别总结:

1.相同点:都是使线程暂停一段时间

2.wait 是Object类的方法,而sleep是Thread类的方法

3.wait必须在synorchnoized修饰的代码块或方法里使用,而sleep在哪都可以

4.调用wait,线程进行BLOCK状态,调用wait线程会主动释放锁,而线程调用sleep会处于TIMED_WAIT状态,不涉及锁操作.

 public static void main(String[] args) {
        Object lock=new Object();
        Thread t1=new Thread(() -> {
            int i=0;
            for(i=0;i<5;i++)
            {
                ;

            }
            System.out.println("线程1已经执行完,去通知线程2执行");
            synchronized (lock) {
                lock.notify();
            }


        });
        Thread t2=new Thread(() ->{
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程2已经执行完");

        });
        t2.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t1.start();
    }
}

像这段代码,我们在t2里面调用lock.wait()就是让t2线程等待t1让它先干完活,虽然这里阻塞了,阻塞在synchronized代码块里,实际上这里的阻塞是释放了锁的,此时其他线程是可以获取到object这个对象的锁的,此时这里的阻塞,就出在WAITING状态, 当t1干完活后,再调用lock.notify()通知线程2干活,线程2此时重新获取到锁。

我们要注意,启动线程的时候,先让t2先启动,过段时间再让t1先干活,因为如果先启动t1,可能会存在t1线程已经执行完了,而t2线程此时再执行,此时线程2就会一直阻塞下去,t1的notify已经执行完了,也就不起作用了。

      t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t2.start();

像这样线程2就会一直阻塞下去,没法被唤醒。

这里面还需要注意的一个点是wait(),notify()这两个方法需要搭配synchronized使用,为啥呢? 

因为wait操作,先释放锁,进行阻塞等待,收到通知以后,尝试获取锁,并且在获取锁之后,继续往下执行。 

notify和notifyAll区别

多个线程wait的时候,notify随机唤醒一个,notifyAll 所有线程都唤醒,这些线程在一起竞争锁。

三个线程分别只能打印ABC,且保证按照ABC的顺序打印。

public static void main(String[] args) throws InterruptedException {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(() -> {
            System.out.println("A");
            synchronized (locker1) {
                locker1.notify();//通知线程2,唤醒线程2
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker1) {
                try {
                    locker1.wait();//等待线程1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("B");

            synchronized (locker2) {
                locker2.notify();//通知线程3唤醒线程3
            }
        });
        Thread t3 = new Thread(() -> {
            synchronized (locker2) {
                try {
                    locker2.wait();//等待线程2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("C");
        });

        t2.start();//先让t2 t3先启动,防止线程1的notify提前被调用,线程2就无法被唤醒
        t3.start();
        Thread.sleep(100);
        t1.start();
    }

单例模式:单个实例(对象)

在有些场景中,有的特定的类,只能创建一个实例类,不应该创建多个实例,java里实现单例模式有很多种,我们主要介绍两种:(1)饿汉模式 (2)懒汉模式

(1)饿汉模式

public class Signale {
//在此处,先把这个实例创建出来
    public static Signale signale=new Signale();
//如果需要使用这个唯一实例,统一通过Singale.getInstance()
    public static Signale getInstance()
    {
        return signale;
    }
//把构造方法设为Private,在类外面,就无法通过new的方式来创建Singale实例了。
    private Signale()
    {

    }
}

像这里static修饰这个对象,就会保证这个实例是唯一的,保证这个实例在一定时机被创建起来 。

我们用private这个属性,在类外面就无法通过new的方式来创建这个Singale实例了。

类对象本身和static没关系,而是类里面使用static修饰的成员会作为类属性,也就相当于这个属性对应的内存空间在类对象里面

class text123
{
    public int a;
    public static int b;


}


public class Thread5 {
    public static void main(String[] args) {
        text123 t1=new text123();
        text123 t2=new text123();
        //两个实例分别指向两份不同的a
        t1.a=20;
        t2.a=30;
        //被static 修饰的b只有一份,两次都指向同一个b
        text123.b=10;
        text123.b=20;
        System.out.println("t1="+t1.a);
        System.out.println("t2="+t2.a);
        System.out.println(text123.b);
    }

}

类加载:运行一个java程序,就需要让java进程能够找到并读取对应的.class文件就会读取文件内容,并解析并构成类对象,这一系列的过程操作,叫做类加载。

单例模式的懒汉模式实现:

public class Signallazy {
    public static Signallazy signallazy=new Signallazy();
    public Signallazy getInstance()
    {
        if(signallazy==null)
        {
            signallazy=new Signallazy();
        }
        return signallazy;
        
    }
    private Signallazy()
    {
        
    }
}

我们再把饿汉模式拿过来看一下:

public class Signale {
//在此处,先把这个实例创建出来
    public static Signale signale=new Signale();
//如果需要使用这个唯一实例,统一通过Singale.getInstance()
    public static Signale getInstance()
    {
        return signale;
    }
//把构造方法设为Private,在类外面,就无法通过new的方式来创建Singale实例了。
    private Signale()
    {

    }
}

这两个模式哪个是安全的呢?

像饿汉模式我们知道它只涉及读,不涉及修改,那么它应该是安全的。

而懒汉模式我们发现它涉及读和修改两种操作,如果不给它加锁,它是不安全的。

 

如果是一个线程,那么是安全的,但是如果是多个线程,像这里的t2线程就会读到“脏数据",也就是未修改后的值。

那么我们应该加上锁

public class Signallazy {
    public static Signallazy signallazy=new Signallazy();
    public Signallazy getInstance()
    {
        synchronized (Signallazy.class) {
            if (signallazy == null) {
                signallazy = new Signallazy();
            }

          }
       return signallazy;

    }
    private Signallazy()
    {

    }
}

那这样是不是就完美了呢?我们知道加锁操作是有开销的 ,当signallazy一旦不为空时,此是会直接返回signallazy,相当于一个是比较操作,一个是返回操作,这两个都是读操作,而不涉及修改操作,此时就不需要加锁了,因此我们在外边在判断一下signallazy是否为空,是否需要加锁就行

public class Signallazy {
    public static Signallazy signallazy = new Signallazy();

    public Signallazy getInstance() {
        if (signallazy == null) {  //第一个if用来判断是否需要加锁,
            synchronized (Signallazy.class) {
                if (signallazy == null) {//第二个if用来判断是否需要new对象
                    signallazy = new Signallazy();
                }

            }
        }
        return signallazy;
    }
    
    private Signallazy()
    {

    }
}

第一个if语句用来判断是否需要加锁,第二个if语句用来判断是否 需要new对象。

那么代码这样写是不是就安全了吗?我们明白只有第一次读才是读的内存,后面读的都是寄存器和cache,内存可见性问题,另外还会涉及到指令重排序问题。

signallazy=new Signallazy()  拆分成三个步骤:1.申请内存空间  2.在内存空间里构造合法的对象

3.把内存空间的地址赋值给引用signallazy.

如果编译器为了提高效率,调整代码顺序,出现指令重排序的问题正常顺序是1、2、3,这时可能为1、3、2如果是单线程,2和3顺序颠倒不会出现问题,但是如果是多线程,假设线程1按照1、3、2执行,当执行完3,执行2之前,线程1被切除CPU,线程2执行,在线程2看起来此处引用非空,就直接返回了,但是由于t1还没执行2操作,此时t2拿到的是一个非法对象,还没构造完成的不完全对象。

针对内存可见性和指令重排序问题,我们需要用volatile!!!!

 public volatile static Signallazy signallazy = new Signallazy();

用volatile修饰一下signallazy就可以了。

下边这就是单例模式懒汉模式的安全版本了。

public class Signallazy {
    public volatile static Signallazy signallazy = new Signallazy();

    public Signallazy getInstance() {
        if (signallazy == null) {  //第一个if用来判断是否需要加锁,
            synchronized (Signallazy.class) {
                if (signallazy == null) {//第二个if用来判断是否需要new对象
                    signallazy = new Signallazy();
                }

            }
        }
        return signallazy;
    }

    private Signallazy()
    {

    }
}

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/75609.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(1)AWD入门攻略大纲

1.比赛介绍 (1)比赛环境 (2)常见服务器信息介绍 比赛名称 白名单&#xff1a;一般用于防止外部恶意攻击&#xff0c;如果赛方发现名单以外IP可能会进行封禁处理。 服务器账号密码 Token和虚拟IP&#xff1a;token为提交答案的凭证&#xff0c;绑定了队伍&#xff1b;虚拟IP为…

SpringCloud中Feign注解@FeignClient参数一览表

写在前面 Feign是微服务中服务间调用的优选组件&#xff0c;后来的OpenFeign也是基于此来开展的。 为什么要梳理一下Feign注解FeignClient中的各个参数&#xff1f; 踩坑太多面试总问 参数一栏表 FeignClient的源码示例图如下&#xff1a; 今天我们接着来说最后的几个参数。…

Java面试题(六)美团JVM夺命7连问(灵魂拷问)

0.来看一道美团的面试题 这题直接把人给问懵逼了&#xff0c;你能全部答出来吗&#xff1f; Object o new Object();请解释对象的创建过程&#xff1f;DCL要不要加volatile问题&#xff1f;对象在内存中的存储布局&#xff1f;什么是指针压缩&#xff1f;对象头具体包含哪些…

生成树问题汇总

生成树问题汇总注1、最小(大)生成树思路代码例子&#xff1a;1、最小生成树结果是2、最大生成树结果2、在最小生成树中再加一条边&#xff0c;求新的最小生成树思路代码核心代码全部代码例子3、次小生成树思路:在上一个功能基础上进一步扩充代码核心代码全部代码例子4、判断最小…

一个轻量级的分布式日志标记追踪神器,十分钟接入,非常好用!

TLog简介 1、TLog通过对日志打标签完成企业级微服务的日志追踪。它不收集日志&#xff0c;使用简单&#xff0c; 产生全局唯一的追踪码。除了追踪码以外&#xff0c;TLog还支持SpanId和上下游服务信息 标签的追加。 2、为用户使用方便而设计&#xff0c;提供完全零侵入式接入…

es入门(上)

笔记来源于学习 b站中的【IT李老师】的elasticsearch课程 自己在实习做的es模块中的理解。 后续会有 中&#xff0c;下篇笔记更新&#xff0c;目前这一篇是上篇。 目录 Elastic Stack简介 1.1简介 1.2特色 1.3组件介绍 2.Elasticsearch的接收与核心概念 2.1搜索是什么…

【Keras+计算机视觉+Tensorflow】OCR文字识别实战(附源码和数据集 超详细必看)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 一、OCR文字识别简介 利用计算机自动识别字符的技术&#xff0c;是模式识别应用的一个重要领域。人们在生产和生活中&#xff0c;要处理大量的文字、报表和文本。为了减轻人们的劳动&#xff0c;提高处理效率&#xff0c;从…

[python]初步练习脚本

之前练习的python&#xff0c;编写的脚本&#xff0c;现在作为记录&#xff0c;方便查看~ python 初步练习脚本基础部分的练习脚本脚本代码1、helloworld.py&#xff0c;有for循环语句2、main.py3、range—test.py&#xff0c;范围4、RE.py&#xff0c;花式输出内容5、turtle练…

Jekins安装和部署

1.官网下载 注意jekins各版本不同支持jdk的版本也不同 https://www.jenkins.io/download/ 如图进去后可看见最新版&#xff0c;而past releases是历史版本 查看自己各版本的支持 我下载的是2.346.1版本&#xff0c;是war包形式 2.启动jekins 直接在war包路径 java命令启动…

lspci命令整理

1. 作用&#xff1a; 显示当前主机的所有PCI总线信息 2. 常用指令&#xff1a; lspci -nn 第一列的数字&#xff1a;总线编号(Bus Number)&#xff1a;设备编号&#xff08;Device Number&#xff09;&#xff1a;功能编号&#xff08;Function Number&#xff09; 第一个中括…

全国青少年软件编程等级考试C语言标准解读(1_10级)

考试性质 全国青少年软件编程等级考试标准&#xff08;C/C&#xff09;由中国电子学会科普培训与应用推广中心指定。由全国青少年电子信息科普创新联盟标准工作组开发&#xff0c;由中国电子学会普及工作委员会审核通过&#xff0c;适用于由中国电子学会主办的青少年软件编程等…

vue中的process.env的理解

创建项目的时候突然发现好多前端有好多地方用到了这个process.env.xxxx但是发现其实我的新项目并没有定义这个内容&#xff0c;于是就对这个变量产生了好奇&#xff0c;这里总结一下 上图是我在node命令行下执行的查看了一下变量&#xff0c;看这情况直接是把系统的环境变量给…

少走弯路,关于在线客服系统的二三事

日常生活中&#xff0c;我们购买一个服务或一个商品时&#xff0c;时常会遇到以下场景&#xff1a; 售前咨询&#xff1a;向商家咨询服务的信息咨询、商品的规格产品咨询、以及商场活动、优惠咨询等 售后服务&#xff1a;商品使用问题、商品不满意退/换货等 在移动通信没有普…

Camera Surface 从应用到cameraserver的流转

一、Android相机应用与Surface Camera应用的预览一般通过SurfaceView去显示&#xff0c;SurfaceView作为显示的载体&#xff0c; Surface surface mSurfaceView.getSurfaceHolder().getSurface(); 获取的surface会通过Camera API1/API2的接口下发到framework层&#xff1b;…

基于模型的设计(MBD)在汽车ECU软件开发中的实践

基于模型的设计&#xff08;Model-based Design&#xff0c;以下简称MBD&#xff09;是一种围绕模型展开的项目开发方法&#xff0c;指对开发对象或者项目产品进行精确建模&#xff0c;项目的需求分析、功能设计、系统框架、代码生成、测试验证等开发环节都在模型的基础上展开。…

如何用策略模式,优化你代码里的的if-else?

最近有一个学妹在跟我沟通如何有效的去避免代码中一长串的if else判断或者switch条件判断&#xff1f;针对更多的回答就是合理的去使用设计来规避这个问题。 在设计模式中&#xff0c;可以使用工厂模式或者策略模式来处理这类问题&#xff0c;之前已经分享了工厂模式&#xff…

Hadoop集群中HDFS的API测试案例以及MapReduce的多种提交Job方式案例

这两个案例默认是hadoop集群环境已经搭建好以及IDEA环境也已经配置好 1、HDFS客户端测试案例 1.1、pom依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www…

Java使用ftl模板文件生成Word,以及Word转换图片或Pdf工具类

Java使用ftl模板文件生成Word 一、写在前面 最近在项目中使用打印功能&#xff0c;发现这个功能我已经写过多次了&#xff0c;下面这个文章的发步日期在2020年&#xff0c;不得不感慨时间之快啊。 https://blog.csdn.net/weixin_43238452/article/details/109636200?spm1001…

this关键字,是如何把你难倒的?

作为一名实战前端工程师&#xff0c;在jq时代&#xff0c;是经常被this关键字难倒的。几年前每次我意识到程序出现问题的时候&#xff0c;都本能反应是自己的this没有绑定好&#xff0c;于是重新绑定一下&#xff0c;就能解决了。但是他确实一直为难着我。 转眼到了2022年底&a…

图解LeetCode——1780. 判断一个数字是否可以表示成三的幂的和(难度:中等)

一、题目 给你一个整数 n &#xff0c;如果你可以将 n 表示成若干个不同的三的幂之和&#xff0c;请你返回 true &#xff0c;否则请返回 false 。 对于一个整数 y &#xff0c;如果存在整数 x 满足 y 3^x &#xff0c;我们称这个整数 y 是三的幂。 二、示例 2.1> 示例…