差不多做了一段时间的java开发了,然后其实项目上高并发用到的并不多。就只有自己来学习一下了,网上搜了下资料,然后自己总结了一下。下面开始正题。

现目前许多项目都会涉及到高并发的问题,先来说下线程和进程。

进程:操作系统中正在运行的程序。

线程:是进程中的一个执行流程;

用一句话来说,解决高并发问题,就是解决 多线程对某个资源的有序访问和修改。避免脏读,幻读等。通常来说数据库也可以解决这类的问题,表锁,行锁,对其数据进行读锁或者写锁操作。这些都是题外话了,后面会针对数据库的操作写一篇博客。

言归正传。

现在涉及两个简单的场景:就用卖票为例吧

1、两个窗口,每个窗口有100张车票。互不干扰

2、两个窗口一起卖100张票,卖完为止。

在此之前,使用线程的方法基本上常用的就是继承Thread,实现runnable。

先说第一种情况吧,我们先继承Thread

public class TicketsThread extends Thread{
    //设置总票数
    private int num = 100;

    @Override
    public void run() {
      //多线程中建议使用while
       while (true)
       {
           if(num>0)
           {
               num--;
               //Thread.currentThread().getName()得到当前线程的名称
               System.out.println(Thread.currentThread().getName()+"卖了张票,还剩"+num);
           }
       }
    }
}

然后写一个test去操作它

public static void main(String args[])
{
    //分别卖一百张票
    //窗口1
    TicketsThread threadTest1 = new TicketsThread();
    //窗口2
    TicketsThread threadTest2 = new TicketsThread ();
    threadTest1.setName("窗口1");
    threadTest2.setName("窗口2");
    threadTest1.start();
    threadTest2.start();
}

ok,然后执行

fastapi多线程并发gpu 多线程并发场景_fastapi多线程并发gpu

然后说第二种情况,同用100张票。

之前我们说过了  解决并发问题就是为了  解决多线程对某个资源的有序访问和修改!!!

我们接下来就是为了解决这个问题 : 解决多线程对某个资源的有序访问和修改!!!(记住这句话)

有的同学可能猜到了,我们会使用同步锁synchronized,那么恭喜你,猜错了。这里我想先说下volatile。

我相信,有的同学会使用volatile来修饰这个变量,但是有些时候volatile并不能解决我们刚刚说的问题(解决多线程对某个资源的有序访问和修改),重要的事情说三遍,记住这句话,句话,话!

我们先把代码展示出来

public class TicketsVolatile implements Runnable{

    private volatile  int num = 100;


    @Override
    public void run() {
        while (true)
        {
            if(num>0)
            {
                num--;
                System.out.println(Thread.currentThread().getName()+"卖出一张票,还剩"+num);
            }
        }
    }
} 


 public static void main(String args[])
  {
        //volatile
        TicketsVolatile ticketsVolidate = new TicketsVolatile();
        Thread thread1 = new Thread(ticketsVolidate);
        Thread thread2 = new Thread(ticketsVolidate);
        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread1.start();
        thread2.start();
   }

运行:

fastapi多线程并发gpu 多线程并发场景_高并发_02

这里说明一下,每次运行的结果肯定不同。但是我要的结果还是得到了,就是没办法解决我们刚刚说的需要解决的问题。

这是为什么呢?下面是网上说的volatile的作用。

fastapi多线程并发gpu 多线程并发场景_高并发_03

为什么还剩2张的时候,突然变成了15呢?

这里简单的说一下,为什么,volatile在多线程的时候不能满足要求。

注意我划线的地方,什么叫 可见性。

简单来说,就是 我们住在一起,冰箱里有10个苹果,我吃一个,你马上就知道了还剩9个。你再吃一个,我马上也能知道所剩的个数是9-1=8个。这里有同学肯定会问了,那不是应该是正确的吗,为什么会出现这种问题

因为 volatile只能满足可见性,但是没办法满足原子性(只针对在操作单一操作的情况可以保证原子性,只要是复合操作那么没办法保证(复合操作:i++,i=j+1等)(单一普通操作:i=i));

比如说num--;它会分三个步骤去执行:

  1. 从内存中(工作内存或者主内存)读取num的值。
  2. 进行num =num-1运算
  3. 把num的值写回到内存中

问题就在于这三个操作,处理器可能不会一下就把他们执行完毕。有两种情况:1、可能说,A线程执行了1,2之后,B线程这时候获得了CPU的执行权。然后B线程执行完了之后,A线程通过程序计数器的帮助,将3这个步骤执行完。这时候就会出现这种情况:

但是请记住这句话:


volatile 写的内存语义如下:


当写一个 volatile 变量时, JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。


volatile 读的内存语义如下:


当读一个 volatile 变量时, JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。


 


 


1、窗口1卖出一张票,还剩98张 窗口2卖出一张票,还剩98张 

fastapi多线程并发gpu 多线程并发场景_同步锁_04

这种情况为什么会出现呢?是因为,线程2将100-1=99,然后阻塞。接着线程1从主内存中获取值为99,然后99-1=98,(如上线语义定义所述)回写到主内存中。然后打印出来。此时主内存中的值就是98。然后线程2从主内存中读值98,然后打印。

2、出现显示异常的问题

fastapi多线程并发gpu 多线程并发场景_fastapi多线程并发gpu_05

fastapi多线程并发gpu 多线程并发场景_fastapi多线程并发gpu_06

这种情况出现的原因是:当线程1获得执行权之后,16-1=15;然后进入了打印方法中,

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

此时,已经进来了。但是还未执行synchronized这句话。CPU执行权就又被线程2夺走了,但是此时已经回写了主内存了。等到线程1再次获取cpu执行权时,由于打印语句的结构,它已经进入了。所以,他们不会再去主内存中取值了,因为在它让出cpu执行权之前,它已经读取过了。这个地方很绕,大家只需要记住,编译器执行的语句是通过编译之后的语句,不是像我们写的代码这样,一行可能对应编译器的多行。这就是为什么会出现这些问题的。如果理解不了的,大家请记住一点:volatile只适用于一写多读的情况,如果一个共享变量会被多个线程操作修改,那么一定不要使用它。

fastapi多线程并发gpu 多线程并发场景_fastapi多线程并发gpu_07

 

要记住的是:volatile变量写操作之后执行,也不一定会回写了主内存才会被切换。也可能在回写前,就被切换了!

这就是为什么,volatile有局限性,不建议使用在线程并发的操作中。

然后同步锁,最普遍使用的一个操作synchronized

先展示代码

public class TicketsRunnable implements Runnable{

    private int count = 100;

    private Object lock = new Object();

    @Override
    public void run() {
        while (true)
        {
            //这里使用this也可以
            synchronized (lock)
            {
                if(count>0)
                {
                    count--;
                    System.out.println(Thread.currentThread().getName()+"卖了张票,还剩"+count);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }
}

 

public static void main(String args[])
   {
        //一起卖一百张票 同步锁
        TicketsRunnable ticketsRunnable = new TicketsRunnable();
        Thread thread1 = new Thread(ticketsRunnable);
        Thread thread2 = new Thread(ticketsRunnable);
        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread1.start();
        thread2.start();
    }

运行代码:

fastapi多线程并发gpu 多线程并发场景_fastapi多线程并发gpu_08

现在就可以了,这是因为

synchronized 既可以满足可见性,又可以满足有序性;

但是这里提一句,不用把不需要解决高并发的代码也扔到synchronized中,因为会大大影响性能。

OK,that's all;

接下来会把jvm原理的博客补齐,方便大家理解这模块的知识。

大家有什么问题可以提出来,我们一起探讨