Java多线程(9)

草莓成熟度与叶片病虫害智能检测数据集应用全解析 本文深入解析草莓成熟度与叶片病虫害智能检测数据集的应用价值,涵盖从绿草莓到红果转色的完整成熟周期及白粉病、炭疽病等7种病害的精准识别。通过实战案例展示如何提升病害识别率至94%,并介绍数据集的科研价值,包括跨品种迁移学习和多模态数据融合技术。 阅读详情

Java多线程(9)

AQS(2)

锁的占有与释放

对于AQS来说,线程同步的关键是对状态值state进行操作,根据state是否属于一个线程,操作state的方式分为独占方式和共享方式

  • 独占方式下获取和释放资源使用的方法为:
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }


     public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

     public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

  • 共享泛方式下获取和释放资源的方法为:
public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    } 

使用独占方式获取的资源是与具体线程绑定的,就是一个线程获取了资源,就会标记是这个线程获取到了,其他线程再尝试操作state获取资源时就会发现当前该资源不是自己持有的,就会在获取失败后阻塞

共享方式的资源与具体线程是不相关的,当多个线程请求资源时通过CAS方式竞争获取资源,当一个线程获取到了资源后,另外一个线程再次去获取时如果当前资源还能满足它的需要,则当前线程只需要使用CAS方式进行获取即可

独占方式获取和释放资源的流程:

  • (1)

当一个线程调用 acquire(int arg)方法获取独占资源时,会首先使用tryAcquire方法尝试获取资源,具体就是设置状态变量state的值,成功就直接返回,失败就将当前线程封装为类型Node.EXCLUSIVE的Node节点后插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己


 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }


  • (2)

当一个线程调用release方法时会尝试使用tryRelease操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread),被激活的线程使用tryAcquire尝试,看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下进行,否则还是会被放入AQS队列并被挂起

  public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
}


细节:

AQS类没有提供可用的tryAcquire和tryRelease方法,这需要由具体的子类去实现,子类在实现这两个方法时根据具体场景使用CAS算法尝试修改state状态值,成功返回true,否则返回false,子类还需定义在调用acquire和release方法时state状态值的增减代表什么含义

共享方式获取和释放资源的流程:

  • (1)

线程调用acquireShared获取共享资源时,首先使用tryAcquireShared尝试获取资源,具体设置状态变量state的值,成功则直接返回,失败则将当前线程封装为Node.SHARED的Node节点后插入到AQS阻塞队列的尾部,并使用LockSupport.park(this)方法挂起自己


public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }


  • (2)

当一个线程调用releaseShared时会尝试使用tryReleaseShared操作释放资源,这里是设置状态变量state的值,然后使用LockSupport.unpark(thread)激活AQS队列里面被阻塞的一个线程(thread),被激活的线程则使用tryReleaseShared查看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    } 

细节:

AQS类没有提供可用的tryAcquireShared和tryReleaseShared方法,这两个方法需要由子类实现,子类在实现这两个方法时要根据具体场景使用CAS算法尝试修改state的状态值,成功返回true,否则返回false

基于AQS实现的锁除了需要重写上面的方法,还要重写isHeldExclusively方法来判断锁是否被当前线程独占还是被共享

其中不带Interruptibly关键字的方法的意思是不对中断进行响应,也就是线程在调用不带Interruptibly关键字的方法获取资源时或者获取资源失败被挂起时,其它线程中断了该线程,那么该线程不会因为被中断而抛出异常,它还是继续获取资源或者被挂起,也就是不对中断进行响应,忽略中断

独占锁模式

获取锁的过程:

  • 当线程调用acquire()申请获取锁资源,如果成功,则进入临界区。

  • 当获取锁失败时,则进入一个FIFO等待队列,然后被挂起等待唤醒。

  • 当队列中的等待线程被唤醒以后就重新尝试获取锁资源,如果成功则进入临界区,否则继续挂起等待

释放锁过程:

  • 当线程调用release()进行锁资源释放时,如果没有其他线程在等待锁资源,则释放完成

  • 如果队列中有其他等待锁资源的线程需要唤醒,则唤醒队列中的第一个等待节点(先入先出)

acquire(int arg)

获取锁:

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

逻辑:

  • 调用子类实现的tryAcquire() 方法尝试获取锁资源,如果成功则整个acquire()方法执行完毕,即当前线程获得锁资源,可以进入临界区

  • 如果获取锁失败,则开始进入后面的逻辑,先是addWaiter(Node.EXCLUSIVE)方法

// 该入队方法的返回值就是新创建的节点
private Node addWaiter(Node mode) {
        //基于当前线程,节点类型(Node.EXCLUSIVE)创建新的节点
        //由于这里是独占模式,因此节点类型就是Node.EXCLUSIVE
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        //这里为了提搞性能,首先执行一次快速入队操作,即直接尝试将新节点加入队尾
        if (pred != null) {
            node.prev = pred;
            //这里根据CAS的逻辑,即使并发操作也只能有一个线程成功并返回,其余的都要执行后面的入队操作。即enq()方法
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

入队操作:

//完整的入队操作
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //如果队列还没有初始化,则进行初始化,即创建一个空的头节点
            if (t == null) { 
                //同样是CAS,只有一个线程可以初始化头结点成功,其余的都要重复执行循环体
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //新创建的节点指向队列尾节点,毫无疑问并发情况下这里会有多个新创建的节点指向队列尾节点
                node.prev = t;
                //基于这一步的CAS,不管前一步有多少新节点都指向了尾节点,这一步只有一个能真正入队成功,其他的都必须重新执行循环体
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    //该循环体唯一退出的操作,就是入队成功(否则就要无限重试)
                    return t;
                }
            }
        }
    }


  • 初始化队列的触发条件就是当前已经有线程占有了锁资源,因此上面创建的空的头节点可以认为就是当前占有锁资源的节点(虽然它并没有设置任何属性)

  • 整个代码是处在一个死循环中,知道入队成功。如果失败了就会不断进行重试

经过上面的操作,申请获取锁的线程已经成功加入了等待队列,接下来节点现在要做的就是挂起当前线程,等待被唤醒



// 该方法入参node就是刚入队的包含当前线程信息的节点
final boolean acquireQueued(final Node node, int arg) {
        //锁资源获取失败标记位
        boolean failed = true;
        try {
            //等待线程被中断标记位
            boolean interrupted = false;
            //这个循环体执行的时机包括新节点入队和队列中等待节点被唤醒两个地方
            for (;;) {
                //获取当前节点的前置节点
                final Node p = node.predecessor();
                //如果前置节点就是头结点,则尝试获取锁资源
                if (p == head && tryAcquire(arg)) {
                    //当前节点获得锁资源以后设置为头节点
                    //头结点就表示当前正占有锁资源的节点
                    setHead(node);
                    p.next = null; //帮助GC
                    //表示锁资源成功获取,因此把failed置为false
                    failed = false;
                    //返回中断标记,表示当前节点是被正常唤醒还是被中断唤醒
                    return interrupted;
                }
                如果没有获取锁成功,则进入挂起逻辑
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            //获取锁失败处理逻辑
            if (failed)
                cancelAcquire(node);
        }
    }

挂起逻辑目前为止,只是根据当前线程,节点类型创建了一个节点并加入队列中,其他属性都是默认值

//node是当前线程的节点,pred是它的前置节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取前置节点的waitStatus
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //如果前置节点的waitStatus是Node.SIGNAL则返回true,然后会执行parkAndCheckInterrupt()方法进行挂起
            return true;
        if (ws > 0) {
            //由waitStatus的几个取值可以判断这里表示前置节点被取消
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            //这里我们由当前节点的前置节点开始,一直向前找最近的一个没有被取消的节点
            //注,由于头结点head是通过new Node()创建,它的waitStatus为0,因此这里不会出现空指针问题,也就是说最多就是找到头节点上面的循环就退出了
            pred.next = node;
        } else {
            //根据waitStatus的取值限定,这里waitStatus的值只能是0或者PROPAGATE,那么我们把前置节点的waitStatus设为Node.SIGNAL然后重新进入该方法进行判断
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

上面的方法逻辑用来判断当前节点是否可以被挂起,也就是唤醒条件是否已经具备,即如果挂起了,那一定是可以由其他线程来唤醒的。该方法如果返回false,即挂起条件没有完备,那就会重新执行acquireQueued方法的循环体,进行重新判断,如果返回true,那就表示万事俱备,可以挂起了,就会进入parkAndCheckInterrupt()方法

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        //被唤醒之后,返回中断标记,即如果是正常唤醒则返回false,如果是由于中断醒来,就返回true
        return Thread.interrupted();
    }

acquireQueued方法的最后一步,finally模块。这里是针对锁资源获取失败以后做的一些善后工作


//传入的方法参数是当前获取锁资源失败的节点
private void cancelAcquire(Node node) {
        // 如果节点不存在则直接忽略
        if (node == null)
            return;
        
        node.thread = null;

        // 跳过所有已经取消的前置节点,跟上面的那段跳转逻辑类似
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;
        //这个是前置节点的后继节点,由于上面可能的跳节点的操作,所以这里可不一定就是当前节点,仔细想一下。^_^
        Node predNext = pred.next;

        //把当前节点waitStatus置为取消,这样别的节点在处理时就会跳过该节点
        node.waitStatus = Node.CANCELLED;
        //如果当前是尾节点,则直接删除,即出队
        //注:这里不用关心CAS失败,因为即使并发导致失败,该节点也已经被成功删除
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    //这里的判断逻辑很绕,具体就是如果当前节点的前置节点不是头节点且它后面的节点等待它唤醒(waitStatus小于0),
                    //再加上如果当前节点的后继节点没有被取消就把前置节点跟后置节点进行连接,相当于删除了当前节点
                    compareAndSetNext(pred, predNext, next);
            } else {
                //进入这里,要么当前节点的前置节点是头结点,要么前置节点的waitStatus是PROPAGATE,直接唤醒当前节点的后继节点
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }

释放锁

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease()方法是子类实现的释放锁逻辑,如果成功,就判断等待队列中有没有需要被唤醒的节点(waitStatus为0表示没有需要被唤醒的节点)

唤醒操作

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            //把标记为设置为0,表示唤醒操作已经开始进行,提高并发环境下性能
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        //如果当前节点的后继节点为null,或者已经被取消
        if (s == null || s.waitStatus > 0) {
            s = null;
            //注意这个循环没有break,也就是说它是从后往前找,一直找到离当前节点最近的一个等待唤醒的节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //执行唤醒操作
        if (s != null)
            LockSupport.unpark(s.thread);
    }



共享锁模式

获取锁的过程:

  • 当线程调用acquireShared()申请获取锁资源时,如果成功,则进入临界区。

  • 当获取锁失败时,则创建一个共享类型的节点并进入一个FIFO等待队列,然后被挂起等待唤醒

  • 当队列中的等待线程被唤醒以后就重新尝试获取锁资源,如果成功则唤醒后面还在等待的共享节点并把该唤醒事件传递下去,即会依次唤醒在该节点后面的所有共享节点,然后进入临界区,否则继续挂起等待。

释放锁过程:

  • 当线程调用releaseShared()进行锁资源释放时,如果释放成功,则唤醒队列中等待的节点,如果有的话。

获取锁:

  public final void acquireShared(int arg) {
        //尝试获取共享锁,返回值小于0表示获取失败
        if (tryAcquireShared(arg) < 0)
            //执行获取锁失败以后的方法
            doAcquireShared(arg);
    }

tryAcquireShared()方法是留给子类去实现具体的获取锁逻辑

关于该方法的实现有两点需要特别说明

  • 该方法必须自己检查当前上下文是否支持获取共享锁,如果支持再进行获取。

  • 该方法返回值是个重点。其一、由上面的源码片段可以看出返回值小于0表示获取锁失败,需要进入等待队列。其二、如果返回值等于0表示当前线程获取共享锁成功,但它后续的线程是无法继续获取的,也就是不需要把它后面等待的节点唤醒。最后、如果返回值大于0,表示当前线程获取共享锁成功且它后续等待的节点也有可能继续获取共享锁成功,也就是说此时需要把后续节点唤醒让它们去尝试获取共享锁。

doAcquireShared()


  //参数不多说,就是传给acquireShared()的参数
    private void doAcquireShared(int arg) {
        //添加等待节点的方法跟独占锁一样,唯一区别就是节点类型变为了共享型,不再赘述
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                //表示前面的节点已经获取到锁,自己会尝试获取锁
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    //注意上面说的, 等于0表示不用唤醒后继节点,大于0需要
                    if (r >= 0) {
                        //这里是重点,获取到锁以后的唤醒操作,后面详细说
                        setHeadAndPropagate(node, r);
                        p.next = null;
                        //如果是因为中断醒来则设置中断标记位
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //挂起逻辑跟独占锁一样,不再赘述
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            //获取失败的取消逻辑跟独占锁一样,不再赘述
            if (failed)
                cancelAcquire(node);
        }
    }

独占锁模式获取成功以后设置头结点然后返回中断状态,结束流程,而共享锁模式获取成功以后,调用了setHeadAndPropagate方法


 //两个入参,一个是当前成功获取共享锁的节点,一个就是tryAcquireShared方法的返回值,注意上面说的,它可能大于0也可能等于0
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; //记录当前头节点
        //设置新的头节点,即把当前获取到锁的节点设置为头节点
        //注:这里是获取到锁之后的操作,不需要并发控制
        setHead(node);
        //这里意思有两种情况是需要执行唤醒操作
        //1.propagate > 0 表示调用方指明了后继节点需要被唤醒
        //2.头节点后面的节点需要被唤醒(waitStatus<0),不论是老的头结点还是新的头结点
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //如果当前节点的后继节点是共享类型获取没有后继节点,则进行唤醒
            //这里可以理解为除非明确指明不需要唤醒(后继等待节点是独占类型),否则都要唤醒
            if (s == null || s.isShared())
                //后面详细说
                doReleaseShared();
        }
    }

    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }


唤醒操作:


private void doReleaseShared() {
        for (;;) {
            //唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了
            //其实就是唤醒上面新获取到共享锁的节点的后继节点
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                //表示后继节点需要被唤醒
                if (ws == Node.SIGNAL) {
                    //这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;      
                    //执行唤醒操作      
                    unparkSuccessor(h);
                }
                //如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                
            }
            //如果头结点没有发生变化,表示设置完成,退出循环
            //如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
            if (h == head)                   
                break;
        }
    }

释放共享锁


public final boolean releaseShared(int arg) {
        //尝试释放共享锁
        if (tryReleaseShared(arg)) {
            //唤醒过程,详情见上面分析
            doReleaseShared();
            return true;
        }
        return false;
    }

独占锁相比,共享锁的主要特征在于当一个在等待队列中的共享节点成功获取到锁以后(它获取到的是共享锁),既然是共享,那它必须要依次唤醒后面所有可以跟它一起共享当前锁资源的节点.

毫无疑问,这些节点必须也是在等待共享锁(这是大前提,如果等待的是独占锁,那前面已经有一个共享节点获取锁了,它肯定是获取不到的)。当共享锁被释放的时候,可以用读写锁为例进行思考,当一个读锁被释放,此时不论是读锁还是写锁都是可以竞争资源的

同物种,不同版本之间的坐标转化 是最常用的工具,如果有可用的链文件,这通常是最便捷的方法。CrossMap是一个功能强大的替代工具,支持多种文件格式和物种。自定义生成链文件适用于没有现成资源的情况,虽然复杂一些,但可以满足特定需求。Ensembl 和 Gramene提供的工具适用于支持的物种,尤其在植物基因组领域可能更具优势。选择合适的方法取决于你正在处理的物种、基因组版本以及现有的资源。 阅读详情

相关推荐

STM32控制气泵和电磁阀实现

2、mos管的选型:mos管选择主要注意两个参数即可,一是导通的电流,二是耐压值,并且常用NMOS管,根据要求,气泵和电磁阀供电电压为12V,所以选择的mos管耐压值要大于12V,这里选用耐压值为30V的MOS管,并且导通电流为5.8A,如下图。1、气泵和电磁阀的开和关均为开关量,实现控制方法有多种,比如继电器,但是继电器动作有噪声且体积较大,更好的方法为使用mos管。使用STM32控制气泵和电磁阀的开和关,气泵和电磁阀的供电电压为12V。最终实现单片机控制气泵和电磁阀的开和关。

jacklood的博客 1万+

JAVA多线程实现方式

1.JAVA多线程实现方式 JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。 2.继承Thread类实现多线程 继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runn

godspeedlaile9的专栏 482

Gitee自动创建PR之——评审模式

一 创建PR的常规操作 本地更新主干(一般为master分支),基于主干新建一个分支并(如Dev分支)切换; 进行新的需求开发; 将这个分支推送到远端; 打开 Gitee ,进入创建 Pull Request 界面; 选定目标分支(及我们需要合并到主干分支的分支,一般即为我们的开发分支); 填写 Title 以及 Description; 点击提交 Pull Request。 二 更快的常见PR的方式— — 评审模式 现在 Gitee.

calm_encode的博客 2984

JAVA分布式、高并发、多线程是什么?

什么是分布式? 分布式更多的一个概念,是为了解决单个物理服务器容量和性能瓶颈问题而采用的优化手段。该领域需要解决的问题极多,在不同的技术层面上,又包括:分布式文件系统、分布式缓存、分布式数据库、分布式计算等,一些名词如Hadoop、zookeeper、MQ等都跟分布式有关。从理念上讲,分布式的实现有两种形式,水平扩展和垂直拆分: 水平扩展:当一台机器扛不住流量时,就通过添加机器的方式,将流量平分到...

weixin_39497038的博客 1079

从Applied Intelligence被拒到The Visual Computer录用:我的计算机视觉论文投稿时间线全记录(含详细修改意见)

本文详细记录了作者从Applied Intelligence被拒到The Visual Computer成功发表计算机视觉论文的全过程,重点分享了如何将模糊的拒稿理由转化为具体改进方案。文章包含技术改进细节、写作调整策略以及期刊选择经验,为计算机视觉领域的研究者提供了实用的投稿指南。

weixin_30872499的博客 464

手把手教学|用飞书+扣子工作流实现自动化批量图片生成,效率提升99%!体验完整操作流程

在先前的文章中,我们已经深入探讨了扣子智能体的多种应用场景,以及如何创建和使用工作流进行高效管理。本文将引领大家通过一个具体案例,将扣子的工作流功能嵌入飞书应用中,实现操作的自动化与高效化。

weixin_66485800的博客 6028

Java多线程(九)之ReentrantLock与Condition

一、ReentrantLock 类 1.1 什么是reentrantlock java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 ...

爱.NET 663

从零开始:Basler相机与pylon Viewer的工业视觉入门指南

本文详细介绍了Basler相机与pylon Viewer在工业视觉系统中的入门指南,涵盖硬件连接、软件安装、核心功能解析及实战案例。通过pylon Viewer的全面控制和参数调整功能,帮助新手快速掌握工业视觉检测技术,提升生产效率。

julia4scientist的博客 1134

Java多线程入门篇,算法+分布式+微服务

1.3 什么是多线程? 多线程就是几乎同时执行多个线程。 1.4 为什么要使用多线程 1.4.1 使用线程可以把占据时间长的程序中的任务放到后台去处理。 1.4.2 用户界面更加吸引人,这样比如用户点击了一个按钮去触发某件事件的处理,可以弹出一个进度条来显示处理的进度。 1.4.3 程序的运行效率可能会提高。 1.4.4 在一些等待的任务实现上如用户输入,文件读取和网络收发数据等,线程就比较有用了。 2 线程的状态 ======= 一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable

m0_64204730的博客 602

Python库CloudScraper详细使用(绕过 Cloudflare 的反机器人页面的 Python 模块)

CloudScraper是一个专门用于绕过Cloudflare反机器人保护的Python库,它基于requests库构建,能够处理Cloudflare的5秒盾、WAF和人机验证等防护机制。

数据知道的博客 1万+

【图像去噪】论文精读:FM2S: Self-Supervised Fluorescence Microscopy Denoising With Single Noisy Image

论文题目:FM2S: Self-Supervised Fluorescence Microscopy Denoising With Single Noisy Image —— FM2S:单噪声图像自监督荧光显微镜去噪arXiv 2024!显微图像去噪!通过可视化详细的细胞结构和生物过程,荧光显微镜具有显着先进的生物学研究。然而,由于难以精确建模固有噪声和获取干净图像进行训练,这种图像去噪任务往往面临挑战,这限制了大多数现有方法。在本文中,我们提出了一种有效的。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 408

java 多线程的分布式锁

分布式锁: 为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。 多个线程或者请求去操作对同一个资源进行操作,为了保证资源的数据正确,通过分布式锁来实现。 实现: 通过redis + ThreadLocl + LUA脚本来实现。 流程图 里面的参数可以根据自己的需求进行调整。 ...

weixin_45661641的博客 536

L2tp环境搭建笔记- Openwrt平台

本文使用3台openwrt设备, 分别搭建L2TP服务和2个客户端, 进行端到端验证和局域网组网,并分析搭建过程中涉及的默认路由( defaultroute) 配置问题。

十年通信老兵的技术修炼之路——从3G到5G,从地面基站到卫星通信。 3389

[Java] 9.多线程

前面我们系统的了解了Java的基础知识,本篇开始将进入到Java更深层次的介绍,我们先来介绍一下Java中的一个重要的概念——线程。 一、什么是线程   在了解线程前,我们首先要了解进程的概念。进程是操作系统调度和分配资源的基本单位,进程之间的通信需要通过专门的系统机制,比如消息、socket和管道来完成。而线程是比进程更小的执行单位,每个线程拥有自己的栈和寄存器等资源数据,多个线程之间共享进...

ingy 171

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

9.java之多线程

1.基本概念:程序、进程、线程 1.1 程序、进程、线程 程序(program)是为完成特定任务、用某种语言编写的一组命令的集合。即指一段静态的代码,静态的对象 进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程——生命周期 ( 进程作为资源分配的单位,)系统在运行时回味每个进程分配不同的内存区域 线程(thread)是一个程序内部的一条执行路径。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc)。一个进程中的多个线程

hutc_Alan的博客 269

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

第9章 Java多线程

第9章 Java多线程

xqmids99的博客 1152

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

9、 JAVA多线程

9. 线程 1. 概念 一、程序 进程 线程 1、程序:指令集静态概念 2、进程:操作系统 调度程序 动态概念 3、线程:在进程内多条执行路径 4、了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。 5、什么是进程? 通过任务管理器我们就看到了进程的存在。而通过观察,我们发现只有运行的程序才会出现进程。 进程:就是正在运行的程序。进程是系统...

陈同学的博客 643

java note 9 多线程

文章目录1. 进程和线程1.1 线程的生命周期1.2 线程创建定义线程类实现Runnable接口2. 优先级3. sleep()3.1 yield()3.2 交替输出4. join()5. 锁5.1 同步异步5.2 两种同步方法同步对象同步方法5.3 类锁5.4 死锁6. 守护线程7. 定时器 把之前那篇java多线程重新排版一下。 1. 进程和线程 单核计算机中,CPU在某一时间点只能做一件事,...

Starr 227

Java(9)多线程

多线程编程,包括线程的创建、同步、异常处理,以及网络编程中的文件传输。通过示例代码,理解了线程的挂起与唤醒、异常的捕获与抛出,以及客户端与服务器之间的文件传输过程。

Tttian622的博客 837

Java多线程9 多线程的提高

之前的内容:理解多线程。创建多线程。多线程的安全问题/分析与理解。安全问题的解决办法/锁 ———————————多线程的提高    线程之间的通信————————————— 之前的例子都是多个线程执行一个线程任务,多线程的提高——多个线程执行不同的线程任务,并发执行 例如:消费者与生产者,一个消费一个生产,执行的线程任务不同,但是处理的资源是一样的。但是要求同时进行 这叫做线程间的通信——...

Stitch__的博客 361

9、Java中的多线程

同步代码块简单来说就是将一段代码用一把锁给锁起来, 只有获得了这把锁的线程才访问, 并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码。4、Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西,所以Runnable或Thread本身没有可比性。线程插队:join()方法,在某个线程中调用其他线程的join()时,调用的线程将被阻塞,直到加入的线程执行完之后,它才会继续运行。

qq_59102256的博客 160

Java >> 9. 多线程

线程概念 进程和线程 进程:当执行一个程序时,要给程序分配内存、外设等资源,并等候处理器的调度执行程序中的指令。程序执行完毕后,系统要收回所分配的资源。这样一个过程就是进程。 进程是一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。 进程是存储器、外设等资源的分配单位,也是处理器的调度对象。 每个进程都有自己独立的内存空间和资源,进程之间不会共享系统资源。 线程:比进程更小的运行单位...

原谅我这一生不羁放纵爱自由 375

Java小白-9.Java多线程

创建一个线程 Java 提供了三种创建线程的方法: 通过实现 Runnable 接口; 通过继承 Thread 类本身; 通过 Callable 和 Future 创建线程。 通过实现 Runnable 接口来创建线程 http://www.runoob.com/java/java-multithreading.html 继承Thread类 /** * 继承Thread类的...

WMY10001的博客 353

Java学习(9)—— 多线程

多线程的创建

Archer__13的博客 960

Java进阶9--多线程

本篇文章详细介绍了Java中多线程相关的知识点,适合新手小白!

bobby102的博客 449

【JAVA】JAVA多线程基础9

如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。锁消除,也是一种编译器优化的手段。但是,如果粒度细的锁被反复进行加锁解锁,可能实际效果还不如粒度粗的锁(涉及到锁的反复竞争)。我们可以看到,synchronized的策略是比价复杂的,在背后做了很多事情,目的为了让程序猿哪怕啥都不懂,也不至于写出特别慢的程序。

2401_84064192的博客 1043
上一篇: Java多线程(8)
下一篇: Java多线程(10)
Coder_py
博客等级 码龄10年 106粉丝 258原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值