欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java锁机制

程序员文章站 2022-07-08 15:36:46
java虚拟机,它里面内存结构的堆和方法区,是所有线程数据共享的区域。所以当多个线程对其资源进行竞争使用时就会可能会出现难以预料的结果,所以我们需要用锁机制来对他进字样是为了帮助对象来对其而成。每个java对象都有一把对象锁,存在对象头中。java中的对象主要分为三个部分:对象头、实例数据、填充字节。填充字节:主要是为了帮助对于对象来这个数据就是你在初始化对象设定的属性和状态的那种。实例数据:就是在初始对象时锁设属性和状态内容。对象头:存放了一些对象运行时的信息(运行状态信息)。分2部分1.Ma...

java虚拟机,它里面内存结构的堆和方法区,是所有线程数据共享的区域。所以当多个线程对其资源进行竞争使用时就会可能会出现难以预料的结果,所以我们需要用锁机制来对其进行限制。

每个java对象都有一把对象锁,存在对象头中。

java中的对象主要分为三个部分:对象头、实例数据、填充字节。

填充字节:对象占用的字节必须是8字节的倍数,填充字节用来填充字节数。

实例数据:就是在初始对象时锁设属性和状态内容。

对象头:存放了一些对象运行时的信息(运行状态信息)。分2部分

1.Mark word:存储了很多和当前对象运行时状态有关的数据。 Java锁机制

2.Class Point:这是一个指针,它指向了当前对象类型所在方法区中对应的类型数据

synchronized被编译后会生成monitorenter和monitorexit2个字节码指令,依赖这两个指令来进行线程同步。

synchronized的锁一共有4种状态。线程通过锁标志位来确定对象锁的状态

无锁:就是没有对资源进行锁定,所有线程都能访问到同一资源。

1.无竞争:如果该资源没有在多线程环境下,无需进行线程之间的竞争,那么就不需要给他加锁。

2.存在竞争:如果该资源存在竞争,但是我又不想给他进行加锁锁定。那么就可以进行无锁编程使用CAS(compare and swap),能保证只有一个线程能修改成功。其他修改失败的线程就会进行不断的尝试。

偏向锁:现在给对象加锁,但是在实际运行过程中只有一个线程来获取这个对象锁。那么我们就可以在Mark word中记录这个线程的id,那么只要是这个线程过来获取锁时,我们就可以直接把锁交出去。我们就可以认为这个锁偏爱这个线程。

如果发现运行过程中不止一个线程,而是多个线程都在来竞争这个锁,那么就会将偏向锁升级为轻量级锁。

轻量级锁:当一个线程想要获取某个对象的锁时,假如看到锁的标志位是00,那么就可以判断这个锁的状态为轻量级锁。这时线程就会在自己的虚拟栈中开辟一个被称为Lock Record的空间(虚拟机栈是线程私有的空间)。Lock record当中,他存放的是Mark word中的副本以及owner指针。所以当线程成功获取该所时,他就会将该对象头中的Mark word复制到Lock record中,并且将Lock record中的owner指针指向该对象。同时,对象中Mark word的前30bit就会生成一个指针来指向该线程虚拟机栈中的Lock record。这样一来就实现了对象和线程的绑定。他们就互相知道了对方的存在,这时这个对象就被该线程锁定了。获取了这个对象锁的线程就可以相应的去执行一些任务。

而这次如果又有其他的线程想要获取这个对象所怎么办呢?其他线程就会进行自旋等待。如果自旋等待的线程数超过一个,那么对象锁就会由轻量级锁升级为重量级锁。

(自旋可以把它看成是一种轮询,就是线程自己不断的循环尝试着去看一下目标对象的锁有没有释放,自旋它就相当于是CPU的空转,如果线程长时间的自旋,但会极大地消耗系统性能。自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。

重量级锁:需要Monitor对象来对线程进行控制,此时将会完全锁定资源。

Monitor翻译管制/监视器。一个线程进入了monitor,那么其他线程只能进行等待。只有当这个线程退出,其他线程才有进入的机会。

首先在Entry Set的中聚集了一些想要进入monitored的线程,他们正处于在waiting状态。假如线程A他进入了monitor,那么此时他会处于在active状态。假如A线程他在执行过程中出现了一个判断条件,需要他暂时让出该执行权,那么他将进入到Wait Set中,同时状态也会被标记为waiting。而Entry Set中的线程,就有机会进入monitor,假如一个线程B成功进入了。那么他就可以通过notify来唤醒Wait set中的线程A,让线程A再次进入monitor执行任务执,行完毕后线程A变便可以退出。这就是synchronized同步机制。

但是synchronized存在性能问题。因为synchronized编译后会生成monitorenter和monitorexit2个字节码指令,而monitor是依赖于操作系统的mutex lock(互斥锁)来实现的,Java线程实际是对操作系统线程的映射,所以每当挂起或唤醒一个线程时,都要切换操作系统内核态,所以有些情况下甚至线程切换的时间会超过程序执行的时间。这样的话使用synchronized将会对程序的性能产生很严重的影响。所以从Java6开始synchronized进行了优化,引入了偏向锁、轻量级锁。引入后,锁一共有四种状态,分别为无锁、偏向锁、轻量级锁和重量级锁。同时锁只能升级不能降级。
Java锁机制
简单理解的话:每个对象可以锁多个代码(即可以在N个方法前加synchronized),但是只有一把钥匙(锁对象:只有线程占有锁对象才能访问,即有一个线程在访问该对象的某个同步方法,其他线程则必须等待该线程)

Synchronized修饰普通同步方法:锁对象当前实例对象

Synchronized修饰静态同步方法:锁对象是当前的类Class对象

Synchronized修饰同步代码块:锁对象是Synchronized后面括号里配置的对象,这个对象可以是某个对象(xlock),也可以是某个类(Xlock.class)

修饰非静态方法的都是对象锁,而修饰在static上面的,他是类class锁,所以当访问对象中的被修饰静态方法时它不能同时访问两个。

class  Mthreads {
	public void countAdd() {
            synchronized(this){
                        System.out.println(Thread.currentThread().getName() + ":" + (count++));    
            }
        }
	 public void printCount() {
            synchronized(name) {
                        System.out.println(Thread.currentThread().getName() + " count:" + count);
                }
        }
}
//该对象中synchronized修饰了2个同步代码块,而锁对象(可以看成是钥匙)就是synchronized()小括号里面的,一个是this对象本身,一个是name字段,锁对象不同所以可以同时访问该类的2个方法
//如果小括号里面的都是this。那么就不能同时访问了,谁先拿到该锁对象(Mthreads)谁就先用。

自用测试代码:

public class AllTests {
    @Test
    public void test(){
            System.out.println("使用关键字synchronized");
            Mthreads mt=new Mthreads();
            Thread thread1 = new Thread(mt, "mt1");
            Thread thread2 = new Thread(mt, "mt2");
            thread1.start();
            thread2.start();
            while (true){

            }
        }
    }
    class  Mthreads implements Runnable{
        private int count;
        private final String name="aa";

        public Mthreads() {
            count = 0;
        }

      synchronized   public static void add() {

                for (int i = 0; i < 5; i ++) {
                    try {
                        System.out.println(11111);
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        }
        synchronized       public static void leve() {
               for (int i = 0; i < 5; i++) {
                   try {
                       System.out.println(22222);
                       Thread.sleep(100);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }

       }



        public void countAdd() {
            synchronized(this) {
                for (int i = 0; i < 5; i ++) {
                    try {
                        System.out.println(Thread.currentThread().getName() + ":" + (count++));
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        //非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
        public void printCount() {
            synchronized(name) {
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(100);
                        System.out.println(Thread.currentThread().getName() + " count:" + count);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        public void run() {
            String threadName = Thread.currentThread().getName();
            if (threadName.equals("mt1")) {
//                countAdd();
                add();
            } else if (threadName.equals("mt2")) {
//                printCount();
                leve();
            }
        }

    }

本文地址:https://blog.csdn.net/weixin_46072357/article/details/114260468

相关标签: 笔记