Java基础——多线程的安全问题
2013-10-16 00:34
567 查看
首先用一个例子来引入,多线程中常见的线程安全问题:
我们自定义一个线程类实现Runnable接口:
线程用于逐个字符打印str字符串;
我们启动了两个线程打印不同的字符串。按照正常来说,我们应该每次都会打印完整的“AAA-BBB”“CCC-DDD”字符串,但是结果却是:
之所以会发生这种情况是因为,在多个线程争夺CUP的使用权的时候发生了不安全的事情:当线程1取得CPU使用权打印到一部分时,线程2抢到了CPU的使用权,这时开始打印线程2中的数据,就出现了上面的结果。为了解决这样的问题,我们必须保证在执行类似于打印字符串这样的写操作时,一定要保证它能够执行完(类似于数据库中的一个事务),才让出CPU的使用权。
Java中使用线程同步解决这样的问题:
这里将打印的操作放入同步代码块中(synchronized关键字);代码块需要一个对象作为同步锁。这里为什么不直接用str对象作为锁呢,而要用一个静态的对象。原因是:既然要起到锁的作用,就必须保证需要保持同步的线程只共享一个锁,虽然在类中咋一看,觉得str只有一个,但是仔细想一下发现,并不是这样的,因为每次创建runnable对象的时候都会传进来一个新的对象(“AAA-BBB”、“CCC-DDD”就充当了不同的两把锁,在这里同步就变得没有意义了)。因为静态的对象是属于类的,而不是一个对象的,所以它可以起到同步锁的作用。
另外的解决办法:
将普通方法变成同步的可不可以呢? 当然不行,因为普通非静态的同步方法使用的同步锁对象是:this。大家仔细想想,我们创建了两个runnable的实例,当然他们的this就是他们各自的this了,这就变成和使用str对象作为同步锁一样的性质了。
当然我们可以将这个方法变成静态的:
这样就可以解决问题了。(原因不难想,是因为静态的方法使用的同步锁对象是类的字节码 也就是 class对象)
我们自定义一个线程类实现Runnable接口:
package com.flyingduck.thread; public class PrintThread implements Runnable { private String str; public PrintThread(String str) { this.str = str; } @Override public void run() { while (true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } printString(str); } } private void printString(String str) { for (int i = 0; i < str.length(); i++) { System.out.print(str.charAt(i)); } System.out.println(); } }
线程用于逐个字符打印str字符串;
package com.flyingduck.thread; public class TraditionalThread { public static void main(String[] args) { PrintThread target1 = new PrintThread("AAA-BBB"); PrintThread target2 = new PrintThread("CCC-DDD"); new Thread(target1).start(); new Thread(target2).start(); } }
我们启动了两个线程打印不同的字符串。按照正常来说,我们应该每次都会打印完整的“AAA-BBB”“CCC-DDD”字符串,但是结果却是:
AAA-BBBC CC-DDD ACCC-DDD AA-BBB CAAA-BBB CC-DDD AAA-BBB CCC-DDD ACCC-DDD AA-BBB CCC-DDDA AA-BBB CACC-DDDA A-BBB ACAA-BBB
之所以会发生这种情况是因为,在多个线程争夺CUP的使用权的时候发生了不安全的事情:当线程1取得CPU使用权打印到一部分时,线程2抢到了CPU的使用权,这时开始打印线程2中的数据,就出现了上面的结果。为了解决这样的问题,我们必须保证在执行类似于打印字符串这样的写操作时,一定要保证它能够执行完(类似于数据库中的一个事务),才让出CPU的使用权。
Java中使用线程同步解决这样的问题:
package com.flyingduck.thread; public class PrintThread implements Runnable { private String str; private static Object lock = new Object(); public PrintThread(String str) { this.str = str; } @Override public void run() { while (true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock) { //为什么不适用str作为锁呢? printString(str); } } } private void printString(String str) { for (int i = 0; i < str.length(); i++) { System.out.print(str.charAt(i)); } System.out.println(); } }
这里将打印的操作放入同步代码块中(synchronized关键字);代码块需要一个对象作为同步锁。这里为什么不直接用str对象作为锁呢,而要用一个静态的对象。原因是:既然要起到锁的作用,就必须保证需要保持同步的线程只共享一个锁,虽然在类中咋一看,觉得str只有一个,但是仔细想一下发现,并不是这样的,因为每次创建runnable对象的时候都会传进来一个新的对象(“AAA-BBB”、“CCC-DDD”就充当了不同的两把锁,在这里同步就变得没有意义了)。因为静态的对象是属于类的,而不是一个对象的,所以它可以起到同步锁的作用。
另外的解决办法:
package com.flyingduck.thread; public class PrintThread implements Runnable { private String str; private static Object lock = new Object(); public PrintThread(String str) { this.str = str; } @Override public void run() { while (true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // synchronized (lock) { //为什么不适用str作为锁呢? printString(str); // } } } private synchronized void printString(String str) { for (int i = 0; i < str.length(); i++) { System.out.print(str.charAt(i)); } System.out.println(); } }
将普通方法变成同步的可不可以呢? 当然不行,因为普通非静态的同步方法使用的同步锁对象是:this。大家仔细想想,我们创建了两个runnable的实例,当然他们的this就是他们各自的this了,这就变成和使用str对象作为同步锁一样的性质了。
当然我们可以将这个方法变成静态的:
package com.flyingduck.thread; public class PrintThread implements Runnable { private String str; private static Object lock = new Object(); public PrintThread(String str) { this.str = str; } @Override public void run() { while (true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // synchronized (lock) { //为什么不适用str作为锁呢? printString(str); // } } } private synchronized static void printString(String str) { for (int i = 0; i < str.length(); i++) { System.out.print(str.charAt(i)); } System.out.println(); } }
这样就可以解决问题了。(原因不难想,是因为静态的方法使用的同步锁对象是类的字节码 也就是 class对象)
相关文章推荐
- 黑马程序员--Java基础--多线程安全问题
- Java基础 多线程 解决安全问题 等待唤醒机制 Lock Condition interrupt join setPriority yield
- java基础-多线程的安全问题
- JAVA基础多线程的安全问题
- 黑马程序员——JAVA基础之多线程的安全问题
- Java基础-多线程-②多线程安全问题
- java基础知识回顾之java Thread类学习(七)--java多线程安全问题(死锁)
- Java基础-多线程-②多线程安全问题
- java基础—多线程下的单例设计模式的安全问题
- JAVA基础再回首(二十四)——多线程的概述、实现方式、线程控制、生命周期、多线程程序练习、安全问题的解决
- 黑马程序员:Java基础——多线程之安全问题与同步
- java基础知识回顾之java Thread类学习(四)--java多线程安全问题(锁)
- 【黑马程序员】Java基础05:多线程与其安全问题
- java基础知识回顾之java Thread类学习(五)--java多线程安全问题(锁)同步的前提
- JAVA基础再回首(二十五)——Lock锁的使用、死锁问题、多线程生产者和消费者、线程池、匿名内部类使用多线程、定时器、面试题
- java语言基础(90)——多线程(同步代码块解决多线程售电影票时的负票问题)
- Java多线程——安全与死锁问题
- java多线程(6)--多线程的安全问题总结
- java笔记之java多线程的安全问题
- java多线程基础总结【一】volatile关键字与原子性问题