聊聊java并发那些事情

随风

随着摩尔定律日渐失效,多核心编程已经是不得不面对的现实。而java从一开始就设计成了一种支持多线程的语言。那么我们如何使用java来进行多线程程序的编写呢?很简单,一般来说,在java开启一个多线程,我们有三种方法,这几种方法使用场合不一样,并没有孰优孰劣。

1.继承Thread类,这种方法很简单,但是java是单继承的,这阻碍了你业务的继承扩展,因此并不适用于需要继承扩展的场合。

2.实现Runnable接口,这种方法比较灵活而且简单。后面文章也主要使用这种方法。这种方法只需要实现run方法就可以轻松创建一个线程。

3.实现Callable接口,这种方法功能最为强大,可以在任务结束后返回值。call方法还能够抛出异常。而run方法并不能返回值。

下面是创建线程的简单例子。

package com.company;



class MyThread2 implements Runnable{
    private static int count = 0;// 计数器

    @Override
    public void run() {
       synchronized (this) {
           ++count;

           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(count);
       }
    }
}
public class test {
    public static void main(String args[]){
         MyThread2 myThread2=new MyThread2();
         Thread t1=new Thread(myThread2,"t1");
         Thread t2=new Thread(myThread2,"t2");
         Thread t3=new Thread(myThread2,"t3");
         t1.start();
         t2.start();
         t3.start();
    }
}

可以看出创建一个线程还是相当的简单的,但令人头疼的其实是线程同步问题。上述代码中++count并不是一个atomic操作。实际上包含了查询,+1和赋值操作,这样的结果就是在并发中会产生所谓的线程同步问题,读者可以试着把同步字段删除。这将会导致结果为333,这与设计的初衷就发生了违背。为什么会这样呢?原因很简单,sleep的时候线程将会被挂起,这样其它线程就会相继进行++count操作然后也被挂起。最后输出的当然是最终的数值3。因此如果我们需要使其线程同步的话,我们需要加上互斥锁,通过锁的竞争确保其同步。但这样的代价是多线程实际上退化了。这样就导致了性能实际上没有提高,反而由于上下文切换等下降了。

可能读者会觉得这样一段小代码没有什么实际用途,其实这种想法是错误的,java springboot controller实际上是单例多线程的,因此如果我们在@controller下使用全局变量将会导致跟上面一样的错误。如果我们做的是购物系统,那么很遗憾,你这个系统将会产生超卖问题。当有人可能会说,我们呢平时crud也没感觉要加锁,其实数据库的事务本来就是atomic的,所以并不需要加锁。因此我们技术人要感谢前人的努力,才使得现在做一个web应用如此简单,但是简单的背后却是tomcat和spring全家桶这样的大黑箱。如果我们不努力研究黑箱的本身,那么最终我们就只能是一个crud程序猿。

最后卖个广告,创万联新一代物联网平台正在火热建设中,其中包含了大量的优秀特性。更安全,接口更加规范,极大提高了“柔性”程度。平台跟项目绑定的时代也许将要彻底告别,我们欢迎各位开源人士的加入,你们的加入是我们组织的福气。让我们共同迎接aiot和数字孪生的美好未来。