600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > Java多线程与高并发-synchronized与volatile

Java多线程与高并发-synchronized与volatile

时间:2020-12-05 03:51:37

相关推荐

Java多线程与高并发-synchronized与volatile

synchronized关键字对某个对象加锁

publicclassT{privateintcount=10;privateObjecto=newObject();publicvoidm(){synchronized(o){//任何线程要执行下面的代码,必须先拿到o的锁count--;System.out.println(Thread.currentThread().getName()+"count="+count);}}}

每次都生成一个新对象不方便,可以直接锁定当前对象自身

publicclassT{privateintcount=10;publicvoidm(){synchronized(this){//任何线程要执行下面的代码,必须先拿到this的锁count--;System.out.println(Thread.currentThread().getName()+"count="+count);}}}

如果一段代码在开始执行的时候就要锁订一个对象,直到结束时才释放,那么可以直接在方法上加上synchronized关键字

publicclassT{privateintcount=10;publicsynchronizedvoidm(){//等同于在方法的代码执行时要synchronized(this)count--;System.out.println(Thread.currentThread().getName()+"count="+count);}}

互斥锁;锁定的是一个对象

对于静态方法如何加锁?

publicclassT{privatestaticintcount=10;publicsynchronizedstaticvoidm(){//这里等同于synchronized(yxxy.c_004.T.class)count--;System.out.println(Thread.currentThread().getName()+"count="+count);}publicstaticvoidmm(){synchronized(T.class){//考虑一下这里写synchronized(this)是否可以?不行,静态方法不能使用关键字thiscount--;}}}

分析一下程序的输出

publicclassTimplementsRunnable{privateintcount=10;public/*synchronized*/voidrun(){count--;System.out.println(Thread.currentThread().getName()+"count="+count);}publicstaticvoidmain(String[]args){Tt=newT();for(inti=0;i<5;i++){newThread(t,"THREAD"+i).start();}}}

很显然,如果不使用synchronized关键字,那么5个线程访问到count变量就不能保证同步。

同步方法和非同步方法是否可以同时调用?

publicclassT{publicsynchronizedvoidm1(){System.out.println(Thread.currentThread().getName()+"m1start...");try{Thread.sleep(10000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"m1end");}publicvoidm2(){try{Thread.sleep(5000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"m2");}publicstaticvoidmain(String[]args){Tt=newT();/*newThread(()->t.m1(),"t1").start();newThread(()->t.m2(),"t2").start();*/newThread(t::m1,"t1").start();newThread(t::m2,"t2").start();/*newThread(newRunnable(){@Overridepublicvoidrun(){t.m1();}});*/}}

观察上面的程序执行结果,说明是可以的

如果对业务写方法加锁,而对读方法不加锁就会导致脏读问题:

publicclassAccount{Stringname;doublebalance;publicsynchronizedvoidset(Stringname,doublebalance){this.name=name;/*try{Thread.sleep(2000);}catch(InterruptedExceptione){e.printStackTrace();}*/this.balance=balance;}public/*synchronized*/doublegetBalance(Stringname){returnthis.balance;}publicstaticvoidmain(String[]args){Accounta=newAccount();newThread(()->a.set("zhangsan",100.0)).start();try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(a.getBalance("zhangsan"));try{TimeUnit.SECONDS.sleep(2);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(a.getBalance("zhangsan"));}}

synchronized获得的锁是可重入的

publicclassT{synchronizedvoidm1(){System.out.println("m1start");try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}m2();}synchronizedvoidm2(){try{TimeUnit.SECONDS.sleep(2);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("m2");}}

一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁。也就是说synchronized获得的锁是可重入的。

子类的同步方法调用父类的同步方法,锁定的一定是当前对象,即this

publicclassT{synchronizedvoidm(){System.out.println("mstart");try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("mend");}publicstaticvoidmain(String[]args){newTT().m();}}classTTextendsT{@Overridesynchronizedvoidm(){System.out.println("childmstart");super.m();System.out.println("childmend");}}

程序执行过程中如果出现异常,锁会被默认释放:

publicclassT{intcount=0;synchronizedvoidm(){System.out.println(Thread.currentThread().getName()+"start");while(true){count++;System.out.println(Thread.currentThread().getName()+"count="+count);try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}if(count==5){inti=1/0;//此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续System.out.println(i);}}}publicstaticvoidmain(String[]args){Tt=newT();Runnabler=newRunnable(){@Overridepublicvoidrun(){t.m();}};newThread(r,"t1").start();try{TimeUnit.SECONDS.sleep(3);}catch(InterruptedExceptione){e.printStackTrace();}newThread(r,"t2").start();}}

程序在执行过程中,如果出现异常,默认情况锁会被释放。所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。因此要非常小心的处理同步业务逻辑中的异常

注意:synchronized同步代码块中的语句越少越好

volatile关键字

publicclassT{/*volatile*/booleanrunning=true;//对比一下有无volatile的情况下,整个程序运行结果的区别voidm(){System.out.println("mstart");while(running){/*try{TimeUnit.MILLISECONDS.sleep(10);}catch(InterruptedExceptione){e.printStackTrace();}*/}System.out.println("mend!");}publicstaticvoidmain(String[]args){Tt=newT();newThread(t::m,"t1").start();try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}t.running=false;}}

volatile无法保证原子性,只能保证可见性,它不能替代synchronized关键字

publicclassT{volatileintcount=0;voidm(){for(inti=0;i<10000;i++)count++;}publicstaticvoidmain(String[]args){Tt=newT();List<Thread>threads=newArrayList<Thread>();for(inti=0;i<10;i++){threads.add(newThread(t::m,"thread-"+i));}threads.forEach((o)->o.start());threads.forEach((o)->{try{o.join();}catch(InterruptedExceptione){e.printStackTrace();}});System.out.println(t.count);}}

上面程序的输出永远小于100000,因为count++的原子性没有得到保证。

上面的程序可以改为

publicclassT{/*volatile*/intcount=0;synchronizedvoidm(){for(inti=0;i<10000;i++)count++;}publicstaticvoidmain(String[]args){Tt=newT();List<Thread>threads=newArrayList<Thread>();for(inti=0;i<10;i++){threads.add(newThread(t::m,"thread-"+i));}threads.forEach((o)->o.start());threads.forEach((o)->{try{o.join();}catch(InterruptedExceptione){e.printStackTrace();}});System.out.println(t.count);}}

解决同样的问题的更高效的方法,使用AtomXXX类

publicclassT{/*volatile*///intcount=0;AtomicIntegercount=newAtomicInteger(0);/*synchronized*/voidm(){for(inti=0;i<10000;i++)//ifcount.get()<1000count.incrementAndGet();//count++}publicstaticvoidmain(String[]args){Tt=newT();List<Thread>threads=newArrayList<Thread>();for(inti=0;i<10;i++){threads.add(newThread(t::m,"thread-"+i));}threads.forEach((o)->o.start());threads.forEach((o)->{try{o.join();}catch(InterruptedExceptione){e.printStackTrace();}});System.out.println(t.count);}}

AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的

注意避免将锁定对象的引用变为另外的对象

publicclassT{Objecto=newObject();voidm(){synchronized(o){while(true){try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName());}}}publicstaticvoidmain(String[]args){Tt=newT();//启动第一个线程newThread(t::m,"t1").start();try{TimeUnit.SECONDS.sleep(3);}catch(InterruptedExceptione){e.printStackTrace();}//创建第二个线程Threadt2=newThread(t::m,"t2");t.o=newObject();//锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程2将永远得不到执行机会t2.start();}}

锁定某对象o,如果o的属性发生改变,不影响锁的使用。但是如果o变成另外一个对象,则锁定的对象发生改变。应该避免将锁定对象的引用变成另外的对象。

不要以字符串常量作为锁定对象

publicclassT{Strings1="Hello";Strings2="Hello";voidm1(){synchronized(s1){}}voidm2(){synchronized(s2){}}}

不要以字符串常量作为锁定对象。在下面的例子中,m1和m2其实锁定的是同一个对象。这种情况还会发生比较诡异的现象,比如你用到了一个类库,在该类库中代码锁定了字符串“Hello”, 但是你读不到源码,所以你在自己的代码中也锁定了"Hello",这时候就有可能发生非常诡异的死锁阻塞,因为你的程序和你用到的类库不经意间使用了同一把锁

声明:文章来自网络,版权归原作者所有!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。