疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java多线程同步与等待唤醒


 

   

  1:数据安全问题

  1.1:什么情况下会出现数据安全问题?

  多个线程对同一个资源进行操作,并且操作资源的语句有多条。那么这个时候这些语句因为cpu的随机性,有可能被多个线程分开执行。导致数据安全问题。

  例子:有3个人分别是你爸、你妈妈、你姐,去你的一个账户汇钱给你,每一个只能存3次,一次只能存100元。每存一次,请打显示出账户里的余额。代码体现:

  复制代码

  1 public class SaveMoneyDemo1 {

  2

  3 public static void main(String[] args) {

  4 SaveDemo1 s = new SaveDemo1();

  5 Thread t1 = new Thread(s);

  6 t1.setName("老爸");

  7 Thread t2 = new Thread(s);

  8 t2.setName("老妈");

  9 Thread t3 = new Thread(s);

  10 t3.setName("姐姐");

  11 t1.start();

  12 t2.start();

  13 t3.start();

  14 }

  15

  16 }

  17

  18 class SaveDemo1 implements Runnable{

  19 private int sum = 0;

  20 //要执行的代码块放在run方法里面。

  21 public void run() {

  22 //每个人能存三次,就是循环三遍

  23 for(int i=0; i<3; i++){

  24 sum+=100;

  25 System.out.println(Thread.currentThread().getName()+"给你汇了100,目前账号共有 "+sum+" 元");

  26

  27 }

  28 }

  29 }

  30 /**

  31 * 执行结果:

  32 老妈给你汇了100,目前账号共有 200 元

  33 老妈给你汇了100,目前账号共有 400 元

  34 老妈给你汇了100,目前账号共有 500 元

  35 姐姐给你汇了100,目前账号共有 300 元

  36 姐姐给你汇了100,目前账号共有 600 元

  37 姐姐给你汇了100,目前账号共有 700 元

  38 老爸给你汇了100,目前账号共有 200 元

  39 老爸给你汇了100,目前账号共有 800 元

  40 老爸给你汇了100,目前账号共有 900 元

  41 *

  42 */

  复制代码

  运行结果好喜感。为什么会出现这种情况?分析:这三人存款是不需按照顺序和次数的,反正帮你存够三次就行了,所以用多线程更为合理。可是,打个比方:当你妈妈在存钱的时候钱是存进去了,在没来得及显示余额的时候你爸正好也把钱存了进去,这时候总金额连同你妈和你爸的加在一起了!所以显示出的金额会发生这样的情况。那么如何解决类似的情况呢?这就要限定一个人存一次就先把金额加上,不能让多人存完之后再一起加,如果这样那金额的显示就乱套了。这时候就要使用同步机制了。

  1.2:解决方案: 同步机制

  1.2.1:同步代码块。

  synchronized(锁){//锁可以为任意对象。但是需要保证多个线程用的是同一把锁。

  对同一个资源的操作语句。

  }

  1.2.2:同步方法的锁:

  2.1:同步方法-----this

  2.2:静态同步方法-----字节码文件对象。类名.class

  复制代码

  1 public class SaveMoneyDemo2 {

  2

  3 public static void main(String[] args) {

  4 SaveDemo2 s = new SaveDemo2();

  5 Thread t1 = new Thread(s);

  6 t1.setName("老爸");

  7 Thread t2 = new Thread(s);

  8 t2.setName("老妈");

  9 Thread t3 = new Thread(s);

  10 t3.setName("姐姐");

  11 t1.start();

  12 t2.start();

  13 t3.start();

  14 }

  15

  16 }

  17

  18 class SaveDemo2 implements Runnable{

  19 private int sum = 0;

  20 //要执行的代码块放在run方法里面。

  21 public void run() {

  22 //每个人能存三次,就是循环三遍

  23 synchronized(this){

  24 for(int i=0; i<3; i++){

  25 sum+=100;

  26 System.out.println(Thread.currentThread().getName()+"给你汇了100,目前账号共有 "+sum+" 元");

  27 }

  28 }

  29 }

  30 }

  复制代码

  1.3:如果加了同步,还出现数据安全问题,如何排查?

  1.3.1:是否为同一把锁

  1.3.2:访问资源的多条语句是否在同步中。

  1.4:关于同步的拙劣理解:一件事先一口气做完!不让别人插手。(好像太牵强了)

  2:死锁问题——互不释放资源(互相等待资源)

  2.1  需求:用程序来描述以下情况:一手交钱一手交货。商家与顾客两人都是很小气的人,顾客买商家的东西,商家收顾客的前,顾客说:先给我货我再给你钱;商家说:先给我钱我再给你货。最好的结局就是各自得到各自的东西。

  2.2  分析:对于商家和客户来说和他们俩有关的不是钱就是货,而限制这两人的也就是钱和货。这样一来钱和货就可以看做是程序中的两个锁了。造成死锁的原因:同步代码嵌套!在平时开发时应避免同步嵌套!

  复制代码

  1 public class DeadLockDemo1 {

  2 public static void main(String[] args) {

  3 Thread t1 = new Customer1();

  4 Thread t2 = new Seller1();

  5 t1.start();

  6 t2.start();

  7 }

  8 }

  9

  10 class Customer1 extends Thread{

  11 public static Object money = new Object();

  12 @Override

  13 public void run() {

  14 //客户有钱

  15 synchronized(money){

  16 System.out.println("客户等商家给货");

  17 //客户等货

  18 synchronized (Seller1.goods) {

  19 System.out.println("客户给商家钱");

  20 }

  21 }

  22 }

  23 }

  24

  25 class Seller1 extends Thread{

  26 public static Object goods = new Object();

  27 @Override

  28 public void run() {

  29 //商家有货

  30 synchronized (goods) {

  31 System.out.println("商家等客户给钱");

  32 //商家等钱

  33 synchronized (Customer1.money) {

  34 System.out.println("商家给客户货");

  35 }

  36 }

  37 }

  38 }

  39

  40 /**

  41 * 如果想结果暴露地更明显,可以使用sleep()方法

  42 * 运行死锁的结果:

  43 * 客户等商家给货

  44 * 商家等客户给钱

  45 *

  46 */

  复制代码

  3:等待唤醒机制。

  前面多个线程案例中,每个线程执行的操作是一样的。如果线程所执行的操作不一样呢?比如一个线程负责生产产品,另外一个线程负责消费产品。

  3.1:创建2个线程,2个线程的动作是不一样。比如说:一个生产者和一个消费者。

  需求:生产者生产一个产品。消费者消费一个产品。这就涉及到了等待唤醒机制。当生产者生产一个产品后进入等待模式等待消费者来消费这个产品,当消费者消费了这个产品,发现没有产品了,消费者等待,叫生产者生产产品。生产者生产了产品则通知消费者。这就涉及到等待唤醒机制。

  3.2:等待唤醒机制。

  等待唤醒机制必须是在同步中进行,因为等待和唤醒都是要通过锁来操作的,查看API就是的,wait()和notify()是属于Object的方法,任何对象都是可以作为锁的。

  wait:让当前线程等待。在哪里等待的就在哪里醒过来。

  notify() :唤醒其中一个等待的线程。

  notifyAll():唤醒所有等待的线程

  wait和sleep的区别:

  1:sleep会拥有锁,而wait会释放锁。

  2:sleep睡眠的时间是固定的,而wait等待的时间是不固定的。

  3:sleep可以放在同步中,也可以不放在同步中。wait方法必须放在同步中。

  3.3:一个生产者和一个消费者的代码实现

  复制代码

  1 /*

  2 * 生产者生产一个产品。

  3 * 消费者消费一个产品。

  4 * 生产者可以生产多个产品,但是一次只能生产一个产品。消费了才能生产。

  5 * 消费者可以消费多个产品。但是一次只能消费一个 产品。生产有了产品才能消费。

  6 */

  7 public class ProCusDemo1 {

  8 public static Object lock = new Object();//创建一个对象作为锁

  9 public static int num = 0;//产品数

  10 public static void main(String[] args) {

  11 Pro pro = new Pro();

  12 Cus cus = new Cus();

  13 pro.start();

  14 cus.start();

  15 }

  16

  17 }

  18

  19 class Pro extends Thread {

  20 @Override

  21 public void run() {

  22 //不断生产,使用循环

  23 while(true){

  24 try {

  25 Thread.sleep(100);

  26 } catch (InterruptedException e1) {

  27 // TODO Auto-generated catch block

  28 e1.printStackTrace();

  29 }

  30 // System.out.println("111");

  31 //操作同一数据——产品数(num),使用同步代码块,也可以是等待唤醒机制必须在同步在同步中进行

  32 synchronized (ProCusDemo1.lock) {

  33 //当有一个产品了,生产者就不用生产了

  34 if(ProCusDemo1.num == 1){

  35 try {

  36 //不用生产的体现就是等待

  37 ProCusDemo1.lock.wait();

  38 // System.out.println("222");

  39 } catch (InterruptedException e) {

  40 e.printStackTrace();

  41 }

  42 }

  43 ProCusDemo1.num ++;//生产了一个产品

  44 System.out.println("生产者生产了一个产品,现有:"+ProCusDemo1.num+" 个");

  45 //当生产了一个产品之后就可以唤醒消费者消费了

  46 ProCusDemo1.lock.notify();

  47 }

  48 }

  49 }

  50 }

  51

  52 class Cus extends Thread {

  53 @Override

  54 public void run() {

  55 while(true){

  56 // System.out.println("333");

  57 try {

  58 Thread.sleep(100);

  59 } catch (InterruptedException e1) {

  60 // TODO Auto-generated catch block

  61 e1.printStackTrace();

  62 }

  63 //多个线程操作同一数据使用同步

  64 synchronized (ProCusDemo1.lock) {

  65 if(ProCusDemo1.num == 0){

  66 try {

  67 ProCusDemo1.lock.wait();

  68 // System.out.println("444");

  69 } catch (InterruptedException e) {

  70 e.printStackTrace();

  71 }

  72 }

  73 ProCusDemo1.num--;

  74 System.out.println("消费者消费了一个产品,现有:"+ProCusDemo1.num+" 个");

  75 ProCusDemo1.lock.notify();

  76 }

  77 }

  78

  79 }

  80 }