线程同步:解决线程不安全问题

当多个线程并发访问同一个资源对象时,可能会出现线程不安全的问题,比如现有50个苹果,现在有请三个童鞋(小A,小B,小C)上台表演吃苹果.因为A,B,C三个人可以同时吃苹果,此时使用多线程技术来实现这个案例.

 

 
  1. class Apple implements Runnable{

  2. private int num = 50;//苹果总数

  3.  
  4. public void run() {

  5. for (int i = 0; i < 50; i++) {

  6. if (num > 0) {

  7. try {

  8. //模拟网络延迟

  9. Thread.sleep(100);

  10. System.out.println(Thread.currentThread().getName() + "吃了编号为"

  11. + num-- + "的苹果");

  12. } catch (Exception e) {

  13. e.printStackTrace();

  14. }

  15.  
  16. }

  17. }

  18. }

  19.  
  20. }

  21. public class AppleEatingImplements {

  22. public static void main(String[] args) {

  23. //创建三个线程(三个同学),吃苹果

  24. Runnable apple = new Apple();

  25. new Thread(apple,"A童鞋").start();

  26. new Thread(apple,"B童鞋").start();

  27. new Thread(apple,"C童鞋").start();

  28. }

  29.  
  30. }


以上代码运行结果:

 

为什么编号为39的苹果被吃了两次呢?

当A、B线程拿到编号为39的苹果时,打印出来,有一个还没来得及做num--,而有一个做了num减一操作,num还剩38,这时候线程进入睡眠状态。这时候C线程来了,打印38,做减1操作,睡眠……

要解决上述多线程并发访问多一个资源的安全性问题,就必须得保证打印苹果编号和苹果总数减1操作,必须同步完成.即是说,A线程进入操作的时候,B和C线程只能在外等着,A操作结束,A和B和C才有机会进入代码去执行.

解决多线程并发访问资源的安全问题,有三种方式:

方式1:同步代码块

方式2:同步方法

方式3:锁机制(Lock)

 

 

方式1:同步代码块:

语法:

synchronized(同步锁)

{

     需要同步操作的代码

}

 

同步锁:

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制.也称为同步监听对象/同步锁/同步监听器/互斥锁。

实际上,对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁,谁拿到锁,谁就可以进入代码块,其他线程只能在代码块外面等着,而且注意,在任何时候,最多允许一个线程拥有同步锁.

Java程序运行可以使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象.

 
  1. //同步代码块

  2. class Apple2 implements Runnable{

  3. private int num = 100;

  4. public void run() {

  5. for(int i = 0; i < 50; i ++){

  6. //this表示Apple2对象,该对象属于多线程共享的资源

  7. synchronized(this){

  8. if(num>0){

  9. System.out.println(Thread.currentThread().getName()+"吃了"+num-- +"个苹果");

  10. }

  11. }

  12. }

  13.  
  14. }

  15. }

  16.  
  17. public class SynchronizedBlockDemo {

  18. public static void main(String[] args) {

  19. Runnable a = new Apple2();

  20. //三个线程表示三个人

  21. new Thread(a,"小A").start();

  22. new Thread(a,"小B").start();

  23. new Thread(a,"小C").start();

  24.  
  25. }

  26.  
  27. }

 

方式2:同步方法:

使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着.

Synchronized public void doWork(){

     ///TODO

}

同步锁是谁:

      对于非static方法,同步锁就是this.  

      对于static方法,我们使用当前方法所在类的字节码对象(Apple2.class).

 

 
  1. //同步方法

  2. class Apple3 implements Runnable{

  3. <span style="white-space:pre"> </span>private int num = 50;

  4. <span style="white-space:pre"> </span>public void run() {

  5. <span style="white-space:pre"> </span>for(int i = 0; i < 50; i ++){

  6. <span style="white-space:pre"> </span>try {

  7. <span style="white-space:pre"> </span>doWork();

  8. <span style="white-space:pre"> </span>} catch (InterruptedException e) {

  9. <span style="white-space:pre"> </span>// TODO Auto-generated catch block

  10. <span style="white-space:pre"> </span>e.printStackTrace();

  11. <span style="white-space:pre"> </span>}

  12. <span style="white-space:pre"> </span>}

  13. <span style="white-space:pre"> </span>}

  14. <span style="white-space:pre"> </span>synchronized private void doWork() throws InterruptedException {

  15. <span style="white-space:pre"> </span>if(num>0){

  16. <span style="white-space:pre"> </span>System.out.println(Thread.currentThread().getName()+"吃了"+num +"个苹果");

  17. <span style="white-space:pre"> </span>num --;

  18. <span style="white-space:pre"> </span>Thread.sleep(10);

  19. <span style="white-space:pre"> </span>}

  20. <span style="white-space:pre"> </span>

  21. <span style="white-space:pre"> </span>

  22. <span style="white-space:pre"> </span>}

  23. }

  24.  
  25.  
  26. public class SynchronizedMethodDemo {

  27. <span style="white-space:pre"> </span>public static void main(String[] args) {

  28. <span style="white-space:pre"> </span>Runnable a = new Apple3();

  29. <span style="white-space:pre"> </span>new Thread(a,"小A").start();//三个线程表示三个人

  30. <span style="white-space:pre"> </span>new Thread(a,"小B").start();

  31. <span style="white-space:pre"> </span>new Thread(a,"小C").start();

  32. <span style="white-space:pre"> </span>

  33. <span style="white-space:pre"> </span>}

  34.  
  35.  
  36. }

注意:

不要使用synchronized修饰run方法,修饰之后,某一个线程就执行完了所有的功能. 好比是多个线程出现串行.

 

解决方案:把需要同步操作的代码定义在一个新的方法中,并且该方法使用synchronized修饰,再在run方法中调用该新的方法即可.

 

实际上,同步代码块和同步方法差不了多少,在本质上是一样的,两者都用了一个关键字synchronized,synchronized保证了多线程并发访问时的同步操作,避免线程的安全性问题,但是有一个弊端,就是使用synchronized的方法/代码块的性能比不用要低一些,因此如果要用synchronized,建议尽量减小synchronized的作用域。

方式3:同步锁(锁机制)

Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象.

 
  1. //锁机制

  2. class Apple4 implements Runnable{

  3. private int num = 50;

  4. //创建锁对象

  5. private final Lock lock = new ReentrantLock();

  6. public void run() {

  7. for(int i = 0; i < 50; i ++){

  8. doWork();

  9. }

  10. }

  11. private void doWork() {

  12. //进入方法,立马加锁

  13. lock.lock();//获取锁

  14. try {

  15. //注意:if要放到try里,不然num为0时就不进入if中,最后锁就释放不了了

  16. if(num>0){

  17. System.out.println(Thread.currentThread().getName()+"吃了"+num +"个苹果");

  18. num--;

  19. Thread.sleep(10);

  20. }

  21. } catch (Exception e) {

  22. e.printStackTrace();

  23. }finally{

  24. //释放锁

  25. lock.unlock();

  26. }

  27.  
  28. }

  29. }

  30. public class LockDemo {

  31. public static void main(String[] args) {

  32. Runnable a = new Apple4();

  33. new Thread(a,"小A").start();//三个线程表示三个人

  34. new Thread(a,"小B").start();

  35. new Thread(a,"小C").start();

  36. }

  37. }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值