Java锁机制

Java锁有两种特性:并发性和可见性。

保证可见性的方法:Volatile Synchronized  Final(一旦初始化完成其他线程就可见)。

一. Volatile

Volatile能够使变量在发生改变后尽快让其他线程知道。

使用Volatile的原因:

编译器为了使程序的运行速度加快,对某些变量的写操作会在cpu或寄存器上进行,最后才写入内存,而在这个过程中,变量的新值对其他线程是不可见的。

public class RunThread extends Thread {
    private boolean isRunning = true;
    public boolean isRunning() {
        return isRunning;
    }
    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }
    @Override
    public void run() {
        System.out.println("进入到run方法");
        while (isRunning == true) {
        }
        System.out.println("线程执行完成");
    }
}
public class Run {
    public static void main(String[] args) {
        try {
            RunThread thread = new RunThread();
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

分析:main线程中调用了thread.setRunning(false)想让RunThread线程结束,但如果使用JVM -server参数执行程序时,并不会结束,而是进入了死循环。

原因:现在有两个线程:main线程和RunThread线程,两个线程都想对isRunning进行更改,按照JVM内存模型,main线程将setRunning读取到本地内存并进行修改,修改后刷新回主存,而在JVM设置为-server模式运行时,RunThread会在私有堆栈中读取isRunning变量,因此无法读取到被被main线程修改后的isRunning变量值,从而就会导致RunThread线程进入死循环,而无法终止。

解决方法:volatile private boolean isRunning=true;

原理分析:当对volatile标记的变量值进行修改时,会将其他线程中存储的值清除掉,然后重新读取。一般来说,会在修改A内存中的变量值之后,清除掉其他缓存中的值,当其他缓存中的线程B需要读取此变量的值时,先向总线发出请求,然后线程A接收到请求时,会将修改后的变量值传给线程B,最后将新值写入内存。volatile的作用是每次有变量的值进行更改的时候都会重复此步骤。

二. Synchronized

当synchronized修饰某一代码块或某一方法时,同一时段最多只能有一个线程访问该代码块,当有两个并发线程同时访问Object对象中的加锁代码块时,一个时间内只能有一个线程访问该加锁区域,另一个线程只能等待,必须等到当前线程执行完毕后才能访问该加锁区域,但此时该线程可以访问非加锁区域。

synchronized方法使用:

public synchronized void Run(){

   //方法体

}

synchronized的缺陷:

1.synchronized是java的一个关键字,也就是说拥有java语言内置对象的特性,当synchronized对某一代码块进行加锁时,同一时间只能有一个线程对该代码块进行访问,其他线程只能等待该线程释放锁,即便调用interrupt()方法也不会中断线程的等待(interrupt方法只能中断阻塞中的线程),只能一直等待下去,此时拥有锁的线程释放锁只有两种可能:

(1)获得锁的线程执行完毕释放锁。

(2)线程执行异常,此时JVM会让线程释放锁。

但是获得锁的线程如果要等待IO或由于其他原因(调用sleep方法等)阻塞了,而又没有释放锁,那么此时其他线程只能一直等待,从而降低程序执行效率。

2.当多个线程进行读写操作时会发生冲突,进行写写操作时也会发生冲突,但当多个线程进行读读操作时却不会发生冲突,当被synchronized修饰的代码块,即便所有线程都是进行读读操作,此时其他线程也需要等待,仍然会降低程序运行效率。

为解决以上缺陷,就会用Lock来修饰该代码块。

三. Lock

Lock是一个类,通过这个类可以实现线程的同步访问。

四. volatile和synchronized的区别

1.volatile只能修饰变量,而synchronized可以修饰变量和方法。

2.volatile不会造成线程的阻塞,而synchronized可能造成线程的阻塞。

3.volatile的本质是告诉JVM该变量的值时不确定的,只能从主存中读取,而synchronized的本质是对变量进行加锁,同一时刻只允许一个线程访问,其他线程只能等待。

4.volatile只能保证变量的修改可见性,而synchronized能保证变量的修改可见性和原子性。(《Java编程思想上》讲,定义long或double变量时,使用volatile可以获得原子性(简单的赋值操作和返回操作))。

5.当某一变量的值依赖于之前的值(如n=n+1 n++)或者当某一区域的值依赖于其他区域的值(如Range类的lower边界和upper边界必须满足lower<=upper)的时候,volatile将不在适用。

6.使用volatile的唯一安全情况是类中只有一个可变的域。

五. synchronized和lock的区别

1.synchronized是java的关键字,具有java内置语言的特性,而Lock是java中的类。

2.使用synchronized不需要手动释放锁,而使用Lock需要在finally块中调用unLock ()方法手动释放锁,如果不手动释放锁将会发生死锁现象。

3.Lock可以提高多线程读读操作的效率。

4.Lock可以调用方法中断阻塞的线程,而synchronized不可以,等待的线程只能一直等待。

5.通过Lock可以知道线程有没有获得锁,而synchronized无法办到。

在性能上讲,如果竞争资源不激烈时,Lock和synchronized的性能差不多,但当有大量线程竞争时,Lock的性能要远远优于synchronized,因此在使用时要看具体情况决定。

附:

wait(),sleep(),notify(),notifyall()讲解:

wait():释放占有的对象锁,线程进入线程池,释放cpu,此时其他线程可以抢占锁,获得锁的线程执行程序。

sleep():调用sleep()的线程会进入一段休眠时间,此时只释放cpu而不会释放对象锁,其他线程不会进入代码内部,休眠结束时,线程重新获得cpu执行代码。sleep()与wait()的最大区别在于wait()会释放对象锁而sleep()不会释放对象锁。

notify():唤醒因调用wait而等待的线程。其实就是对对象锁的的唤醒,从而使调用wait()方法的线程可以获得对象锁。调用notify并不会立即释放锁,而是继续执行synchronized代码块,直到执行完毕释放对象锁。JVM会在等待的过程中调用一个线程去获得对象锁,执行代码。需要注意的是,wait()和notify()必须在synchronized代码块中调用。

notifyall():唤醒所有线程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值