java 并发

经典并发问题的深度分析与实现【c++与golang】【万字分析】 锁与信号量经典的多线程并发问题,需要考虑线程之间的同步和互斥,常用的解决方法包括互斥锁、条件变量、信号量等。针对不同的问题,需要选择合适的解决方法,保证线程之间的正确同步。生产者-消费者问题:有一组生产者线程/进程和一组消费者线程/进程,它们共享一个有限容量的缓冲区。生产者负责将数据项放入缓冲区,消费者则从缓冲区中取出数据项进行处理哲学家就餐问题:涉及到多个哲学家和多个餐叉,每个哲学家需要持有两个餐叉才能进餐。当多个哲学家同时想进餐时,可能会出现死锁。读者-写者问题。 阅读详情

并发

1什么是并发问题。

多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题。

银行两操作员同时操作同一账户就是典型的例子。比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户减去 50元,A先提交,B后提交。 最后实际账户余额为1000-50=950元,但本该为 1000+100-50=1050。这就是典型的并发问题。如何解决?可以用锁。

2 java中synchronized的用法

  1. 用法1
    public class Test{
        public synchronized void print(){
            ....;
        } 
    }
    

    某线程执行print()方法,则该对象将加锁。其它线程将无法执行该对象的所有synchronized块。

  2. 用法2
    public class Test{
        public void print(){
            synchronized(this){//锁住本对象
                ...;
            }
        }
    }
    

    同用法1, 但更能体现synchronized用法的本质。

  3. 用法3
    public class Test{
        private String a = "test";
        public void print(){
            synchronized(a){//锁住a对象
                ...;
            }
        }
        public synchronized void t(){
            ...; //这个同步代码块不会因为print()而锁定.
        }
    }
    

    执行print(),会给对象a加锁,注意不是给Test的对象加锁,也就是说 Test对象的其它synchronized方法不会因为print()而被锁。同步代码块执行完,则释放对a的锁。

    为了锁住一个对象的代码块而不影响该对象其它 synchronized块的高性能写法:

    public class Test{
        private byte[] lock = new byte[0];
        public void print(){
            synchronized(lock){
                ...;
            }
        }
        public synchronized void t(){
            ...; 
        }
    }
    
  4. 静态方法的锁
    public class Test{
        public synchronized static void execute(){
            ...;
        }
    }
    

    效果同

    public class Test{
        public static void execute(){
            synchronized(TestThread.class){
                ...;
            }
        }
    }
    

3 Java中的锁与排队上厕所。

锁就是阻止其它进程或线程进行资源访问的一种方式,即锁住的资源不能被其它请求访问。在JAVA中,sychronized关键字用来对一个对象加锁。比如:

public class MyStack {
    int idx = 0;
    char [] data = new char[6];

    public synchronized void push(char c) {
        data[idx] = c;
        idx++;
    }

    public synchronized char pop() {
        idx--;
        return data[idx];
    }

    public static void main(String args[]){
        MyStack m = new MyStack();
        /**
           下面对象m被加锁。严格的说是对象m的所有synchronized块被加锁。
           如果存在另一个试图访问m的线程T,那么T无法执行m对象的push和
           pop方法。
        */
        m.pop();//对象m被加锁。
    }
}

Java的加锁解锁跟多个人排队等一个公共厕位完全一样。第一个人进去后顺手把门从里面锁住,其它人只好排队等。第一个人结束后出来时,门才会打开(解锁)。轮到第二个人进去,同样他又会把门从里面锁住,其它人继续排队等待。

用厕所理论可以很容易明白: 一个人进了一个厕位,这个厕位就会锁住,但不会导致另一个厕位也被锁住,因为一个人不能同时蹲在两个厕位里。对于Java 就是说:Java中的锁是针对同一个对象的,不是针对class的。看下例:

MyStatck m1 = new MyStack();
MyStatck m2 = new Mystatck();
m1.pop();
m2.pop();  

m1对象的锁是不会影响m2的锁的,因为它们不是同一个厕位。就是说,假设有 3线程t1,t2,t3操作m1,那么这3个线程只可能在m1上排队等,假设另2个线程 t8,t9在操作m2,那么t8,t9只会在m2上等待。而t2和t8则没有关系,即使m2上的锁释放了,t1,t2,t3可能仍要在m1上排队。原因无它,不是同一个厕位耳。

Java不能同时对一个代码块加两个锁,这和数据库锁机制不同,数据库可以对一条记录同时加好几种不同的锁,请参见:

http://hi.baidu.com/dapplehou/blog/item/b341a97744fe6616b151b9a3.html

4何时释放锁?

一般是执行完毕同步代码块(锁住的代码块)后就释放锁,也可以用wait()方式半路上释放锁。wait()方式就好比蹲厕所到一半,突然发现下水道堵住了,不得已必须出来站在一边,好让修下水道师傅(准备执行notify的一个线程)进去疏通马桶,疏通完毕,师傅大喊一声:  "已经修好了"(notify),刚才出来的同志听到后就重新排队。注意啊,必须等师傅出来啊,师傅不出来,谁也进不去。也就是说notify后,不是其它线程马上可以进入封锁区域活动了,而是必须还要等notify代码所在的封锁区域执行完毕从而释放锁以后,其它线程才可进入。

这里是wait与notify代码示例:

public synchronized char pop() {
    char c;
    while (buffer.size() == 0) {
        try {
            this.wait(); //从厕位里出来
        } catch (InterruptedException e) {
            // ignore it...
        }
    }
    c = ((Character)buffer.remove(buffer.size()-1)).
        charValue();
    return c;
}

public synchronized void push(char c) {
    this.notify(); //通知那些wait()的线程重新排队。注意:仅仅是通知它们重新排队。
    Character charObj = new Character(c);
    buffer.addElement(charObj);
}//执行完毕,释放锁。那些排队的线程就可以进来了。

再深入一些。

由于wait()操作而半路出来的同志没收到notify信号前是不会再排队的,他会在旁边看着这些排队的人(其中修水管师傅也在其中)。注意,修水管的师傅不能插队,也得跟那些上厕所的人一样排队,不是说一个人蹲了一半出来后,修水管师傅就可以突然冒出来然后立刻进去抢修了,他要和原来排队的那帮人公平竞争,因为他也是个普通线程。如果修水管师傅排在后面,则前面的人进去后,发现堵了,就wait,然后出来站到一边,再进去一个,再wait,出来,站到一边,只到师傅进去执行notify. 这样,一会儿功夫,排队的旁边就站了一堆人,等着notify.

终于,师傅进去,然后notify了,接下来呢?

1. 有一个wait的人(线程)被通知到。
2. 为什么被通知到的是他而不是另外一个wait的人?取决于JVM.我们无法预先
   判断出哪一个会被通知到。也就是说,优先级高的不一定被优先唤醒,等待
   时间长的也不一定被优先唤醒,一切不可预知!(当然,如果你了解该JVM的
   实现,则可以预知)。
3. 他(被通知到的线程)要重新排队。
4. 他会排在队伍的第一个位置吗?回答是:不一定。他会排最后吗?也不一定。
   但如果该线程优先级设的比较高,那么他排在前面的概率就比较大。
5. 轮到他重新进入厕位时,他会从上次wait()的地方接着执行,不会重新执行。
   恶心点说就是,他会接着拉巴巴,不会重新拉。
6. 如果师傅notifyAll(). 则那一堆半途而废出来的人全部重新排队。顺序不可知。

Java DOC 上说,The awakened threads will not be able to proceed until the  current thread relinquishes the lock on this object(当前线程释放锁前,唤醒的线程不能去执行)。

这用厕位理论解释就是显而易见的事。

5 Lock的使用

用synchronized关键字可以对资源加锁。用Lock关键字也可以。它是JDK1.5中新增内容。用法如下:

class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull  = lock.newCondition(); 
    final Condition notEmpty = lock.newCondition(); 

    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) 
                notFull.await();
            items[putptr] = x; 
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) 
                notEmpty.await();
            Object x = items[takeptr]; 
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    } 
}

(注:这是JavaDoc里的例子,是一个阻塞队列的实现例子。所谓阻塞队列,就是一个队列如果满了或者空了,都会导致线程阻塞等待。Java里的 ArrayBlockingQueue提供了现成的阻塞队列,不需要自己专门再写一个了。)

一个对象的lock.lock()和lock.unlock()之间的代码将会被锁住。这种方式比起synchronize好在什么地方?简而言之,就是对wait的线程进行了分类。用厕位理论来描述,则是那些蹲了一半而从厕位里出来等待的人原因可能不一样,有的是因为马桶堵了,有的是因为马桶没水了。通知(notify)的时候,就可以喊:因为马桶堵了而等待的过来重新排队(比如马桶堵塞问题被解决了),或者喊,因为马桶没水而等待的过来重新排队(比如马桶没水问题被解决了)。这样可以控制得更精细一些。不像synchronize里的wait和notify,不管是马桶堵塞还是马桶没水都只能喊:刚才等待的过来排队!假如排队的人进来一看,发现原来只是马桶堵塞问题解决了,而自己渴望解决的问题(马桶没水)还没解决,只好再回去等待(wait),白进来转一圈,浪费时间与资源。

Lock方式与synchronized对应关系:

LockawaitsignalsignalAll
synchronizedwaitnotifynotifyAll

注意:不要在Lock方式锁住的块里调用wait、notify、notifyAll

6利用管道进行线程间通信

原理简单。两个线程,一个操作PipedInputStream,一个操作 PipedOutputStream。PipedOutputStream写入的数据先缓存在Buffer中,如果 Buffer满,此线程wait。PipedInputStream读出Buffer中的数据,如果Buffer 没数据,此线程wait。

jdk1.5中的阻塞队列可实现同样功能。

  1. 例1 这个例子实际上只是单线程,还谈不上线程间通信,但不妨一看。

    http://hi.baidu.com/ecspell/blog/item/7b02d3133ab555005aaf53f5.html

    package io;
    import java.io.*;
    public class PipedStreamTest {
        public static void main(String[] args) {
            PipedOutputStream ops=new PipedOutputStream();
            PipedInputStream pis=new PipedInputStream();
            try{
                ops.connect(pis);//实现管道连接
                new Producer(ops).run();
                new Consumer(pis).run();
            }catch(Exception e){
                e.printStackTrace();
            }
    
        }
    }
    
    //生产者
    class Producer implements Runnable{
        private PipedOutputStream ops;
        public Producer(PipedOutputStream ops)
        {
            this.ops=ops;
        }
    
        public void run()
        {
            try{
                ops.write("hell,spell".getBytes());
                ops.close();
            }catch(Exception e)
                {e.printStackTrace();}
        }
    }
    
    //消费者
    class Consumer implements Runnable{
        private PipedInputStream pis;
        public Consumer(PipedInputStream pis)
        {
            this.pis=pis;
        }
    
        public void run()
        {
            try{
                byte[] bu=new byte[100];
                int len=pis.read(bu);
                System.out.println(new String(bu,0,len));
                pis.close();
            }catch(Exception e)
                {e.printStackTrace();}
        }
    } 
    
  2. 例2 对上面的程序做少许改动就成了两个线程。
    package io;
    import java.io.*;
    public class PipedStreamTest {
        public static void main(String[] args) {
            PipedOutputStream ops=new PipedOutputStream();
            PipedInputStream pis=new PipedInputStream();
            try{
                ops.connect(pis);//实现管道连接
                Producer p = new Producer(ops);
                new Thread(p).start();
                Consumer c = new Consumer(pis);
                new Thread(c).start();
            }catch(Exception e){
                e.printStackTrace();
            }
    
        }
    }
    
    //生产者
    class Producer implements Runnable{
        private PipedOutputStream ops;
        public Producer(PipedOutputStream ops)
        {
            this.ops=ops;
        }
    
        public void run()
        {
            try{
                for(;;){
                    ops.write("hell,spell".getBytes());
                    ops.close();
                }
            }catch(Exception e)
                {e.printStackTrace();}
        }
    }
    
    //消费者
    class Consumer implements Runnable{
        private PipedInputStream pis;
        public Consumer(PipedInputStream pis)
        {
            this.pis=pis;
        }
    
        public void run()
        {
            try{
                for(;;){
                    byte[] bu=new byte[100];
                    int len=pis.read(bu);
                    System.out.println(new String(bu,0,len));
                }
                pis.close();
            }catch(Exception e)
                {e.printStackTrace();}
        }
    }
    
  3. 例3. 这个例子更加贴进应用
    import java.io.*;
           
    public class PipedIO { //程序运行后将sendFile文件的内容拷贝到receiverFile文件中
        public static void main(String args[]){       
            try{//构造读写的管道流对象       
                PipedInputStream pis=new PipedInputStream();       
                PipedOutputStream pos=new PipedOutputStream();       
                //实现关联       
                pos.connect(pis);       
                //构造两个线程,并且启动。           
                new Sender(pos,"c:\\text2.txt").start();           
                new Receiver(pis,"c:\\text3.txt").start();         
            }catch(IOException e){       
                System.out.println("Pipe Error"+ e);       
            }       
        }       
    }       
    //线程发送       
    class Sender extends Thread{           
        PipedOutputStream pos;       
        File file;       
        //构造方法       
        Sender(PipedOutputStream pos, String fileName){       
            this.pos=pos;       
            file=new File(fileName);       
        }          
        //线程运行方法       
        public void run(){          
            try{       
                //读文件内容       
                FileInputStream fs=new FileInputStream(file);       
                int data;       
                while((data=fs.read())!=-1){       
                    //写入管道始端       
                    pos.write(data);       
                }       
                pos.close();                        
            }       
            catch(IOException e) {       
                System.out.println("Sender Error" +e);       
            }       
        }       
    }
           
    //线程读       
    class Receiver extends Thread{       
        PipedInputStream pis;       
        File file;       
        //构造方法       
        Receiver(PipedInputStream pis, String fileName){         
            this.pis=pis;       
            file=new File(fileName);       
        }          
        //线程运行       
        public void run(){          
            try {       
                //写文件流对象       
                FileOutputStream fs=new FileOutputStream(file);       
                int data;       
                //从管道末端读       
                while((data=pis.read())!=-1){
           
                    //写入本地文件       
                    fs.write(data);       
                }       
                pis.close();            
            }       
            catch(IOException e){       
                System.out.println("Receiver Error" +e);       
            }       
        }       
    }
    

7阻塞队列

阻塞队列可以代替管道流方式来实现进水管/排水管模式(生产者/消费者).JDK1.5提供了几个现成的阻塞队列. 现在来看ArrayBlockingQueue的代码如下:

这里是一个阻塞队列

BlockingQueue<Object> blockingQ = new ArrayBlockingQueue<Object> 10;

一个线程从队列里取

for(;;){
    Object o = blockingQ.take();//队列为空,则等待(阻塞)
}

另一个线程往队列存

for(;;){
    blockingQ.put(new Object());//队列满,则等待(阻塞)
}

可见,阻塞队列使用起来比管道简单。

8使用Executors、Executor、ExecutorService、ThreadPoolExecutor

可以使用线程管理任务。还可以使用jdk1.5提供的一组类来更方便的管理任务。从这些类里我们可以体会一种面向任务的思维方式。这些类是:

  1. Executor接口。使用方法:
    Executor executor = anExecutor;//生成一个Executor实例。
    executor.execute(new RunnableTask1());
    

    用意:使用者只关注任务执行,不用操心去关注任务的创建、以及执行细节等这些第三方实现者关心的问题。也就是说,把任务的调用执行和任务的实现解耦。

    实际上,JDK1.5中已经有该接口出色的实现。够用了。

  2. Executors是一个如同Collections一样的工厂类或工具类,用来产生各种不同接口的实例。
  3. ExecutorService接口它继承自Executor. Executor只管把任务扔进 executor()里去执行,剩余的事就不管了。而ExecutorService则不同,它会多做点控制工作。比如:
    class NetworkService {
        private final ServerSocket serverSocket;
        private final ExecutorService pool;
    
        public NetworkService(int port, int poolSize) throws IOException {
            serverSocket = new ServerSocket(port);
            pool = Executors.newFixedThreadPool(poolSize);
        }
     
        public void serve() {
            try {
                for (;;) {
                    pool.execute(new Handler(serverSocket.accept()));
                }
            } catch (IOException ex) {
                pool.shutdown(); //不再执行新任务
            }
        }
    }
    
    class Handler implements Runnable {
        private final Socket socket;
        Handler(Socket socket) { this.socket = socket; }
        public void run() {
            // read and service request
        }
    }
    

    ExecutorService(也就是代码里的pool对象)执行shutdown后,它就不能再执行新任务了,但老任务会继续执行完毕,那些等待执行的任务也不再等待了。

  4. 任务提交者与执行者通讯
    public static void main(String args[])throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Callable<String> task = new Callable<String>(){
            public String call()throws Exception{
                return "test";
            }
        };
        Future<String> f = executor.submit(task); 
        String result = f.get();//等待(阻塞)返回结果
        System.out.println(result);
        executor.shutdown();                
    }
    

    Executors.newSingleThreadExecutor()取得的Executor实例有以下特性:

    1. 任务顺序执行. 比如:
      executor.submit(task1);
      executor.submit(task2);
      

      必须等task1执行完,task2才能执行。

    2. task1和task2会被放入一个队列里,由一个工作线程来处理。即:一共有2个线程(主线程、处理任务的工作线程)。
  5. 其它的类请参考Java Doc

9并发流程控制

本节例子来自温少的Java并发教程,可能会有改动。向温少致敬。

  1. CountDownLatch 门插销计数器
    1. 启动线程,然后等待线程结束。即常用的主线程等所有子线程结束后再执行的问题。
      public static void main(String[] args)throws Exception {
          // TODO Auto-generated method stub
          final int count=10;
          final CountDownLatch completeLatch = new CountDownLatch(count);//定义了门插销的数目是10
                      
          for(int i=0;i<count;i++){
              Thread thread = new Thread("worker thread"+i){
                      public void run(){
                          //do xxxx                                   
                          completeLatch.countDown();//减少一根门插销
                      }
                  };
              thread.start();
          }           
          completeLatch.await();//如果门插销还没减完则等待。
      } 
      

      JDK1.4时,常用办法是给子线程设置状态,主线程循环检测。易用性和效率都不好。

    2. 启动很多线程,等待通知才能开始
      public static void main(String[] args) throws Exception {
          // TODO Auto-generated method stub
          final CountDownLatch startLatch = new CountDownLatch(1);//定义了一根门插销
      
          for (int i = 0; i < 10; i++) {
              Thread thread = new Thread("worker thread" + i) {
                      public void run() {
                          try {
                              startLatch.await();//如果门插销还没减完则等待
                          } catch (InterruptedException e) {
      
                          }
                          // do xxxx
                      }
                  };
              thread.start();
          }
          startLatch.countDown();//减少一根门插销
      }
      
  2. CycliBarrier. 等所有线程都达到一个起跑线后才能开始继续运行。
    public class CycliBarrierTest implements Runnable {
        private CyclicBarrier barrier;
    
        public CycliBarrierTest(CyclicBarrier barrier) {
            this.barrier = barrier;
        }
    
        public void run() {
            //do xxxx;
            try {
                this.barrier.await();//线程运行至此会检查是否其它线程都到齐了,没到齐就继续等待。到齐了就执行barrier的run函数体里的内容
            } catch (Exception e) {
    
            }
        }
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            //参数2代表两个线程都达到起跑线才开始一起继续往下执行
            CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {
                    public void run() {
                        //do xxxx;
                    }
                });
            Thread t1 = new Thread(new CycliBarrierTest(barrier));         
            Thread t2 = new Thread(new CycliBarrierTest(barrier));
            t1.start();
            t2.start();
        }
    
    }
    

    这简化了传统的用计数器+wait/notifyAll来实现该功能的方式。

10并发3定律

  1. Amdahl定律. 给定问题规模,可并行化部分占12%,那么即使把并行运用到极致,系统的性能最多也只能提高1/(1-0.12)=1.136倍。即:并行对提高系统性能有上限。
  2. Gustafson定律. Gustafson定律说Amdahl定律没有考虑随着cpu的增多而有更多的计算能力可被使用。其本质在于更改问题规模从而可以把Amdahl定律中那剩下的88%的串行处理并行化,从而可以突破性能门槛。本质上是一种空间换时间。
  3. Sun-Ni定律. 是前两个定律的进一步推广。其主要思想是计算的速度受限于存储而不是CPU的速度. 所以要充分利用存储空间等计算资源,尽量增大问题规模以产生更好/更精确的解.

11由并发到并行

计算机识别物体需要飞速的计算,以至于芯片发热发烫,而人在识别物体时却一目了然,却并不会导致某个脑细胞被烧热烧焦(夸张)而感到不适,是由于大脑是一个分布式并行运行系统,就像google用一些廉价的linux服务器可以进行庞大复杂的计算一样,大脑内部无数的神经元的独自计算,互相分享成果,从而瞬间完成需要单个cpu万亿次运算才能有的效果。试想,如果在并行处理领域有所创建,将对计算机的发展和未来产生不可估量的影响。当然,其中的挑战也可想而知:许多的问题是并不容易轻易就“分割”的了的。

 

Java并发问题的深入理解与实战:性能优化与正确性保证 Java并发编程是一项需要高度理解和实践的技能。通过合理选择并发工具(如synchronized、Lock、Atomic类等)并结合优化技巧,我们可以有效地解决并发问题,提升程序性能。 阅读详情

相关推荐

并发一:JAVA并发模型

JAVA并发模型

Mssyaa的博客 1082

java实现video标签视频流播放

java实现分片文件video标签播放

lvhong1234的博客 18万+

Java并发实战

并发编程的问题:可见性,原子性,有序性 问题由来: 缓存导致的可见性: 缓存最开始是为了解决CPU单核时代时,CPU和内存交互问题,每个CPU都有自己的缓存,但是在多核时代时,CPU自己的缓存之间是具有独立性的,所以数据从缓存到内存之间是隔离的,这也就导致了内存可见性的原因。 单核时代: 多核时代: 线程切换带来的原子性问题: 在单核时代,应用要是想进行并行运行,是需要CPU对运行任务每隔一定的“时间片”就进行切片处理的,这就导致了多个线程对同一个资源就行修改的时候发生无原子性的问题。 编译优化带来的有

zy763402261的博客 1992

JAVA音视频解决方案----video.js插件使用

序言 前面在成功推流到服务器后,也可以通过video.js插件播放该视频流.(这不废话么,难道项目中我们不该找个播放端的插件么~~~) 另外使用videojs的版本是video-js-7.5.4.首先要自己去下载该js插件(难点还是流服务,要把rtp转成rtmp.后面会讲)如需探讨请联系cuiyaonan2000@163.com videojs播放rtmp协议流DEOM 这里使用插...

难得糊涂 8万+

Java高并发——使用Lock锁实现并发安全

前言 并发,在一个成熟的系统中是必不可少的,这也是广大程序猿探讨的热点,高并发下的数据安全尤为重要。博主最近也在巩固这方面的知识,特此整理一下博客,做一下记录。 什么是并发?并发有哪些问题? 提到并发,就不得不提到线程,关于多线程想必大家都知道,如果一个程序开启多个线程,执行多个任务,那么我们就说这个程序存在并发。 并发场景下,最需要注意的问题就是数据安全性,即线程安全,那么什么是线程安全...

wzy18210825916的博客 2067

多线程 - Java实现并发的几种方法

【Java实现并发的几种方法】

学无止境 3200

java并发-java并发大师

java并发大师

西京刀客 2469

Node框架之NestJS入门学习

文章主要分为三部分:1 NestJS 与Egg对比2 NestJS 基础知识介绍3 使用NestJS快速搭建应用,实现CURD操作e-link文档Egg官网NestJS 官网NestJS中文文档(非官方)

十九万里的博客 4742

Java并发

‌Java的java.util.concurrent包‌:提供了一系列线程安全的集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,它们在高并发环境下提供了更好的性能和伸缩性。在使用这些工具类时,需要根据具体的应用场景和需求选择合适的工具类,并遵循良好的并发编程实践。‌锁(Locks)‌:Java提供了更高级的锁机制,如ReentrantLock,它提供了比synchronized更细粒度的锁控制和更多的功能,如锁中断、锁尝试和条件变量。‌线程是并发执行的基本单位‌。

2403_87122707的博客 1208

合成孔径雷达(SAR)的常见工作模式

随着星载SAR技术研究的深入发展,单一的条带模式已经无法满足当今时代的需求,因此许多新的工作模式相继被提出。这些新模式在分辨率和测绘宽度等方面都有所提高,接下来将简要介绍几种常用的工作模式。

m0_51390495的博客 2315

保姆级教程:用PCAN-View监控汽车CAN总线,从硬件连接到报文分析(附快捷键大全)

本文提供了一份详细的PCAN-View使用教程,涵盖从硬件连接到报文分析的全流程,特别适合汽车电子工程师和初学者。内容包括硬件准备、软件安装、核心功能操作、高级技巧及故障排查,并附有实用的快捷键大全,帮助用户高效监控和分析CAN总线数据。

weixin_42542669的博客 497

JAVA高并发总结

JAVA高并发总结

峰爸的博客 1574

Flotherm XT学习历程

大家好,通过半年的热仿真软件学习使用,最终发现最好用,最好上手的软件就是Flotherm XT,对新手非常友好,但由于软件近几年才发布,网上教程非常少,后面我将在这里持续分析软件使用心得,和大家一起学习。

xiaoredred的博客 6261

【高并发】java高并发核心知识

java并发

hiliang521的博客 1019

Android Studio运行报错: failed to load include path ……\platforms\android-35\android.jar的最细解决方案

【代码】Android Studio运行报错: failed to load include path ……\platforms\android-35\android.jar的最细解决方案。

2401_86789230的博客 2万+

Java 并发

2. 实现Runnable接口 实现 Runnable 接口的 run() 方法,使用Thread 类的构造函数传入 Runnable 对象,调用 start() 方法启动线程。 例子: 3. 使用 Callable 和 FutureTask 实现 Callable 接口的 call() 方法,使用FutureTask 包装 Callable 对象,再通过Thread 启动。 例子: 4. 使用线程池-ExecutorService 通过 ExecutorService 提交 Runnable

weixin_45119534的博客 866

第076篇:大涡模拟进阶

大涡模拟(Large Eddy Simulation, LES)是介于直接数值模拟(DNS)和雷诺平均方法(RANS)之间的重要湍流模拟方法。本文深入介绍动态Smagorinsky模型、尺度相似模型、WALE模型和隐式LES等高级亚格子模型。通过Python实现槽道湍流和圆柱绕流的LES求解器,详细展示近壁面处理、动态系数计算和亚格子应力建模技术。大涡模拟,亚格子模型,动态模型,Smagorinsky,WALE模型,尺度相似模型,近壁面处理,槽道湍流LES的核心思想是对Navier-Stokes方程进行空间

2401_87715305的博客 740

Java的并发模型

Java 最基本的并发模型是线程模型,通过 Thread 类来创建和管理线程。每个线程表示一个独立的执行单元,Java 提供了对线程的创建、启动、暂停和终止的控制。线程池是 Java 5 引入的一个并发模型,基于 Executor 框架。线程池可以重用现有的线程,避免频繁的创建和销毁线程,提供更高效的任务执行和管理。包提供了一些原子变量类,它们支持对变量的原子性操作,这些操作不需要使用锁,具有更高的性能。Java 提供了一些专为并发设计的集合类,这些集合类保证在多线程环境下的线程安全。

qq_46130027的博客 1072

GPU硬件架构与微架构深度解析28-稀疏计算:稀疏矩阵计算的硬件支持

本文介绍了GPU加速稀疏计算的关键技术。主要内容包括:1)稀疏数据的存储格式(COO、CSR、CSC)及其特点;2)现代GPU的稀疏计算硬件优化,如NVIDIA的稀疏张量核心和内存访问优化;3)稀疏计算在推荐系统、NLP和科学计算等领域的应用案例;4)性能优化技巧,包括存储格式选择、硬件特性利用和混合精度计算等。文章指出,专用稀疏计算技术可显著提升AI和大规模科学计算的效率,相比传统方法可获得5-10倍的性能提升。

2301_79143213的博客 113

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

java中的并发是什么意思_java中的并发是什么

java中的并发是什么发布时间:2020-06-10 19:20:22来源:亿速云阅读:190作者:鸽子并发是什么?并发:是指在某个时间段内,多任务交替的执行任务。当有多个线程在操作时,把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行。 在一个时间段的线程代码运行时,其它线程处于挂起状。在并发环境下,程序的封闭性被打破,出现以下特点:● 并发程序之间有相互制约的关系。直接制约体现为...

weixin_33205138的博客 2498

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Java并发:整理自《Java并发编程实战》和《Java并发编程的艺术》

声明:Java并发的内容是自己阅读《Java并发编程实战》和《Java并发编程的艺术》整理来的。 图文并茂请戳 思维导图下载请戳 目录 (1)基础概念 (2)线程 (3)锁 (4)同步器 (5)并发容器和框架 (6)Java并发工具类 (7)原子操作类 (8)Executor框架(执行机制) (9)其他 (一).基础概念 1.可见性和原子性 可见性:一个线程...

liuffei的专栏 1884

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

【Java 并发编程】详解

在并发编程中,使用普通的集合类(如 ArrayList、HashMap 等)往往会出现线程安全问题,因为它们在设计时并没有考虑多线程并发访问的情况。Java 提供了一系列并发容器来解决这个问题,这些并发容器在保证高效性能的同时,能够支持多线程安全地进行读写等操作。

架构师之路 1123

Java的并发集合

当需要修改集合时,它们会创建集合的一个副本进行修改,然后替换原来的引用,这样读操作就不会被写操作阻塞,但写操作成本较高。这意味着在写操作时,会创建一个新的数组副本,并在副本上进行修改,而读操作则直接在原始数组上进行。当需要修改集合时,它们会创建集合的一个副本进行修改,然后替换原来的引用,这样读操作就不会被写操作阻塞,但写操作成本较高。通过这种结构,可以在不加锁的情况下进行高效的查找操作。: 接口代表一个线程安全的队列,其特点是可以在队列为空时阻塞获取元素的线程,或者在队列满时阻塞尝试添加元素的线程。

一个写了10年bug的程序员日常笔记。 1556

【Java并发编程 零】Java并发编程看这一篇就够了

本篇Blog为接下来的Java并发编程精华版本,重点知识,如果某个知识点不理解,可以再深入的看本专栏中的其它Blog内容介绍。为了突出重点,本篇Blog以问答的形式展示,将所有的知识点进行串联。 ...

MaoLin Tian's Blog 1091
上一篇: MySQL的表分区
下一篇: SQL Sever数据库中的锁25例
lb8607
博客等级 码龄18年 17粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值