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

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/caidie_huang/article/details/52748973

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

class Apple implements Runnable{
	private int num = 50;//苹果总数

	public void run() {
		for (int i = 0; i < 50; i++) {
			if (num > 0) {
				try {
					//模拟网络延迟
					Thread.sleep(100);
					System.out.println(Thread.currentThread().getName() + "吃了编号为"
							+ num-- + "的苹果");
				} catch (Exception e) {
					e.printStackTrace();
				}
				
			}
		}
	}
	
}
public class AppleEatingImplements {
	public static void main(String[] args) {
		//创建三个线程(三个同学),吃苹果
		Runnable apple = new Apple();
		new Thread(apple,"A童鞋").start();
		new Thread(apple,"B童鞋").start();
		new Thread(apple,"C童鞋").start();
	}

}

以上代码运行结果:


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

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

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

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

方式1:同步代码块

方式2:同步方法

方式3:锁机制(Lock)


方式1:同步代码块

语法:

synchronized(同步锁)

{

     需要同步操作的代码

}

 

同步锁:

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

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

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

//同步代码块
class Apple2 implements Runnable{
	private int num = 100;
	public void run() {
		for(int i = 0; i < 50; i ++){
			//this表示Apple2对象,该对象属于多线程共享的资源
			synchronized(this){
				if(num>0){
					System.out.println(Thread.currentThread().getName()+"吃了"+num-- +"个苹果");
				}
			}
		}
		
	}
}

public class SynchronizedBlockDemo {
	public static void main(String[] args) {
		Runnable a = new Apple2();
		//三个线程表示三个人
		new Thread(a,"小A").start();
		new Thread(a,"小B").start();
		new Thread(a,"小C").start();
		
	}

}

方式2:同步方法:

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

Synchronized public void doWork(){

     ///TODO

}

同步锁是谁:

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

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

 

//同步方法
class Apple3 implements Runnable{
<span style="white-space:pre">	</span>private int num = 50;
<span style="white-space:pre">	</span>public void run() {
<span style="white-space:pre">		</span>for(int i = 0; i < 50; i ++){
<span style="white-space:pre">			</span>try {
<span style="white-space:pre">				</span>doWork();
<span style="white-space:pre">			</span>} catch (InterruptedException e) {
<span style="white-space:pre">				</span>// TODO Auto-generated catch block
<span style="white-space:pre">				</span>e.printStackTrace();
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>synchronized private void doWork() throws InterruptedException {
<span style="white-space:pre">			</span>if(num>0){
<span style="white-space:pre">				</span>System.out.println(Thread.currentThread().getName()+"吃了"+num +"个苹果");
<span style="white-space:pre">				</span>num --;
<span style="white-space:pre">				</span>Thread.sleep(10);
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>
<span style="white-space:pre">		</span>
<span style="white-space:pre">	</span>}
}


public class SynchronizedMethodDemo {
<span style="white-space:pre">	</span>public static void main(String[] args) {
<span style="white-space:pre">		</span>Runnable a = new Apple3();
<span style="white-space:pre">		</span>new Thread(a,"小A").start();//三个线程表示三个人
<span style="white-space:pre">		</span>new Thread(a,"小B").start();
<span style="white-space:pre">		</span>new Thread(a,"小C").start();
<span style="white-space:pre">		</span>
<span style="white-space:pre">	</span>}


}

注意:

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

 

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

 

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

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

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

//锁机制
class Apple4 implements Runnable{
	private int num = 50;
	//创建锁对象
	private final Lock lock = new ReentrantLock();
	public void run() {
		for(int i = 0; i < 50; i ++){
			doWork();
		}
	}
	private void doWork() {
		//进入方法,立马加锁
		lock.lock();//获取锁
		try {
			//注意:if要放到try里,不然num为0时就不进入if中,最后锁就释放不了了
			if(num>0){
				System.out.println(Thread.currentThread().getName()+"吃了"+num +"个苹果");
				num--;
				Thread.sleep(10);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			//释放锁
			lock.unlock();
		}
		
	}
}
public class LockDemo {
	public static void main(String[] args) {
		Runnable a = new Apple4();
		new Thread(a,"小A").start();//三个线程表示三个人
		new Thread(a,"小B").start();
		new Thread(a,"小C").start();
	}
}


展开阅读全文

没有更多推荐了,返回首页