多线程基本

来源:互联网 发布:c语言声明 编辑:程序博客网 时间:2024/05/22 04:40

        1 共享:变量可以被多个线程访问

         2 可变: 变量的值在其生命周期内可变,意味存在线程不安全的风险。如何保证线程安全1, 不再线程中共享该变量,将变量封装在方法内 2 将状态变量修改为不可变变量(final), 3 使用同步策略访问状态变量  4   使用原子安全类

         3 不可变: 变量的值在其生命周期内不可变,不可变对象一定是线程安全的,不需要额外的同步。

         4 线程安全:当多线程访问某个类时,不管运行时环境采用何种调度方式,或线程如何交替执行,并在主调代码中不需要额外的同步或协调,类都能表现正确的行为,就称这个类线程安全

         5 线程同步, 按照预定的先后顺序进行运行,在多线程编程里面,一些较为敏感的数据时不允许被多个线程同时访问的,使用线程同步技术,确保数据在任何时刻最多只有一个线程访问,保证数据的完整性。

          线程同步的机制有:临界区,互斥量,事件,信号量4种方式

          临界区:通过对多线程的串行化来访问公共资源,在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。

          互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,能保证公共资源不会同时被多个线程访问,互斥能实现不同应用程序资源共享

           信号量:允许多个线程在同一个时刻访问同一资源,但要限制同一时刻访问资源的最大线程数。

           事件:通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作。

           6 原子性,如果某个操作不可分割,就称该操作具有原子性。 非原子操作都存在线程安全问题,需要同步机制保证操作变成原子操作。

           7 可见性 一个线程对状态的修改对另一个线程程是可见的,一个线程修改的结果,另一个线程立马就知道。

          8 有序性 有序性指的是数据不相关的变量在并发的情况下,实际执行的结果和单线程的执行结果是一样的,不会因为重排序的问题导致结果不可预知。volatile, final, synchronized,显式锁都可以保证有序性。

            临界区:提供了同步机制,当一个线程试图访问一个临界区时,将使用一种同步机制查看是否有其他线程进入临界区,如果没有则就可以进入临界区,否则就会被同步机制挂起,直到进入临界区的线程离开;

           

临界区规定:每次只准许一个进程进入临界区,进入后不允许其他进程进入。调度法则为(百度百科):

1、如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。

2、任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。

3、进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。

4、如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。

            在java中有两种方法实现锁机制,一种是synchronized,而另一种是比synchronized更加强大的Lock。Lock确保当一个线程位于代码的临界区时,另一个线程不进入临界区,相对于synchronized,Lock接口及其实现类提供了更加强大、灵活的锁机制。

             锁的公平性

      

公平性的对立面是饥饿。那么什么是“饥饿”呢?如果一个线程因为其他线程在一直抢占着CPU而得不到CPU运行时间,那么我们就称该线程被“饥饿致死”。而解决饥饿的方案则被称之为“公平性”——所有线程均可以公平地获得CPU运行机会。

导致线程饥饿主要有如下几个原因:

高优先级线程吞噬所有的低优先级线程的CPU时间。我们可以为每个线程单独设置其优先级,从1到10。优先级越高的线程获得CPU的时间越多。对大多数应用来说,我们最好是不要改变其优先级值。

线程被永久堵塞在一个等待进入同步块的状态。java的同步代码区是导致线程饥饿的重要因素。java的同步代码块并不会保证进入它的线程的先后顺序。这就意味着理论上存在一个或者多个线程在试图进入同步代码区时永远被堵塞着,因为其他线程总是不断优于他获得访问权,导致它一直得到不到CPU运行机会被“饥饿致死”。

线程在等待一个本身也处于永久等待完成的对象。如果多个线程处在wait()方法执行上,而对其调用notify()不会保证哪一个线程会获得唤醒,任何线程都有可能处于继续等待的状态。因此存在这样一个风险:一个等待线程从来得不到唤醒,因为其他等待线程总是能被获得唤醒。

为了解决线程“饥饿”的问题,我们可以使用锁实现公平性。

         

     我们知道当线程请求一个由其它线程持有锁的对象时,该线程会阻塞,但是当线程请求由自己持有锁的对象时,是否可以成功呢?答案是可以成功的,成功的保障就是线程锁的“可重入性”。

     “可重入”意味着自己可以再次获得自己的内部锁,而不需要阻塞。如下:

class Futher{public synchronized void method(){}}class Son extends Futher{public synchronized void method(){super.method();}}
如果锁是不可重入的,上面的代码就会死锁,因为调用child的method(),首先会获取父类Father的内置锁然后获取Child的内置锁,当调用父类的方法(super.method();)时,需要再次后去父类的内置锁,如果不可重入,可能会陷入死锁。

        java多线程的可重入性的实现是通过每个锁关联一个请求计算和一个占有它的线程,当计数为0时,认为该锁是没有被占有的,那么任何线程都可以获得该锁的占有权。当某一个线程请求成功后,JVM会记录该锁的持有线程 并且将计数设置为1,如果这时其他线程请求该锁时则必须等待。当该线程再次请求请求获得锁时,计数会+1;当占有线程退出同步代码块时,计数就会-1,直到为0时,释放该锁。这时其他线程才有机会获得该锁的占有权。

         http://cmsblogs.com/?p=1643

         

原创粉丝点击