zoukankan      html  css  js  c++  java
  • Java锁机制

    线程同步

    什么是线程同步

    线程之间执行是有先后顺序的,一个线程要等待上一个线程执行完之后才开始执行当前的线程。

    为什么要线程同步

    java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,所以需要线程同步执行,保证了该变量的唯一性和准确性。

    如何实现线程同步

    多线程的线程同步机制实际上是靠锁的概念来控制的。  1)synchronized关键字   2)java.util.concurrent.lock包中的Lock对象

    synchronized关键字 
    synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问。

    1. 在Java程序运行时环境中,JVM需要对两类线程共享的数据进行协调:1)保存在堆中的实例变量  2)保存在方法区中的类变量  这两类数据是被所有线程共享的(Java栈总的数据是线程私有的,不需要协调)。

    2. 在java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的。对于对象来说,相关联的监视器保护对象的实例变量。对于类来说,监视器保护类的类变量。(如果一个对象没有实例变量,或者一个类没有变量,相关联的监视器就什么也不监视。) 

    3. 为了实现监视器的排他性监视能力,java虚拟机为每一个对象和类都关联一个锁。代表任何时候只允许一个线程拥有的特权。 如果线程获取了锁,那么在它释放这个锁之前,就没有其他线程可以获取同样数据的锁了。(锁住一个对象就是获取对象相关联的监视器)

        类锁实际上用对象锁来实现。当虚拟机装载一个class文件的时候,它就会创建一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。

    4. 一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。java编程人员不需要自己动手加锁,对象锁是java虚拟机内部使用的。

    5. 在java程序中,只需要使用synchronized块或者synchronized方法就可以标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类。

     

    使用方式

    1. synchronized关键字修饰方法。public synchronized void save(){}  synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

    2. synchronized关键字修饰代码块。synchronized(object){}  同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。 

    案例1

    复制代码
    public class ThreadTest extends Thread {
        private int threadNo;
    
        public ThreadTest(int threadNo) {
            this.threadNo = threadNo;
        }
    
        @Override
        public synchronized void run() {
            for(int i = 1; i < 10000; i++){
                System.out.println("No." + threadNo + ":" + i);
            }
        }
    
        public static void main(String[] args) throws Exception {
            for(int i = 1; i < 10; i++){
                new ThreadTest(i).start();
                Thread.sleep(1);
            }
        }
    
    }
    复制代码

    这个程序其实就是让10个线程在控制台上数数,从1数到9999。理想情况下,我们希望看到一个线程数完,然后才是另一个线程开始数数。但是这个程序的执行过程告诉我们,这些线程还是乱糟糟的在那里抢着报数,丝毫没有任何规矩可言。

    但是细心的读者注意到:run方法还是加了一个synchronized关键字的,按道理说,这些线程应该可以一个接一个的执行这个run方法才对阿。

    对于一个成员方法加synchronized关键字,这实际上是以这个成员方法所在的对象本身作为对象锁

    在本例中就是 以ThreadTest类的一个具体对象,也就是该线程自身作为对象锁的。一共十个线程,每个线程持有自己 线程对象的那个对象锁。这必然不能产生同步的效果。

    案例2

    复制代码
    public class ThreadTest extends Thread {
        private int threadNo;
        private String lock;
    
        public ThreadTest(int threadNo, String lock) {
            this.threadNo = threadNo;
            this.lock = lock;
        }
    
        public void run() {
            synchronized(lock){
                for(int i = 1; i < 10000; i++){
                    System.out.println("No." + threadNo + ":" + i);
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            String lock = new String("lock");
            for(int i = 1; i < 10; i++){
                new ThreadTest(i, lock).start();
                Thread.sleep(1);
            }
        }
    
    }
    复制代码

    该程序通过在main方法启动10个线程之前,创建了一个String类型的对象。并通过ThreadTest的构造函数,将这个对象赋值给每一个ThreadTest线程对象中的私有变量lock。

    根据Java方法的传值特点,这些线程的lock变量实际上指向的是堆内存中的同一个区域,即存放main函数中的lock变量的区域。

    程序将原来run方法前的synchronized关键字去掉,换用了run方法中的一个synchronized块来实现。这个同步块的对象锁,就是 main方法中创建的那个String对象。换句话说,他们指向的是同一个String类型的对象,对象锁是共享且唯一的!

    于是,我们看到了预期的效果:10个线程不再是争先恐后的报数了,而是一个接一个的报数。

    案例3        

    复制代码
    public class ThreadTest extends Thread {
        private int threadNo;
    
        public ThreadTest(int threadNo) {
            this.threadNo = threadNo;
        }
    
        public void run() {
            abc(threadNo);
        }
    
        public static void main(String[] args) throws Exception {
            for(int i = 1; i < 20; i++){
                new ThreadTest(i).start();
                Thread.sleep(1);
            }
        }
    
        public static synchronized void abc(int threadNo) {
            for(int i = 1; i < 10000; i++){
                System.out.println("No." + threadNo + ":" + i);
            }
        }
    
    }
    复制代码

    这段代码没有使用main方法中创建的String对象作为这10个线程的线程锁。而是通过在run方法中调用本线程中一个静态的同步 方法abc而实现了线程的同步。

    这里synchronized静态方法是用什么来做对象锁的呢?对于同步静态方法,对象锁就是该静态放发所在的类的Class实例,由于在JVM中,所有被加载的类都有唯一的类对象,具体到本例就是唯一的 ThreadTest.class对象。不管我们创建了该类的多少实例,但是它的类实例仍然是一个!


    总结

    1. 对于同步的方法或者代码块来说,必须获得对象锁才能够进入同步方法或者代码块进行操作;

    2. 如果采用method级别的同步,则对象锁即为method所在的对象,如果是静态方法,对象锁即指method所在的Class对象(唯一);

    3. 对于代码块,对象锁即指synchronized(abc)中的abc;

    4. 同步有两种方式,同步块和同步方法。

        如果是同步代码块,则对象锁需要编程人员自己指定,一般有些代码为synchronized(this)只有在单态模式才生效。如果是同步方法,则分静态和非静态两种,静态方法则一定会同步,非静态方法需在单例模式才生效,推荐用静态方法(不用担心是否单例)。

    5. 在Java多线程编程中,最常见的synchronized关键字实际上是依靠对象锁的机制来实现线程同步的。

    Lock详解http://www.cnblogs.com/aishangJava/p/6555291.html

    死锁问题

    所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态 或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

    产生死锁的条件

    互斥条件

    指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

    请求和保持条件

    指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

    不剥夺条件

    进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

    环路等待条件

    指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

    处理方法

    预防死锁:破坏四个必要条件中的一个或多个

    避免死锁

    检测死锁

    解除死锁

  • 相关阅读:
    121. Best Time to Buy and Sell Stock
    70. Climbing Stairs
    647. Palindromic Substrings
    609. Find Duplicate File in System
    583. Delete Operation for Two Strings
    556 Next Greater Element III
    553. Optimal Division
    539. Minimum Time Difference
    537. Complex Number Multiplication
    227. Basic Calculator II
  • 原文地址:https://www.cnblogs.com/moxiaotao/p/9767963.html
Copyright © 2011-2022 走看看