多线程之间实现同步

来源:互联网 发布:淘宝店铺点击图片链接 编辑:程序博客网 时间:2024/06/06 17:32

2.1 为什么有线程安全问题?

     当多个线程同时共享,同一个全局变量或静态变量,做的操作时,可能会发生数据冲突问题,
也就是线程安全问题。做操作是不会发生数据冲突问题。

多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的。线程安全问题多是由全局变量和静态变量引起的,当多个线程对共享数据只执行读操作,不执行写操作时,一般是线程安全的;当多个线程都执行写操作时,需要考虑线程同步来解决线程安全问题。(写操作:对数据修改或者删除,读操作:只是获取数据,查询数据是没有任何问题的)

当什么时候会出现线程不安全问题?

比如我们定义了一个全局变量,比如上面的count,当多个线程共享同一个全局变量或者静态变量的时候可能会受到其他线程的干扰,引起线程安全问题。

什么是同步代码块?

使用synchronized关键字包裹起来,传个参数,这个参数就是锁。

为什么要在后面传个关键字?

上面线程同步的效果是怎么实现的呢?Java中任意的对象都可以作为一个监听器(monitor),监听器可以被上锁和解锁,在线程同步中称为同步锁,且同步锁在同一时间只能被一个线程所持有。上面的obj对象就是一个同步锁,

synchronized (expression){

2     //需要同步的代码

3 }

其中,expression必须是一个引用类型的变量,这里我们可以理解为任意的一个Java对象,否则编译出错。

显然多个线程检查的都是一个新的对象,不同的同步锁对不同的线程不具有排他性,不能实现线程同步的效果,这时候线程同步就失效了。所以线程同步的一个前提:线程同步锁对多个线程必须是互斥的,即多个线程需要使用同一个同步锁。第一段代码中obj对象被多个线程共享,能够实现同步。

 



案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。

package ThreadTrain;/** * 什么是线程不安全问题,就是当多个线程,共享同一个数据,可能有一个线程的语句没有执行完, * 会受到另一个线程的干扰! * 解决线程安全问题:使用锁。 * 什么是线程同步,就是当的多个线程共享同一个数据不会受到其他线程干扰! * @author Administrator * @classDesc: 功能描述:(多线程之买火车票案例-展示线程不安全) * */class Dog implements Runnable {/** * 现在有100张火车票 */private static  int count = 100;    private Object oj =new Object();/** * 每个线程来抢票时,对火车票减减。 */@Override//synchronizedpublic  void run() {//模拟一直有人在抢票while (true) {if (count > 0) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("threadId:" + Thread.currentThread().getId() + "--" + count--);}}}}public class ThreadTest {public static void main(String[] args) { Dog dog = new Dog(); Thread thread = new Thread(dog); Thread thread1 = new Thread(dog); thread.start(); thread1.start();}}


出现抢到0张票的问题。

问题产生的原因

首先我们想到是因为两个线程共享了数据t。如果多个线程不涉及数据共享,各自执行自己的代码,就不会出现这个问题。

在线程不安全的单例模式中,就涉及到这个问题。

①多个线程共享了数据。

如果我们不判断t>0,直接打印t--,或者只判断t>0,不执行t--,只要循环不结束,程序不终止,从逻辑上来说,程序也没有问题

②在线程任务中设计对共享数据的操作(这里的操作包括①判断t>0;②执行t--),一个线程在操作共享数据的时候,其他的

线程也操作了共享数据。

这时候就可能造成数据出错。

总结来说,多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果

的二义性,就可以称作是线程安全的。线程安全问题多是由全局变量和静态变量引起的,当多个线程对共享数据只

执行读操作,不执行写操作时,一般是线程安全的;结论发现,当多个线程都执行写操作时,需要考虑线程同步来解决线程安全问题。

画图演示




三、 解决办法:

    问:如何解决多线程之间线程安全问题?

答:使用多线程之间同步或使用锁(lock)。

问:为什么使用线程同步或使用锁能解决线程安全问题呢?

答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,

      让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

问:什么是多线程之间同步?

答:当多个线程共享同一个资源,不会受到其他线程的干扰。

①.使用同步代码块

什么是同步代码块?

答:就是将可能会发生线程安全问题的代码,给包括起来。

synchronized(同一个数据){

 可能会发生线程冲突问题

}

  代码样例:

private Object mutex = new Object();// 自定义多线程同步锁public void sale() {synchronized (mutex) {if (trainCount > 0) {try {Thread.sleep(10);} catch (Exception e) {}System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");trainCount--;}}}


②.synchronized同步方法

什么叫同步方法?  

除了synchronized代码块,synchronized关键字还可以修饰方法,让该方法进行线程同步,效果跟同步代码块一样。

 代码样例

 public synchronized void run() {          while (true) {              if (t > 0) {                  try {                      Thread.sleep(100);                      System.out.println("当前线程:" + Thread.currentThread().getName() + "---" + t--);                  } catch (InterruptedException e) {}              }          }     }

这时候synchronized后面没有了expression,从哪儿获取同步锁呢?
·        对于实例的同步方法,使用this即当前实例对象,如上面的dog;
·        对于静态的同步方法,使用当前类的字节码对象,如上面的Dog.class。
也就是说使用同步方法的话,同步锁只能是this或者当前类的字节码对象。所以根据同步锁必须互斥的前提,如果同时使用synchronized代码块和synchronized方法对同一个共享资源进行线程同步,synchronized代码块的同步锁也必须跟synchronized方法一样(要么是this,要么是类的字节码对象)。
同步代码块和同步方法的区别
两者的区别主要体现在同步锁上面。对于实例的同步方法,因为只能使用this来作为同步锁,如果一个类中需要使用到多个锁,为了避免锁的冲突,必然需要使用不同的对象,这时候同步方法不能满足需求,只能使用同步代码块(同步代码块可以传入任意对象);或者多个类中需要使用到同一个锁,这时候多个类的实例this显然是不同的,也只能使用同步代码块,传入同一个对象。


③静态同步函数
答:什么是静态同步函数?
方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
静态的同步函数使用的锁是  该函数所属字节码文件对象 
可以用 getClass方法获取,也可以用当前  类名.class 表示。
 

     代码样例:

synchronized (ThreadTrain.class) {System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");trainCount--;try {Thread.sleep(100);} catch (Exception e) {}}

总结:
synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件


package ThreadTrain;class Dog2 implements Runnable {/** * 现在有100张火车票 */private static  int count = 100;    private Object oj =new Object();/** * 每个线程来抢票时,对火车票减减。 */@Override//synchronizedpublic  void run() {//模拟一直有人在抢票while (true) {show2();}}/** * 为什么我使用synchronized放在方法上就会实现同步呢? * 对于实例的同步方法,使用this即当前实例对象,如上面的dog; * 加上synchronized防止线程不安全 * 第1种方法:使用同步代码块 */   public  void show1(){synchronized (this) {if (count > 0) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("threadId:" + Thread.currentThread().getId() + "--" + count--);}}}//第2种方法:使用同步函数public synchronized void show(){if (count > 0) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("threadId:" + Thread.currentThread().getId() + "--" + count--);}}  //第3种方法:静态同步函数public static synchronized void show2(){//synchronized (this) {if (count > 0) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("threadId:" + Thread.currentThread().getId() + "--" + count--);}//}}}public class ThreadTest2 {public static void main(String[] args) {Dog2 dog = new Dog2(); Thread thread = new Thread(dog); Thread thread1 = new Thread(dog); thread.start(); thread1.start();}}

三、多线程死锁
  
就是在多线程中,多个线程持有的锁,不释放,就会导致死锁。
就是在多线程中在同步中嵌套同步
演示代码


package ThreadTrain;/** * 死锁演示 *  * @author zzq * */public class Deadlock implements Runnable {public boolean flag = true;public Deadlock(boolean flag) {this.flag = flag;}@Overridepublic void run() {if (flag) {while (true) {synchronized (MyLock.MYLOCKA) {System.out.println("... if a");synchronized (MyLock.MYLOCKB) {System.out.println("... if b");}}}} else {while (true) {synchronized (MyLock.MYLOCKB) {System.out.println("... else a");synchronized (MyLock.MYLOCKA) {System.out.println("... else b");}}}}}}class MyLock {public static String MYLOCKA = "a";public static String MYLOCKB = "b";public static void main(String[] args) {Deadlock deadlock1 = new Deadlock(true);Deadlock deadlock2 = new Deadlock(false);Thread thread1 = new Thread(deadlock1);Thread thread2 = new Thread(deadlock2);thread1.start();thread2.start();}}

演示结果


停止掉了,不执行代码。


什么是线程安全?
当多个线程共享同一个变量或者数据的时候会造成其他线程干扰,会导致线程不安全问题。
怎样实现同步?
使用同步代码块,即加上synchronized这把锁,synchronized有两种方式,同步代码块和同步方法,
同步代码块是自己自定义锁的关键字,而同步方法是默认this关键字

原创粉丝点击