Java多线程的锁知识点总结(多实例-篇1)

所有java的 锁机制都可以分为乐观锁和悲观锁,悲观锁是实实在在的锁,乐观锁更像是一种保证业务数据安全的机制。

乐观锁和悲观锁

乐观锁

在操作资源的时候,乐观地认为没有线程与自己争抢资源,所以不会加锁,而是通过无锁编程的逻辑去实现。如:在对更新资源前去判断,该资源有无被更新,没有则进行更新,有则另外操作。

常用:版本号机制;CAS自旋

悲观锁

在对资源进行操作时,悲观地认为一定有线程与自己争抢资源。在修改资源的时候,会一定确保只有自己能操作该资源。synchronized关键字和Lock的实现类都是悲观锁

悲观锁之synchronized
特性:
  • 原子性:使用synchronized实现了同步,同步实现了原子性,保证了被同步的代码块在同一时间只有一个线程在执行。

  • 可见性:当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。

  • 有序性:禁止代码重排序

主要作用体现:

实例对象:当前实例加锁,进入同步代码前获取当前实例的锁。

代码块:对括号中的对象进行加锁

静态方法:对类class进行加锁。简称类锁。

1,一个对象中如果有多个synchronized方法,同一时刻,只有有一个线程去调用其中一个synchronized方法了,其它线程都只能等待,不能进去当前对象的其它的synchronized方法。这里的锁是锁的当前对象。(对象锁)

样例(大家可以看看,猜一下会先打印哪一行,运行后再回过头读读上面一段话)

package com.rojer.test.threadtest;
​
import java.util.concurrent.TimeUnit;
​
public class LockDemo {
​
    public synchronized void dog() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("this is a dog!");
    }
​
    public synchronized void cat() {
        System.out.println("this is a cat!");
    }
​
​
    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();
        new Thread(() -> {
            lockDemo.dog();
        }, "a").start();
        new Thread(() -> {
            lockDemo.cat();
        }, "b").start();
    }
}

2,锁只与有锁的方法有关,如果执行的是普通方法,那么与上情况无关(对象锁)

package com.rojer.test.threadtest;
​
import java.util.concurrent.TimeUnit;
​
public class LockDemo {
​
    public synchronized void dog() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("this is a dog!");
    }
​
    public synchronized void cat() {
        System.out.println("this is a cat!");
    }
​
    private void fish() {
        System.out.println("this is a fish!");
    }
​
​
    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();
        new Thread(() -> {
            lockDemo.dog();
        }, "a").start();
        new Thread(() -> {
            lockDemo.fish();
        }, "b").start();
    }
}

结果输出

3,锁与对象息息相关,两个对象,就不是同一把锁了(对象锁)

package com.rojer.test.threadtest;
​
import java.util.concurrent.TimeUnit;
​
public class LockDemo {
​
    public synchronized void dog() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("this is a dog!");
    }
​
    public synchronized void cat() {
        System.out.println("this is a cat!");
    }
​
    private void fish() {
        System.out.println("this is a fish!");
    }
​
​
    public static void main(String[] args) {
        LockDemo lockDemo1 = new LockDemo();
        LockDemo lockDemo2 = new LockDemo();
        new Thread(() -> {
            lockDemo1.dog();
        }, "a").start();
        new Thread(() -> {
            lockDemo2.cat();
        }, "b").start();
    }
}

输出结果

4,如果将锁的方法改为静态同步方法之后,情况又有不同。

对于普通同步方法而言,锁的是当前的实例对象,通常指的是this,如代码 LockDemo lockDemo1 = new LockDemo();中的lockDemo1。

对于静态同步方法而言,锁的是类的class对象。如代码 LockDemo lockDemo1 = new LockDemo();中的LockDemo。

对于同步方法块,锁的是synchronized括号内的对象

例子

package com.rojer.test.threadtest;
​
import java.util.concurrent.TimeUnit;
​
public class LockDemo {
​
    public static synchronized void dog() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("this is a dog!");
    }
​
    public static synchronized void cat() {
        System.out.println("this is a cat!");
    }
​
    private void fish() {
        System.out.println("this is a fish!");
    }
​
​
    public static void main(String[] args) {
        LockDemo lockDemo1 = new LockDemo();
        LockDemo lockDemo2 = new LockDemo();
        new Thread(() -> {
            lockDemo1.dog();
        }, "a").start();
        new Thread(() -> {
            lockDemo2.cat();
        }, "b").start();
    }
}

运行结果:

5,对象锁和类锁是两把不同的锁,对象锁锁的是实例对象本身,类锁锁的是类的class对象。

静态同步方法与普通同步方法之间是不存在竞争关系的。

例子

package com.rojer.test.threadtest;
​
import java.util.concurrent.TimeUnit;
​
public class LockDemo {
​
    public static synchronized void dog() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("this is a dog!");
    }
​
    public synchronized void cat() {
        System.out.println("this is a cat!");
    }
​
    private void fish() {
        System.out.println("this is a fish!");
    }
​
​
    public static void main(String[] args) {
        LockDemo lockDemo1 = new LockDemo();
        LockDemo lockDemo2 = new LockDemo();
        new Thread(() -> {
            lockDemo1.dog();
        }, "a").start();
        new Thread(() -> {
            lockDemo2.cat();
        }, "b").start();
    }
}

运行结果 :

注意:一般请使用最小力度锁,能锁代码块,就不要锁对象,能锁对象就不要用类锁。

我们再来个例子加深一下对synchronized锁运用的理解。

package com.rojer.test;
​
import java.io.IOException;
​
public class MainTest {
    private static /*volatile*/ boolean running = true;
​
    private static void runs() {
        System.out.println("start runs");
        while (running) {
            //System.out.println("Rojer");
        }
        System.out.println("runs end");
    }
​
    public static void main(String[] args) throws InterruptedException, IOException {
        new Thread(MainTest::runs, "t1").start();
        Thread.sleep(1000);
        running = false;
        System.in.read();
    }
​
}

大家可以先拿出来,直接运行看看。

这块代码当它运行起来的时候,会发现它只会打印start runs,且运行不会停止

而当我们将while循环中的语句放开时就会出现runs end。

原因在于println方法,被synchronized修饰了。synchronized是保证可见性的,它会将线程缓存中的数据回刷(内存和缓存的更新),使线程获取到更新的值。

悲观锁之公平锁与非公平锁

公平锁:指定是多个线程按照申请锁的顺序进行排队获取锁。实现:ReentrantLock lock = new ReentrantLock(true);

非公平锁:指的是多线程获取锁的顺序并不是按照申请的顺序进行的,有可能后申请的反而会先执行,在高并发环境下可能会出现饥饿状态(饥饿指的是某个线程一直处理等待状态)

实现:ReentrantLock lock = new ReentrantLock(false); 或ReentrantLock lock = new ReentrantLock();

synchronized也是非公平锁。

思考:

1,为什么锁默认是非公平锁?

2,公平锁应用场景 有哪些?

样例(可自己手动改为公平锁试试):

package com.rojer.test.threadtest;
​
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
​
public class LockDemo {
​
    private int number = 20;
    ReentrantLock lock = new ReentrantLock(false);
​
    public void sale() {
        lock.lock(); // 操作资源前先加锁
        try {
            if (number > 0 ) {
                System.out.println(Thread.currentThread().getName() + "卖出第:" + number-- + "   还剩余票: " + number);
            }
        } finally {
            lock.unlock();
        }
    }
​
    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();
        for (int t = 0; t < 3; t++) {
            new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    lockDemo.sale();
                }
            }, "线程" + t).start();
        }
    }
}
​

非公平执行结果展示(结果不一定一致):

公平锁执行结果(结果不一定一致):

这个时候我们再回过头想想,为什么锁默认是非公平的?

我们知道程序对锁的切换是要耗费一定资源的,对我们人来说这个切换时间的消耗几乎感觉不到,但是对于cpu来说这个时间是非常明显的,所以非公平锁能更好的充分利用cpu的时间片,减少线程切换的开销,减少cpu的空闲时间。

悲观锁之可重入锁

概念:指的是同一个线程在外层方法获取锁的时候,在进入内存方法会自动获取锁(前提:锁的是同一个对象),不会因为之前已经获取过还没释放而阻塞。

ReentrantLock和synchronized都是可重入锁

样例:

package com.rojer.test.threadtest;
​
import java.util.concurrent.locks.ReentrantLock;
​
public class LockDemo {
//    ReentrantLock lock = new ReentrantLock(true);
​
    public synchronized void a() {
        System.out.println(Thread.currentThread().getName() + "运行了a");
        b();
    }
​
    public synchronized void b () {
        System.out.println(Thread.currentThread().getName() + "运行了b");
        c();
    }
​
    public synchronized void c() {
        System.out.println(Thread.currentThread().getName() + "运行了c");
    }
​
​
​
    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();
        new Thread(() -> {
            lockDemo.a();
        }, "线程Rojer").start();
    }
}

运行结果:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值