Java——聊聊JUC中的锁(synchronized & Lock & ReentrantLock)

详解JUC包下的锁 相对之下公平锁由于是有序执行,所以相对非公平锁来说执行更慢,吞吐量更小一些。而非公平锁可以在特定场景下实现插队,所以很有可能出现某些线程被频繁插队而导致"线程饥饿"的情况。 阅读详情

文章目录:

1.从乐观锁和悲观锁开始说起

2.synchronized的8锁案例

2.1 第一种情况:两个线程锁的是同一个实例对象

2.2 第二种情况:第一个线程的逻辑中添加sleep睡眠

2.3 第三种情况:第二个线程执行的是无锁方法

2.4 第四种情况:两个线程锁的是两个不同的实例对象

2.5 第五种情况:两个线程锁的是同一个类对象

2.7 第七种情况:一个线程锁实例对象,一个线程锁类对象

3.字节码角度分析synchronized

3.1 synchronized同步代码块

3.2 synchronized同步实例方法

3.3 synchronized同步静态方法

3.4 synchronized锁的是什么?

5.1 可重入锁之隐式锁synchronized

5.2 可重入锁之显式锁Lock

1.从乐观锁和悲观锁开始说起

  • 悲观锁:悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。

悲观锁的实现方式:① synchronized关键字;

② Lock接口的实现类都是悲观锁。

适合写操作多的场景,先加锁可以保证写操作时数据正确。显示的锁定之后再操作同步资源。

public synchronized void method() {
    //加锁之后的业务逻辑
}

Lock lock = new ReentrantLock();

public void method2() {
    lock.lock();
    try {
        //加锁之后的业务逻辑
    } finally {
        lock.unlock();
    }
}
  • 乐观锁:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作

                     乐观锁的实现方式:① 版本号机制Version。 (只要有人提交了就会修改版本号,可以解决ABA问题)

ABA问题:再CAS中想读取一个值A,想把值  A变为C,不能保证读取时的A就是赋值时的A,中间可能有个线程将A变为B再变为A。

解决方法:Juc包提供了一个AtomicStampedReference,原子更新带有版本号的引用类型,通过控制版本值的变化来解决ABA问题。
② 最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

适合读操作多的场景,不加锁的性能特点能够使其操作的性能大幅提升。

AtomicInteger atomicInteger = new AtomicInteger(1);
atomicInteger.incrementAndGet();

2.synchronized的8锁案例

首先,我们可以看一下阿里巴巴Java开发手册中,关于锁的强制性要求。

2.1 第一种情况:两个线程锁的是同一个实例对象

这里我能使用 Lambda 表达式的原因是,Phone类中的这两个实例方法是无参、无返回值的,和Runnable中的run方法一致,所以直接方法引用是OK的。

两个线程锁的都是我 new 的同一个对象 phone,所以当第一个线程去发邮件的时候就拿到了 phone 对象这把锁,此时第二个线程就拿不到了,只能等待第一个线程执行完释放锁,它才可以去发短信。

 

package com.juc.lock;

import java.util.concurrent.TimeUnit;

/**
 *
 */
class Phone {
    public void sendEmail() {
        synchronized (this) {
            System.out.println("-----发送邮件");
        }
    }

    public void sendSMS() {
        synchronized (this) {
            System.out.println("-----发送短信");
        }
    }
}

public class Lock8 {

    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(phone::sendEmail, "a").start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phone::sendSMS, "b").start();
    }
}

 

 

2.2 第二种情况:第一个线程的逻辑中添加sleep睡眠

和第一种情况不同的是:当第一个线程拿到 phone 对象锁之后,在发邮件的过程中,sleep睡眠了2秒。但是执行结果和第一种情况是一样的。

原因就是 sleep 方法并不会释放锁,只是让线程暂定一段时间,一段时间过后线程照常执行(不要interrupt打断。。。)。

某一个时刻内,只能有唯一的一个线程去访问这些针对于实例对象的synchronized方法, 锁的是当前对象this ,被锁定后,其它的线程都不能 进入到当前对象的其他synchronized方法。

package com.juc.lock;

import java.util.concurrent.TimeUnit;

/**
 *
 */
class Phone {
    public void sendEmail() {
        synchronized (this) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("-----发送邮件");
        }
    }

    public void sendSMS() {
        synchronized (this) {
            System.out.println("-----发送短信");
        }
    }
}

public class Lock8 {

    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(phone::sendEmail, "a").start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phone::sendSMS, "b").start();
    }
}

 

2.3 第三种情况:第二个线程执行的是无锁方法

这种情况,虽然第一个线程拿到 phone 对象锁去发邮件了,中间睡了2秒不会释放锁对象。但是第二个线程的任务是 hello,这个方法并没有任何锁机制,它并不会和synchronized修饰的同步方法、代码块发生争抢,所以两个线程你干你的、我干我的。   由于线程a睡眠了,所以这里线程b就先执行完毕。

package com.juc.lock;

import java.util.concurrent.TimeUnit;

/**
 *
 */
class Phone {
    public void sendEmail() {
        synchronized (this) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("-----发送邮件");
        }
    }

    public void sendSMS() {
        synchronized (this) {
            System.out.println("-----发送短信");
        }
    }

    public void hello() {
        System.out.println("-----hello");
    }
}

public class Lock8 {

    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(phone::sendEmail, "a").start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phone::hello, "b").start();
    }
}

 

2.4 第四种情况:两个线程锁的是两个不同的实例对象

两个线程锁的实例对象不同,线程a锁了phone,线程b锁了phone2,那么这里就算执行的是 synchronized 同步方法、代码块,也互不干扰,因为这是两把锁。

又因为线程a中间睡了2秒,所以线程b先执行完。

package com.juc.lock;

import java.util.concurrent.TimeUnit;

/**
 *
 */
class Phone {
    public void sendEmail() {
        synchronized (this) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("-----发送邮件");
        }
    }

    public void sendSMS() {
        synchronized (this) {
            System.out.println("-----发送短信");
        }
    }

    public void hello() {
        System.out.println("-----hello");
    }
}

public class Lock8 {

    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(phone::sendEmail, "a").start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phone2::sendSMS, "b").start();
    }
}

 

2.5 第五种情况:两个线程锁的是同一个类对象

这里将上面的实例方法修改成了静态方法,一经修改,那么这两个方法就是类级别了,synchronized锁的是 Phone 这个类,那么无论开了多少个线程,当第一个线程获得类锁之后,其他的线程都无法再拿到这个类锁。

 

package com.juc.lock;

import java.util.concurrent.TimeUnit;

/**
 *
 */
class Phone {
    public static void sendEmail() {
        synchronized (Phone.class) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("-----发送邮件");
        }
    }

    public static void sendSMS() {
        synchronized (Phone.class) {
            System.out.println("-----发送短信");
        }
    }

    public void hello() {
        System.out.println("-----hello");
    }
}

public class Lock8 {

    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(Phone::sendEmail, "a").start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(Phone::sendSMS, "b").start();
    }
}

2.6 第六种情况

和第五种情况的区别是:声明了两个Phone对象(phone、phone2),但是原理和第五种情况是一样的,因为这里是类锁。

2.7 第七种情况:一个线程锁实例对象,一个线程锁类对象

两个线程锁的对象不同,第一个线程锁的类对象,去发邮件,中间睡了2秒,执行稍慢;第二个线程锁的phone实例对象,去发短信;二者是互不干扰的,你干你的,我干我的。  由于线程a睡眠了,所以线程b先完成执行。

package com.juc.lock;

import java.util.concurrent.TimeUnit;

/**
 *
 */
class Phone {
    public static void sendEmail() {
        synchronized (Phone.class) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("-----发送邮件");
        }
    }

    public void sendSMS() {
        synchronized (this) {
            System.out.println("-----发送短信");
        }
    }

    public void hello() {
        System.out.println("-----hello");
    }
}

public class Lock8 {

    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(Phone::sendEmail, "a").start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(phone::sendSMS, "b").start();
    }
}

 

2.8 第八种情况

和第七种情况的区别是:Phone对象声明了2个,但是和第七种情况的原理仍然一样,一个锁了类对象、一个锁了实例对象,二者不产生竞争条件。

3.字节码角度分析synchronized

先介绍两个东西:

文件反编译技巧

  • 文件反编译 javap -c ***.class 文件反编译,-c表示对代码进行反汇编

  • 假如需要更多信息  javap -v ***.class ,-v即-verbose输出附加信息(包括行号、本地变量表、反汇编等详细信息)

3.1 synchronized同步代码块

以下面这段代码为例,在IDEA中编译运行之后,会生成 .class 字节码文件,我们找到它所在的目录,cmd打开。

package com.juc.lock;

/**
 *
 */
public class LockSyncDemo {

    Object object = new Object();

    public void method1() {
        synchronized (object) {
            System.out.println("----- synchronized code block");
        }
    }

    public static void main(String[] args) {

    }
}

 

synchronized同步代码块,实现使用的是 moniterenter 和 moniterexit 指令(moniterexit可能有两个,是因为程序要完完全全的确保你能够释放掉占有的锁对象,可能第一次 exit 没有释放掉,你的程序中有一些错误什么的,所以在后面还会有第二个 exit),底层实际上就是靠 这两个指令来确保锁的获取和释放。

那一定是一个enter两个exit吗?(不一样,如果主动throw一个RuntimeException,发现一个enter,一个exit,还有两个athrow)

3.2 synchronized同步实例方法

package com.juc.lock;

/**
 *
 */
public class LockSyncDemo {

    public synchronized void m2() {
        System.out.println("------hello synchronized m2");
    }

    public static void main(String[] args) {

    }
}

使用  javap -v LockSyncDemo.class,更详细的查看字节码文件。

调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。如果设置了,执行线程会将先持有monitor锁,然后再执行方法,最后在方法完成无论是正常完成还是非正常完成)时释放monitor。

3.3 synchronized同步静态方法

package com.juc.lock;

/**
 *
 */
public class LockSyncDemo {

    public static synchronized void m2() {
        System.out.println("------hello synchronized m2");
    }

    public static void main(String[] args) {

    }
}

所以它这里就是通过这两个东西去判断的,如果同时具备了 ACC_STATIC、ACC_SYNCHRONIZED,那就是类锁,只具备 ACC_SYNCHRONIZED,那就是对象锁。

3.4 synchronized锁的是什么?

管程:Monitor(监视器),也就是我们平时说的锁。监视器锁

信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。 管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。

执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管理。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。

4.公平锁和非公平锁

非公平锁:默认是非公平锁。非公平锁可以插队,买卖票不均匀。

是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或饥饿的状态(某个线程一直得不到锁)

公平锁:ReentrantLock lock = new ReentrantLock(true);     是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,                      这是公平的。

为什么会有公平锁/非公平锁的设计?为什么默认是非公平?

  • 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。
  • 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

什么时候用公平?什么时候用非公平?

  •  如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;否则那就用公平锁,大家公平使用。

来看下面的代码案例。

 

package com.juc.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 资源类,模拟3个售票员卖完50张票
 */
class Ticket {
    private int number = 50;

    Lock lock = new ReentrantLock(); //默认就是非公平锁

    public void sale() {
        lock.lock();
        try {
            if(number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出第:\t" + (number--) + "\t 还剩下: " + number);
            }
        } finally {
            lock.unlock();
        }
    }
}

public class SaleTicketDemo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(() -> {
            for (int i = 0; i <55; i++) {
                ticket.sale();
            }
        },"a").start();
        new Thread(() -> {
            for (int i = 0; i <55; i++) {
                ticket.sale();
            }
        },"b").start();
        new Thread(() -> {
            for (int i = 0; i <55; i++) {
                ticket.sale();
            }
        },"c").start();
    }
}

 

下面的执行结果,没有截完整,最后全部都是线程b卖的票,这里就可以看到,严重的非公平锁了,压根就没有线程c的事。

将上述代码修改为公平锁是很简单的,在 ReentrantLock 的构造方法中传入一个布尔值 true就可以了。

至于原因,我在后面会为大家讲解,就是AQS!!!

Lock lock = new ReentrantLock(true); //公平锁

 

5.可重入锁

可重入锁又名递归锁:是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。

如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

  • 可:可以

  • 重:再次

  • 入:进入

  • 锁:同步锁

  • 进入什么:进入同步域(即同步代码块/方法或显示锁锁定的代码)

  • 一句话:一个线程中的多个流程可以获取同一把锁,持有这把锁可以再次进入。自己可以获取自己的内部锁。

5.1 可重入锁之隐式锁synchronized

指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。简单的来说就是:在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的。

5.1.1 针对同步代码块

package com.juc.lock;

/**
 *
 */
public class ReEntryLockDemo {
    public static void main(String[] args) {
        final Object obj = new Object();

        new Thread(() -> {
            synchronized (obj) {
                System.out.println(Thread.currentThread().getName() + " 外层调用....");
                synchronized (obj) {
                    System.out.println(Thread.currentThread().getName() + " 中层调用....");
                    synchronized (obj) {
                        System.out.println(Thread.currentThread().getName() + " 内层调用....");
                    }
                }
            }
        }, "t1").start();
    }
}

 

5.1.2 针对同步方法

package com.juc.lock;

/**
 *
 */
public class ReEntryLockDemo {
    public synchronized void m1() {
        System.out.println(Thread.currentThread().getName() + " --- come in");
        m2();
        System.out.println(Thread.currentThread().getName() + " --- end");
    }

    public synchronized void m2() {
        System.out.println(Thread.currentThread().getName() + " --- come in");
        m3();
    }

    public synchronized void m3() {
        System.out.println(Thread.currentThread().getName() + " --- come in");
    }

    public static void main(String[] args) {
        ReEntryLockDemo obj = new ReEntryLockDemo();
        new Thread(obj::m1, "t1").start();
    }
}

 

针对上面两个案例,为什么可以这样重入锁呢?

  • 首次加锁:当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
  • 重入:在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
  • 释放锁:当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

5.2 可重入锁之显式锁Lock

package com.juc.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 */
public class ReEntryLockDemo {
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " --- 外层调用");
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " --- 内层调用");
                } finally {
                    lock.unlock();
                }
            } finally {
                lock.unlock();
            }
        }, "t1").start();
    }
}

 

上面的案例中,切记:你lock了几次,对应的就要 unlock 几次,否则线程的获取锁、释放锁次数是无法对应的,程序就炸了。

6.死锁及排查

死锁:是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。   a跟b两个资源互相请求对方的资源。

死锁产生的原因:系统资源不足;进程运行推进的顺序不合适;资源分配不当。

package com.juc.lock;

import java.util.concurrent.TimeUnit;

/**
 *
 */
public class DeadLockDemo {
    public static void main(String[] args) {
        final Object objectA = new Object();
        final Object objectB = new Object();

        new Thread(() -> {
            synchronized (objectA) {
                System.out.println(Thread.currentThread().getName() + " --- 获取到了A锁,希望获取B锁");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (objectB) {
                    System.out.println(Thread.currentThread().getName() + " --- 获取到了B锁");;
                }
            }
        }, "A").start();

        new Thread(() -> {
            synchronized (objectB) {
                System.out.println(Thread.currentThread().getName() + " --- 获取到了B锁,希望获取A锁");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (objectA) {
                    System.out.println(Thread.currentThread().getName() + " --- 获取到了A锁");
                }
            }
        }, "B").start();
    }
}

并不是说我们的程序出现了上图中的小红方块,没有结束就一定是死锁,我也可以写一个  while(true)  死循环卡在这里。

所以我们需要对是否是死锁进行排查。

  • jps -l 查看当前进程运行状况

  • jstack 进程编号 查看该进程信息

或者我们可以  win + R,输入 jconsole。

7.总结

这篇文章是关于锁的,是入门级别的文章,后续还会有更深入、更底层的JUC,我会慢慢更新的。。。

 

JUC 常见的锁 目录一、AtomicInteger1、底层实现(1) 如果在执行CAS操作的时候,在判断值是否为我期望的值后,马上有其他线程把这个值改为了其他的值,那这种情况不是依然有问题吗?(2)ABA问题(3)CAS底层是怎么做到的?2、SyncLong VS AtomicLong VS LongAdder二、ReentrantLock1、公平锁与非公平锁2、reentrantlock VS synchronized三、CountDownLatch(倒数 门闩)四、CyclicBarrier (循环 栅栏)五、Phas 阅读详情

相关推荐

Java - JUC(java.util.concurrent)包详解,其下的锁、安全集合类、线程池相关、线程创建相关和线程辅助类、阻塞队列

JUC是什么? Java中线程有六个状态 JUC的结构 synchronized和JUC的Lock   reentrantLock   Condition   JUC下的读写锁ReentrantReadWriteLock 不安全的集合类和JUC的collections   list 不安全-CpoyOnWriteArrayList   set 不安全-CopyOnWriteArraySet   HashMap不安全-ConcurrentHashMap JUC的Callab

m0_65228708的博客 2389

java--JUC快速入门(彻底搞懂JUC)

线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

qq_53808097的博客 4161

Java并发编程:深入解析CAS与自旋锁原理、应用与陷阱

在并发编程领域,线程安全是核心挑战之一。传统锁机制通过阻塞线程实现同步,但会带来显著的性能开销。为解决此问题,硬件级别的原子操作——CAS(Compare-And-Swap)应运而生,它通过比较并交换的原子指令,实现了无锁(Lock-Free)的线程安全更新。基于CAS构建的自旋锁(SpinLock)则采用忙等待策略,避免了线程上下文切换,在锁持有时间极短的场景下性能优势显著。这种无锁同步技术是构建高性能并发数据结构(如无锁队列)的基石,广泛应用于计数器、状态标志等场景。然而,CAS操作存在经典的ABA问题

weixin_34221276的博客 395

Java - JUC详解

一、概述 1.1 什么是JUC? JUC是java.util.concurrent包的简称,在Java5.0添加,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题! 1.2 什么是进程? 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还要用到磁盘、网络等设备。进程就是用来加载指令,管理内存管理IO的。 当一个进程被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。 进程就可以视为一个实例。

S mile0804的博客 6789

Verilog数字钟

使用Verilog语言进行的数字钟的编写,FPGA开发板亲测正常 大学生一个,第一次自己做的东西,写出来分享在这里插入代码片一下,有不对的地方请大家批评指正。 这次的做的题是:设计一个数字钟,该数字钟能进行正常的时,分计时功能,且能对数字钟的时间进行调整。 该数字钟有3个按键,按键1为功能键,用于数字钟在“正常计时”,“时调整”,“分调整”这三个功能之间切换;按键2,3用于调整时间,仅当数字钟处于...

bin_bin_bun的博客 6149

Social-Auto-Upload实战:如何用Python+Playwright实现抖音/B站/小红书一键三连发?

本文详细介绍了如何使用Python和Playwright框架构建Social-Auto-Upload自动化上传工具,实现抖音、B站、小红书等平台的一键多发布。通过模拟真实用户操作和Cookie管理技术,该工具能有效解决内容创作者跨平台发布的重复劳动问题,提升工作效率。

day7的博客 1401

JUC_锁是什么,Synchronized与Lock的区别

多个线程在操作一个资源类的时候,因为操作时间大致相同,异步执行,会造成数据的写入异常。锁就是解决这个问题的,锁的本质就是队列 我举个例子,你去吃饭,但不排队,都不排队,很多学生(多线程)一拥而上一个窗口(资源类),食堂大妈也不知道谁要的什么菜,就听到谁喊要什么菜,就打这个菜(线程不安全) 加锁就是指在窗口设置了一个拦截线,只有排队(队列)才能打饭,这样就保证了每个人打的菜才是自己想要的(线程安全)

我认不到你的博客 376

倒立摆入门详解+pid调参

倒立摆制作全过程 最近在练习pid的控制算法,就选用了2013年的电子设计国赛题目:倒立摆 。 首先给你们看看我的成果 注意哦:倒立摆不是靠上面黄色的传感器保持平衡的,而是通过下面的电机左右扭动使得摆杆不掉下来,难度可不小了。 材料准备 1.机械部分 如图所示选好电机和角位移传感器进行组装就好了,这个部分是我直接网上购买的,如果要自己装也是可以的,不过会比较麻烦,结构差不多的。 2.主控部分 这个部分的话完全是我自己设计的,作用和网上买的一样,因为网上买的话太贵了,有需要的话可以联系我,有pcb文件

small_po_kid的博客 2万+

Java多线程核心原理与实战:从synchronized到JUC并发工具深度解析

在并发编程领域,线程安全是保障程序正确性的基石。其核心原理源于Java内存模型(JMM),它定义了主内存与工作内存的交互规则,是理解可见性、有序性等问题的理论根源。为实现线程安全,Java提供了从语言原语到高级框架的多层次解决方案。synchronized关键字通过对象头与锁升级机制(无锁、偏向锁、轻量级锁、重量级锁),在保证原子性的同时兼顾性能优化。而volatile关键字则通过内存屏障确保变量的可见性与有序性,是轻量级同步的典型代表。在工程实践中,为应对复杂并发场景,Java并发工具包(JUC)提供了更

weixin_33888907的博客 526

线上专利申请(超详细)| 签名确认

在【法人登录】页面点击【证书登录】按钮,弹出证书登录提示框,点击【确 认】按钮,进入到输入证书密码页面,点击【取消】按钮,取消本次操作。企业在提交申请时,需要增加签名,如使用企业账户点击提交,会弹出“法人、代理机构不能签名,请使用经办人签名”,这个时候就需要换个账号了。在需要签名时,将专利官网的登录方式点击法人登录,选择扫码登录,使用移动端经办人账户的扫一扫功能扫描登录。登录完成后,再点击签名,使用移动端扫一扫功能,扫描签名,输入证书号,确认签名,并提交。最后,祝大家的申请都成功!

weixin_62112323的博客 9111

知识图谱中的 Ontology(本体论)

本文的部分图片和内容来自中国科学院自动化研究所刘康老师的课件 Ontology(本体论) Ontology 是(特定领域)信息组织的一种形式,是领域知识规范的抽象和描述,是表达、共享、重用知识的方法。 Ontology 是知识体系构建的关键技术,知识图谱是一种人工智能技术,它的关键在于让计算机能够处理人类的知识。然而,人类脑海中的知识通常是直觉性的,我们无法将这种直觉性的知识直接输入给计算机,On...

P01son的博客 2万+

Java多线程等待唤醒机制:从synchronized到Lock+Condition

今天来聊聊Java多线程里一个超级经典的话题——等待唤醒机制。在多线程编程里,我们经常遇到“线程协作”的场景:比如一个线程生产数据,另一个线程消费数据;或者两个线程需要严格交替执行(像乒乓球一样你一下我一下)。这时候就需要“等待唤醒”:条件不满足时线程自己睡一觉,等条件好了再被叫醒继续干活。今天重点讲,因为它更灵活、更高效,是现代并发编程的主流(Java并发包里的阻塞队列都是用这个实现的)。我们会用一个简单例子——两个线程交替打印0~9——来一步步讲解。

2401_86718044的博客 759

R语言深度学习实战:基于YOLO的目标检测

目标检测是计算机视觉领域的一项任务,旨在识别图像或视频中的物体,并确定它们的位置。与图像分类不同,目标检测要求模型能够识别多个物体,并为每个物体提供边界框(bounding box)的位置信息。这种任务在众多应用中都具有重要价值,例如自动驾驶、安防监控、物体跟踪等。YOLO(You Only Look Once)是一种非常流行的目标检测算法,它在速度和准确性之间取得了良好的平衡。与传统的目标检测方法不同,YOLO将目标检测任务视为回归问题,直接从输入图像中预测目标的类别和位置信息。

YOLO 330

Java多线程实战:从锁机制到JUC并发工具与线程池调优

在并发编程中,线程安全是核心挑战,其本质在于对共享可变状态进行正确管理。从原理层面,Java通过synchronized关键字和volatile关键字提供了基础保障,前者基于对象头与锁升级机制实现互斥,后者则通过内存屏障确保可见性与有序性。这些机制的技术价值在于为高并发场景下的数据一致性提供了底层支持。在实际应用场景中,Java并发工具库(JUC)提供了更高效的解决方案,例如ConcurrentHashMap通过CAS与synchronized结合实现了细粒度锁,大幅提升了读多写少场景的性能。而线程池作为资

weixin_30512043的博客 435

【学习系列】SAP RAP 29:如何创建Application Jobs(下)

本文介绍了在SAP系统中通过API创建和使用Application Jobs的方法。主要内容包括:1) 创建RAP应用并通过Action调用API创建Jobs;2) 使用事件驱动方式异步更新Job状态;3) 通过URL跳转到标准日志页面;4) 使用Custom Entity获取Job日志;5) 演示遗留接口IF_APJ_DT_EXEC_OBJECT和IF_APJ_RT_EXEC_OBJECT的使用。文章提供了详细的代码示例,展示了如何通过CL_APJ_RT_API=&gt;SCHEDULE_JOB调度作业,

DeveloperMrMeng的博客 379

MTK_android11_WIFI(内网)和4G(外网)共存

MTK_android11_WIFI(内网)和4G(外网)共存

王涛的博客 2449

Java多线程面试核心:从JMM、锁升级到并发容器与实战调优

并发编程是构建高性能、高可用系统的核心技术基础,其核心挑战在于解决多线程环境下对共享资源的安全访问。Java内存模型(JMM)定义了主内存与工作内存的交互规范,是理解线程安全中可见性与有序性问题的基石。围绕这一原理,Java提供了synchronized关键字及一系列并发工具来保障数据一致性。synchronized通过锁升级机制(无锁、偏向锁、轻量级锁、重量级锁)在保证线程安全的同时,兼顾了性能。在工程实践中,AQS作为并发包的心脏,为ReentrantLock、CountDownLatch等高级同步器提

weixin_34218890的博客 329

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Java面试核心知识点:多线程、集合框架与Spring原理详解

面向对象编程是Java的核心思想,通过封装、继承和多态三大特性实现代码复用和扩展性。在并发编程领域,线程池技术通过资源复用显著提升系统性能,而JUC包中的CountDownLatch和CyclicBarrier则解决了复杂的线程同步问题。集合框架中ArrayList与LinkedList的底层实现差异直接影响数据操作效率,HashMap的线程安全问题则引出了ConcurrentHashMap的分段锁优化方案。Spring框架通过AOP实现横切关注点的模块化,其自动配置机制大大简化了开发流程。这些Java核心

weixin_34082695的博客 407

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

到底什么是重入锁,一次搞清楚!(不要麻烦了)

这个是 JDK @since 1.5 添加的一种颗粒度更小的锁,它完全可以替代 synchronized 关键字来实现它的所有功能,而且 ReentrantLock 锁的灵活度要远远大于 synchronized 关键字。从类结构图看出,ReentrantLock 实现了 Lock 接口,ReentrantLock 只是 Lock 接口的一个实现而已。它们都是 java.util.concurrent 包里面的内容(俗称 JUC、并发包),也都是 JDK 1.5 开始加入的。

java_xiaoo的博客 1974

Java核心基础:从JVM内存模型到并发编程的实战内功修炼

Java作为企业级应用的主流语言,其核心基础是构建稳健、高性能系统的基石。理解Java内存模型(JMM)是掌握并发编程的关键,它定义了线程间共享变量的访问规则,解决了可见性、原子性和有序性问题。通过深入分析JVM内存区域划分,如堆、方法区(Metaspace),开发者能够有效诊断和解决`OutOfMemoryError`等线上疑难杂症,这是性能调优与系统稳定的技术保障。在并发场景下,从`synchronized`锁的升级优化机制到`java.util.concurrent`工具包的无锁CAS实现,这些原理为

weixin_30399155的博客 395

2025小米Java面经全解析:从基础八股到JVM调优与并发实战

Java面试通常从基础语法、集合框架与设计原则入手,但大厂更看重候选人对并发编程、JVM内存模型、Spring容器原理等底层机制的深度理解。在真实业务场景中,HashMap的并发扩容风险、volatile的可见性保障、Spring事务失效边界、JVM调优与OOM排查,都直接关系到高并发系统的稳定性与性能表现。从基础八股到工程落地,扎实掌握这些核心知识点,不仅有助于应对面试中的连环追问,更能指导日常开发中的故障诊断与架构设计。本文结合2025年小米Java面试中的高频题型,系统梳理Java基础、并发编程、JV

weixin_30367169的博客 1080

Java开发者进阶指南:从核心原理到工程实践的系统学习路线

面向对象编程(OOP)是Java语言的核心思想,通过封装、继承、多态三大特性构建模块化、可复用的代码结构。其底层原理基于类与对象的关系模型,实现了数据抽象和行为封装,这一设计显著提升了代码的可维护性和扩展性。在实际工程中,OOP思想与集合框架、多线程机制紧密结合,共同支撑起高并发、高性能的企业级应用开发。例如,合理运用HashMap等集合类进行数据组织,结合JUC并发工具包处理线程安全问题,能够有效构建稳健的后端服务。本文基于一线开发经验,系统梳理了从Java SE基础到Spring生态、从单机部署到分布式

weixin_30509393的博客 381

Java面试进阶:从八股文到核心能力地图的深度解析

在Java技术生态中,集合框架、JVM内存模型和并发编程是构建高性能应用的核心基础。理解HashMap的负载因子与扩容机制,需要从数据结构与算法的时间空间权衡切入,这体现了工程实践中对性能与资源的优化思想。深入JVM内存区域与垃圾回收原理,是定位线上内存泄漏和CPU飙升问题的关键,例如通过分析GC日志和堆转储文件,可以快速诊断静态集合或ThreadLocal不当使用导致的内存问题。并发编程方面,从synchronized到AQS机制的演进,以及CompletableFuture的异步组合能力,为解决高并发场

weixin_33901926的博客 373

本博客文章汇总

Spring

weixin_37862824的博客 953
上一篇: 膜拜!首次公布Java10W字面经,Github访问量破百万
下一篇: 程序员如果都懂SpringWebFlux框架的话,也不用天天CRUD了
java_lujj
博客等级 码龄4年 18粉丝 152原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值