黑马程序员_<<线程,Runnable,锁>>

--------------------ASP.Net+Android+IOS开发.Net培训、期待与您交流! --------------------


 1.实现Runnable接口

       1.步骤

            1.创建类(A)实现接口Runnable,复写run()方法

      2.创建Thread对象,然后把类A的对象作为参数传给Thread的构造函数,这是把其关联起来。

      3.通过创建的线程对象,然后开启线程。

                解答:复写run()方法,里面存的是线程要运行的代码

       因为Runnble接口中只有一个run()方法,所以没有办法启动线程,所以还得使用Thread类来启动线程,所以要把Runnable的子类的对象作为参数传给Thread类构造函数。

       2.售票例子(Runnable接口)

    

 public class Tick implements Runnable {
  private int tick = 10;
 
  public void run(){
    while (tick > 0) {
      System.out.println(Thread.currentThread().getName() + "卖了第"
           + (tick--) + "张票");
    }
  }
}

结果:
Thread-0卖了第10张票
Thread-1卖了第10张票
Thread-0卖了第9张票
Thread-1卖了第8张票
Thread-0卖了第7张票
Thread-1卖了第6张票
Thread-0卖了第5张票
Thread-0卖了第4张票
Thread-0卖了第3张票
Thread-0卖了第2张票
Thread-0卖了第1张票
Thread-1卖了第0张票

 

   从结果上可以看出,这是我们想要的那种结果,多个窗口共同买共有的票。

3.Runnable接口和Thread类的区别与联系

               1.Runnable接口可以避免单继承的限制,要是继承Thread类的话,那么就不能继承其他的类了,因为只能单继承,如果实现了接口Runnale后,还可以继承其他的类,或是是实现其他的接口

     2.Runnable实现了资源的共享,例如(售票程序的票)

     3.Runnable实增强了程序的健壮性,代码能够被多个程序共享,实现了数据 与代码是独立的。

     4.实现Runnable接口的线程的运行代码存在实现Runnable接口子类的run()方法中,继承Thread类的线程的代码存在Thread子类的run()方法中。

  所以在以后的开发中,使用Runnble接口比较好,更多。

 2.线程的安全问题

     1.原因

          当多条语句执行多个线程共享的资源,执行到一部分后,执行权被抢夺了,导致共享资源的不正正常修改,所以就产生了线程的安全问题。

        例如:售票小例子


public class Ticket implements Runnable {
  private int tick = 10;
 
  public void run() {
    while (true) {
      try {
         if (tick > 0) {
           Thread.sleep(10);
           System.out.println(Thread.currentThread().getName()+ "卖了第"
               + (tick) + "张票");
           tick--;
         }
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
 
    }
  }
}
public class Text {
  public static void main(String[] agrs) {
    Ticket tt=new Ticket();
    new Thread(tt).start();
    new Thread(tt).start();
  }
}
结果:
Thread-1卖了第10张票
Thread-0卖了第10张票
Thread-1卖了第8张票
Thread-0卖了第8张票
Thread-0卖了第6张票
Thread-1卖了第5张票
Thread-1卖了第4张票
Thread-0卖了第3张票
Thread-0卖了第2张票
Thread-1卖了第2张票
Thread-0卖了第0张票




  结果出现了:产生了重复的票,并且出现了0票,那么这是不正常的现象的。

     2.同步代码块---解决办法

             1.概述

         利用的是关键子(synchronized)来处理线程同步问题,保证线程安全,锁就好比门上的锁一下,执行的顺序是:首先是判断锁是否是开着的,若是开着的,那么就可以进去(执行同步代码)然后把锁锁上,执行完后,把锁释放(把锁开开),判断锁是锁着的,那么就在外面等着,直到里面的人把锁开开,出来。(这样保证的是同步代码在某一段时间只有一个线程在执行。)


   

             2.格式

         synchronized(对象){执行共享资源的代码}

             3.同步规则

                      必须是多个线程执行(至少两个)才可以产生同步,必须是多个线程使用的是同一个锁,

           优点:解决了多线程的安全问题

           弊端:多线程需要判断,那么就会消耗时间,消耗资源


public class Ticket implements Runnable {
  private int tick = 10;
 
  public void run() {
    while (true) {
      synchronized (this) {
         try {
           if (tick > 0) {
             Thread.sleep(10);
             System.out.println(Thread.currentThread().getName()
                 + "卖了第" + (tick) + "张票");
             tick--;
           }
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
      }
 
    }
  }
}
结果:
Thread-0卖了第10张票
Thread-0卖了第9张票
Thread-0卖了第8张票
Thread-1卖了第7张票
Thread-1卖了第6张票
Thread-1卖了第5张票
Thread-1卖了第4张票
Thread-1卖了第3张票
Thread-1卖了第2张票
Thread-1卖了第1张票
 


这样结果就和我们正常想要的是一样的,没有异常现象

     3.同步方法---解决办法

         1.概述

               因为方法和代码块都是用来封装代码的,那么代码块可以使用锁来解决同步问题,那么函数也可以使用锁,使方法也可以操作同步问题。

       2.格式

                   public synchronized 方法返回类型 方法名(参数){方法体}

     3.使用

                怎眼确定锁的位置,寿面判断那些是线程运行的代码,那些是共享资源,那些同步代码是执行的是同步资源,那么就把那些操作同步资源的语句使用锁来进行括起来。

 

   例如:客户向银行存钱,每次存三次,每次存100,分两个地方存户存

    思路:共享资源:总价钱,银行,有两个线程(模拟两个地方存)

 

         

**
 * 银行类
 */
public class Bank {
  private int sum;// 表示的是当前账户的总额
 
  public synchronized void add(int mon) {// 存方法,加锁的话,那么里面的语句在一段时间内必须是一个线程在执行
    sum += mon;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("sum=" + sum);
  }
}
public class Cus implements Runnable {
    private Bank bank=new Bank();
    public void run(){//线程执行的代码(线程)
     for(int i=0;i<3;i++){
       bank.add(100);
    }
    }
}
public class Text {
  public static void main(String[] agrs) {
    Cus tt=new Cus();//客户
    new Thread(tt).start();//开始存
    new Thread(tt).start();//开始存
   
  }
}
结果:
sum=100
sum=200
sum=300
sum=400
sum=500
sum=600


     4.同步代码块和同步函数的选择

          假如连续的代码是同步代码,并且都是执行的都是共享资源,那么就可以把其抽取成函数,并且定义为同步函数

       假如一个函数中是全是同步代码,并且执行的全是共享资源,那么就把其定义为同步函数

     5.扩展

            回顾:一共分为四种代码块

    普通代码块:就是定义在方法中的代码块。

     构造块:定义在类中的代码块,优先于构造方法,重复调用。     

    静态块:使用static关键字声明的,只执行一次,优先于构造块。

    同步代码块:使用synchronized关键字声明的代码块,称为同步代码块。格式:synchronized(同步对象){}

 

 4.锁对象的确定

     1.同步函数的锁是this

             验证思路:还是使用的卖票程序,使用两个线程,他们执行的代码不同,一个是同步代码块(锁的对象是this  synchronized(this){}),另一个线程执行的同步函数,如果结果没有异常,那么他们就完全符合线程同步的规则,

         多个线程执行的是同步代码,并且他们的是锁是同一个锁,因为同步代码块的锁对象是this,那么此时同步函数锁的对象也是this

      

               

 /*售票机*/
public class Ticket implements Runnable {
  private int tick = 100;
  public boolean flag = true;
 
  public void run() {
    if (flag) {
      while (true) {
         synchronized (this) {
           try {
             if (tick > 0) {
               Thread.sleep(10);
               System.out.println(Thread.currentThread().getName()
                   + "卖了第" + (tick--) + "张票");
             }
           } catch (InterruptedException e) {
             e.printStackTrace();
           }
         }
      }
 
    } else
      while (true)
         show();
  }
 
  public synchronized void show() {// 同步函数
    try {
      if (tick > 0) {
         Thread.sleep(10);
        System.out.println(Thread.currentThread().getName()+ "卖了第"
             + (tick) + "张票");
         tick--;
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
public class Text {
  public static void main(String[] agrs) {
    Ticket tt=new Ticket();
    new Thread(tt).start();
    /*这里是主线程睡眠一下,让线程1先运行,防止直接改变标志位*/
    try {
      Thread.sleep(50);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tt.flag=false;//更改标志位
    new Thread(tt).start();//开始存
   
  }
}
 
部分结果:
   Thread-0卖了第100张票
Thread-0卖了第99张票
Thread-0卖了第98张票
Thread-0卖了第97张票
Thread-0卖了第96张票
Thread-0卖了第95张票
Thread-1卖了第94张票
Thread-1卖了第93张票
Thread-1卖了第92张票

 

     从结果上额可以看出,结果是正常的,所以结论是正确的,同步函数的锁对象是this

     2.静态同步函数锁对象

         静态同步函数的锁是:该方法所在类的字节码文件对象:类名.class

     验证方法:和上面的一样,把同步代码块的锁对象改为:Ticket.class

 

       

/*售票机*/
public class Ticket implements Runnable {
  private static int tick = 100;
  public boolean flag = true;
 
  public void run() {
    if (flag) {
      while (true) {
         synchronized (Ticket.class) {
           try {
             if (tick > 0) {
               Thread.sleep(10);
               System.out.println(Thread.currentThread().getName()
                   + "卖了第" + (tick--) + "张票");
             }
           } catch (InterruptedException e) {
             e.printStackTrace();
           }
         }
      }
 
    } else
      while (true)
         show();
  }
 
  public static synchronized void show() {// 同步函数
    try {
      if (tick > 0) {
         Thread.sleep(10);
        System.out.println(Thread.currentThread().getName()+ "卖了第"
             + (tick) + "张票");
         tick--;
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

     结果:和上面的一样,正常

 

     我们可以这样思考,静态方法是随着类的加载而加载,此时没有类的对象,但是还是要找个对象,那么就是类的字节码文件的对象。

3.死锁

      1.死锁产生的原因

          死锁产生的原因是:一个所中还有另外一个锁,但是这两个锁对象是不相同的,其中一个锁(A)需要另外一个锁(B),而锁(B)也需要锁(A),这样有时候她们都不会妥协,那么就会产生死锁。

      2.举例死锁

       

class MyLock {
  static Object lockA = new Object();
  static Object lockB = new Object();
}
 
public class DeadLock implements Runnable {
  public boolean flag = true;
 
  public DeadLock(boolean f) {
    this.flag = f;
  }
 
  public void run() {
    if (flag) {
      // while (true) {
      synchronized (MyLock.lockA) {
         System.out.println("ifLockA");
         synchronized (MyLock.lockB) {
           System.out.println("ifLockB");
         }
         // }
      }
    } else {
      // while (true) {
      synchronized (MyLock.lockB) {
         System.out.println("elseLockB");
         synchronized (MyLock.lockA) {
           System.out.println("elseLockA");
         }
         // }
      }
    }
  }
}
public class Text {
  public static void main(String[] agrs) {
    DeadLock l1 = new DeadLock(true);// 第一个线程
    DeadLock l2 = new DeadLock(false);// 第二个线程
    new Thread(l1).start();// 开启线程
    new Thread(l2).start();// 开启线程
  }
}
 
结果:可能会死锁
  例如:
if LockA
else LockB

这就产生了死锁

 

1. 资源共享的时候需要进行同步操作

2. 程序中过多了同步操作会产生死锁

死锁就是程序中互相的等待。

 

--------------------ASP.Net+Android+IOS开发.Net培训、期待与您交流! --------------------


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值