Java中的多线程

  线程同步和线程通信

 

   一.线程同步

    线程同步的产生:多个线程操纵同一个对象,由于对象的实例变量放在运行时数据区的堆区中,所以这些线程可以同时操作该对象的实例变量,导致一个线程修改另外一个线程的临时操作数,产生结果不正确的现象。

  (1)两个线程操作同一个对象,对象的实例变量会受到两个线程的同时修改,因为实例变量在运行时数据区的堆区中,两个线程共享对象的实例变量,而方法的局部变量不存在这样的问题,因为每一个线程都有自己的方法调用栈,用于保存局部变量,局部变量不共享。

  (2)在两个线程并发执行的过程中,由于共享实例变量,一个线程可能会修改另外一个线程的临时操作数(也就是这个共享变量),这样会导致共享资源的访问冲突,从而导致结果不正确。

  (3)因此,要操纵共享变量,需要使用原子操作,原子操作是一组操作的集合,这组操作的特点是,(3.1)操纵与其他线程的共享资源(为实现这一点,原子操作必须在共享对象的类中定义)(在共享对象类中定义原子操作,这样每个使用共享对象的线程都能够执行原子操作),(3.2)并且一个线程在执行原子操作期间,其他线程不能够操纵共享资源(为实现这一点,必须用同步代码块包装原子操作)(原子操作在共享资源的类中定义);

  (4)为了保证每一个线程都能够正常执行原子操作(怎么将一组对共享对象的操纵作为一组原子服务执行)(既操作共享资源,又保证在一个线程操纵共享资源时,没有其他线程操纵共享资源),Java中引入同步机制,在代表原子操作的代码前加上synchronized标记(synchronized从第一条操纵公共资源的语句开始,一直到最后一条操纵公共资源的语句结束,不要多加,也不要少加);

  (5)每一个对象都有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁,只有获得对象的锁,才能够执行对象的synchronized方法,无论是相同的同步代码块,还是不同的同步代码块,只要线程没有对象的锁,那么任意定义在对象类中的synchronized块,线程都没有办法操纵(也就是说,对象唯一的锁+synchronized块共同实现了原子操作,保证一个线程在执行synchronized块<原子操作->操纵共享资源>时,没有其他线程操纵共享资源);

  (6)线程同步的特征:

        a.只要是对共享资源的操作,都应该设为原子操作,使用synchronized修饰符进行修饰,因为所有的synchronized修饰符都修饰原子操作,因此一个线程的synchronized代码没有执行结束前,其他任何线程都不能够执行synchronized块,因为这样会修改临时数据,导致结果不正确。

        b.每一个对象都有且仅有一个同步锁,如果任何线程想要执行对象的synchronized块,必须获得对象的同步锁,如果有一个线程在执行synchronized块,说明它已经得到了对象的同步锁,其他对象要想执行原子操作,必须等待正在执行原子操作的线程释放对象锁(synchronized方法执行结束或者调用对象的wait()方法)

        c.静态方法也可以用synchronized修饰,不仅是对象,每一个加载到运行时数据区方法区的类也有唯一的同步锁;

        d.执行原子操作的线程可以中断,但是不会释放对象的锁,进入同步代码块的线程可以执行sleep()或者yield()方法,这样会将CPU的使用机会让给其他线程(从可运行池中选一个线程出来,sleep任选一个线程,yield选一个相同或者更高优先级的线程),但是等待锁的线程仍旧处于对象的锁池中,处于阻塞状态。

        e.synchronzied特性不会被继承,必须显式指明。

  (7)同步代码块的位置

          共享对象的类中的第一句实例变量操纵的语句开始,到最后一句对实例变量操纵的语句结束,还有线程中对共享对象的实例变量的操纵语句。

  (8)线程安全

         8.1线程安全的定义(一个线程安全的类)

               a.这个类的对象可以同时被多个线程安全访问

               b.每个线程都能够正确地执行原子操作(为保证这一点,原子操作尽量在共享对象的类中定义),得到正确的结果;

               c.线程的原子操作结束后,对象的逻辑正确。

         8.2 不可变类永远是线程安全的,因为不可变类的属性不能够被修改,只能够被访问。

  (9)释放对象的锁

         线程在以下情况下会释放对象的锁

         a.同步代码块执行结束

         b.线程在执行过程中遇到异常,线程终止,这样既释放锁,又让出CPU

         c.线程执行对象的wait()方法,线程会释放对象的锁,进入对象的等待池

              总结:处于阻塞状态的线程

               位于对象的等待池

               位于对象的锁池

               调用sleep(),或者执行IO操作

              前两中阻塞状态下的线程均没有对象的锁,最后一种情况下线程有对象的锁,但是没有CPU ,注意运行中的线程如果调用线程的yield()方法,不会释放锁,直接进入runnable状态

 

  (10)死锁发生的条件,

            至少有两个类的实例,而且这些类的实例被多个线程共享,如果线程A执行对象a的synchronized块,需要操纵对象b的synchronized块,而这时线程B正在执行对象b的synchronized块,拥有对象b的对象锁,线程A进入对象b的锁池,等待线程B释放对象b的对象锁,而线程B在执行synchronized块的过程中需要执行线程A的synchronized块,可是这时对象a的锁被线程A占用,故线程B进入对象a的锁池中,这样,线程A和线程B互相等待对方释放对象锁,进入死锁状态。

            要避免死锁发生, 需要保证所有的线程按照相同的顺序访问共享对象

 

   二.线程通信

       (1)不同的线程执行不同的操作(既然是不同的操作,必然是不同的线程执行不同的synchronized块),如果这些操作之间有联系,既某个操作必须等另外一个操作执行结束后执行,则执行这些操作的线程之间需要能够进行通信,才能协调完成工作

       (2)java.lang.Object类中提供两个用于线程通信的方法

               2.1 wait()

                     a.执行该方法的线程释放对象的锁;

                     b.JVM将该执行该方法的线程放到对象的等待池中;

                     c.该线程等待其他线程将它唤醒。

               2.2 notify()

                     a.该方法使JVM从对象的等待池中选出一个线程,放入对象的锁池中 ,放入锁池中后结果还是阻塞状态

            

               Java中,如果两个线程t1和t2共同操作同一个对象,则这两个线程通过wait()和notify()方法进行线程之间的通信

       (3)线程通信的过程大概如下:

               3.1 线程t1执行对象的synchronized块,持有对象的锁,线程t2在对象的锁池中等待;

               3.2 线程t1执行对象的wait()方法,释放对象的锁,被JVM放入对象的等待池中;

               3.3 线程t2得到对象的锁,开始执行对象的另外一个synchronized代码块,在执行过程中执行对象的notify()方法,JVM将t1从对象的等待池中取出,放到对象的锁池中,t1等待获得对象的锁

               3.4 线程t2通过执行对象的wait()方法或者执行synchronized块结束,释放对象的锁,t1冲洗获得对象的锁后,进入runnable状态,等待CPU的调度。

               线程通信的实例

            

 

  4.wait()和notify()方法必须放在类的synchronized块中;

  5.wait()方法必须在一个while循环中进行调用,因为共享资源的实例变量的状态不定,即使线程重新获得了锁,也要判断当前是否能够继续往下执行,还是需要继续执行对象的wait()方法

  6.一般在synchronized块开始时就执行对象的notifyAll()语句,因为这时应该执行的线程,已经获得了对象的锁,开始执行,没有必要让对象等待池中的线程一直等待,应该放入锁池中,等待对象的锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值