当前位置: 首页 > 后端技术 > Java

Java多线程并发很容易理解和解释

时间:2023-04-01 19:05:07 Java

00。线程或进程现代操作系统以进程为调度单位使用cpu,也有以线程为单位的,如java;也就是说,无论是进程还是线程,都是一组数据结构。这种数据结构最初是为了方便CPU使用而设计的;因此,如果我们创建多个线程或fork多个进程会怎样?因为本质上是数据,所以会占用内存资源,但不会占用cpu资源。它只会在运行时占用cpu资源。理解这一点很重要。01.了解Java程序的运行过程。Java程序是通过java命令运行的。实际上,java命令启动了一个jvm进程;jvm是java虚拟机,jvm翻译java字节码,找到主入口,启动java线程,从主入口执行java程序;通常执行main的java线程就是主线程。主线程在执行java代码的过程中,可能会创建一个新的线程。新创建的线程启动后,会与主线程并行执行。即使在主线程退出后,其他创建的线程可能仍在运行。02.程序运行的本质无论是什么语言的程序,最终都会转化为机器010101...代码执行,所以所有的程序都可以抽象为执行者和任务列表,而执行者最终体现在硬件上.最常见的就是CPU,通用的就是处理器。在软件编程语言层面,执行者就是常说的进程/线程。另一个是任务列表。要做工作,执行者必须有一个任务列表。程序代码可以看作是一个任务列表,是人与机器进行交流的语言。要驱动机器工作,人必须说机器能听懂的语言。这是编程语言。一段代码,为什么会出现多线程的问题。例如,一段代码就像是烹饪菜单的过程。假设你想用同一种菜做10道菜。为了快,需要10个人同时做饭。想象一下,10个人同时使用同一个锅。炒出来的菜可以吃吗?.本菜单的烹饪步骤只是一个代码。如果10个厨师同时做饭,10个线程并发。按理说并发是没有问题的,但是因为共用一个锅,问题就出现了。这就是多线程的问题。03.多线程问题示例——计算固定字符串的md5值publicclassTest{privatestaticMessageDigestdigest;publicstaticvoidmain(String[]args)throwsException{try{digest=MessageDigest.getInstance("md5");}catch(Exceptione){e.printStackTrace();}calcMd5();for(inti=0;i<10;i++){newThread(()->{calcMd5();}).start();}}privatestaticvoidcalcMd5(){Stringstr="test";byte[]res=digest.digest(str.getBytes(Charset.forName("utf-8")));StringBuildersb=newStringBuilder();for(byteb:res){sb.append(String.format("%02x",b));}System.out.println(Thread.currentThread().getName()+":"+sb.toString());}}测试的md5值为098f6bcd4621d373cade4e832627b4f6,但是上面的代码,多线程执行,部分线程计算结果有问题,一次执行的结果如下各有以下方法大厨亲自下锅,即使用线程局部变量公共类测试{publicstaticvoidmain(String[]args)throwsException{MessageDigestdigest;尝试{digest=MessageDigest.getInstance("md5");}catch(Exceptione){e.printStackTrace();}calcMd5();for(inti=0;i<10;i++){newThread(()->{calcMd5();}).start();}}privatestaticvoidcalcMd5(){Stringstr="test";MessageDigest摘要;尝试{digest=MessageDigest.getInstance("md5");}catch(Exceptione){e.printStackTrace();返回;}byte[]res=digest.digest(str.getBytes(Charset.forName("utf-8")));StringBuildersb=newStringBuilder();for(byteb:res){sb.append(String.format("%02x",b));}System.out.println(Thread.currentThread().getName()+":"+sb.toString());}}还是每个厨师一个锅,但是这个锅不是厨师所有的,而是餐厅提供的,也就是ThreadLocalpublicstaticvoidmain(String[]args)throwsException{calcMd5();for(inti=0;i<10;i++){newThread(()->{calcMd5();}).start();}}privatestaticMessageDigestgetMd5(){if(digestThreadLocal.get()==null){try{MessageDigestdigestTmp=MessageDigest.getInstance("md5");digestThreadLocal.set(digestTmp);}catch(Exceptione){e.printStackTrace();}}返回digestThreadLocal.get();}privatestaticvoidcalcMd5(){Stringstr="test";byte[]res=getMd5().digest(str.getBytes(Charset.forName("utf-8")));StringBuildersb=newStringBuilder();for(byteb:res){sb.append(String.format("%02x",b));}System.out.println(Thread.currentThread().getName()+":"+sb.toString());}}也可以共用一个锅,但是要保证每次只有一个厨师用,用完了再去下一个,也就是用锁publicclassTest{privatestaticMessageDigestdigest;privatestaticReentrantLocklock=newReentrantLock();publicstaticvoidmain(String[]args)throwsException{try{digest=MessageDigest.getInstance("md5");}catch(Exceptione){e.printStackTrace();}calcMd5();for(inti=0;i<10;i++){newThread(()->{calcMd5();}).start();}}privatestaticvoidcalcMd5(){Stringstr="test";byte[]res=null;锁.锁();试试{res=digest.digest(str.getBytes(Charset.forName("utf-8")));}finally{lock.unlock();}StringBuildersb=newStringBuilder();for(byteb:res){sb.append(String.format("%02x",b));}System.out.println(Thread.currentThread().getName()+":"+sb.toString());}}总结,以上三种方案一般最好使用局部变量或者ThreadLocal,锁不应该作为最后的手段