Java并发(二) 线程锁机制

805 阅读3分钟
  • 为什么要有线程锁?

多个线程之间会抢夺资源,所以有可能一个线程执行到一半,就被另一个线程抢夺了资源,这样就会造成线程的不安全,为了保证线程的安全性,我们可以使用线程锁来解决这个问题。

比如下面的例子,正常应该打印出两句“今天你学习了吗?”,但是有可能thread1刚执行到一半,thread2就抢夺了资源,所以就会导致执行顺序错乱

public class ThreadDemo {

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        myThread1.start();
        myThread2.start();
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            for (char c : "今天你学习了吗?".toCharArray()) {
                System.out.print(c);
            }
        }
    }
}

执行结果: 今天你学习今天你学了吗?习了吗? //顺序错乱❌
  • 怎么解决线程不安全?

上面的例子会导致线程不安全,我们可以使用synchronized来解决:

public class ThreadDemo {

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        myThread1.start();
        myThread2.start();
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            //这里加锁✅
            synchronized (ThreadDemo.class){
                for (char c : "今天你学习了吗?".toCharArray()) {
                    System.out.print(c);
                }
            }
        }
    }
}

执行结果: 今天你学习了吗?今天你学习了吗? //顺序正确✅
  • Synchronized详解

上面代码我们只需要加上synchronized就可以解决线程安全问题,synchronized也被我们成为“同步锁”,synchronized是一个关键字,可以修饰在成员方法也可以单独写成同步代码块,被synchronized修饰的代码块中,同一时刻最多只能有一个线程执行代码块中的代码。 在这里插入图片描述

从上面图中可以看到,为了防止多个线程同时修改代码影响代码的安全性,我们用锁将一部分代码锁了起来,多个线程之间要抢夺锁,同时只有一个线程可以拿到锁获得执行权限,其他线程只能在外面等着这个线程释放锁,然后再次抢夺锁获得执行权限。这样就可以解决线程不安全的问题。

  • 对象锁

方法一
private synchronized void count(){
    ...
}

方法二
private void count(){
    synchronized (this){
        ...
    }
}

方法三
private Object mObject = new Object();

private void count(){
    synchronized (mObject){
		...
    }
}
  • 类锁

方法一
private synchronized static void count(){
    ...
}

方法二
private static void count(){
    synchronized (ThreadDemo.class){
		...
    }
}

方法三
private static Object mObject = new Object();

private void count(){
    synchronized (mObject){
		...
    }
}
  • 总结

对象锁: 对象锁锁的是对象的实例,多线程使用的时候需要保证是同一个实例,否则加锁将没有意义。

类锁: 类锁锁的是类的唯一class对象,静态方法加synchronized默认就是类锁。

注意: 类锁和对象锁可以同时使用,互不干扰。

  • wait()和notify()、notifyAll()

wait(): 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态,直到被notify()/notifyAll()唤醒。

notify(): 唤醒一个处于wait状态的线程。

notifyAll(): 唤醒所有处于wait状态的线程。

  • 两个线程交替打印0~100

利用wait()和notify()以及synchronized可以很轻松实现这个功能:

public class ThreadDemo {

    private static int num;
    private static Object sObject = new Object();

    public static void main(String[] args) {
        new Thread(new MyRunnable(),"线程1").start();
        new Thread(new MyRunnable(),"线程2").start();
    }

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (sObject){
                    System.out.println(Thread.currentThread().getName() + ": "+ num++);
                    sObject.notify();
                    if (num>=100) return;
                    try {
                        sObject.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

}

打印:
线程1: 0
线程2: 1
线程1: 2
...
线程2: 99
线程1: 100