Synchronized和Lock接口

发布时间 2023-10-26 02:06:37作者: 长名06

Synchronized

Synchronized关键字回顾

synchronized是java中的关键字,是一种同步锁。它修饰的对象有以下几种:

  • 1.修饰一个代码块,被修饰的代码块称为同步代码块,其作用的范围是大括号{},括起来的代码,作用的对象是调用这个代码块的对象,synchronized不能修饰静态代码块。
  • 2.修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
  • 3.修饰一个静态方法,其作用范围是整个静态方法,作用的对象是这个类的所有对象。
  • 4.修饰一个类,其作用范围是synchronized后面括号括起来的部分,作用主要的对象是这个类的所有对象。

作用的对象,有点不了解。以及synchronized锁作用在this对象,和作用在类.class上有什么区别?学完后面的课程,记得来回答这个问题。

关于synchronized的理解,共有两种类型的锁:

(1)类锁:只有synchronized修饰静态方法或者修饰一个类的class对象时,才是类锁。
(2)对象锁:除了类锁,所有其他的上锁方式都认为是对象锁。比如synchronized修饰普通方法或者synchronized(this)给代码块上锁等。
应该注意的是,因为一个类只有一个class对象,因此所有的访问者在访问被加了类锁的代码时,都是共用同一把锁,而类的实例却可以有很多个,因此不同对象访问加了对象锁的代码,它们的访问互不干扰。

synchronized锁的访问规则

(1)加了相同锁的代码,它们的访问规则是相同的,即当某个访问者获得该锁时,它们一起向该访问者开放访问,向其他没有获得该锁的访问者关闭访问。
(2)加了不同锁的代码访问互相不干扰。
(3)而没有加锁的代码随时都可以任意访问,不受任何限制。

判断是否同一把synchronized锁

(1)不同类型的锁不是同一把锁。
(2)加的是对象锁,那么必须是同一个对象实例才是同一把锁。
(3)加的是类锁,那必须是同一类才是同一把锁。

对于sysnchronized修饰的代码能否访问

1.首先判断是不是同一把锁。
2.然后判断各自的访问规则。

注意事项

虽然synchronized关键字可以来修饰方法,但synchronized关键字不属于方法定义的一部分。因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,子类又覆盖了该方法,在子类中的方法默认情况下是不进行同步的,而必须显示在子类的方法上也加上synchronized关键字才可以。当然,也可以在子类中直接调用父类方法,这样虽然子类方法不同步但是方法体的内容,是同步的,因此相当于子类方法也同步。

多线程编程步骤(上)

第一,创建资源类,创建属性和操作方法。第二 创建多线程调用资源类的方法。

售票案例

sale 销售 ticket 票
案例要求,3个售票员进行售票,共售卖30张票。

代码
/**
 * @author 长名06
 * @version 1.0
 * 多线程编程步骤,第一步 创建资源类,定义属性和方法
 * 第二步,创建多个线程,调用资源类的操作方法
 */
public class SaleTickets {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 40; i++){
                ticket.sale();
            }
        }, "aa").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++){
                ticket.sale();
            }
        }, "bb").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++){
                ticket.sale();
            }
        }, "cc").start();

    }
}

class Ticket {
    //票数
    private static int number = 30;

    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + "\t 卖出一张票"
                    + "剩下票数为" + --number);
        }
    }
}
代码分析
  • 1.synchronized关键字修饰的非静态方法,此时这个方法的锁就是synchronized(this)锁,就是对调用当前方法的对象上锁。案例中的aa,bb,cc线程都是使用ticket这同一个对象,调用的sale()方法,所以这三个线程是互斥的,同时只能由三个中的一个,使用sale()方法。从输出结果,也可以看出,是互斥访问的。

  • 2.当一个线程获取了对应的锁,可以访问对应锁的代码块,其他需要访问该代码块的线程,就要等待。等待获取锁的线程释放锁,但是获取到锁的线程的执行有两种情况。
    1)获取锁的线程,执行对应的代码块,然后线程释放锁,无事发生,正常情况;
    2)拥有锁的线程执行中,出现了异常,此时JVM会让线程自动释放锁。

  • 3.但如果获取锁的线程的操作,需要等待IO或者其他原因(如sleep方法)被阻塞了,但是线程没有释放锁,其他的线程就只能等待,会很影响执行效率。所以需要一种机制可以不让等待的线程一直无限的等待下去(等待一定的时间,就能响应中断),通过Lock(java.util.concurrent.locks包下的接口)就可以实现。

  • 4.以上锁都是synchronized锁。

Lock

基本介绍

Lock锁实现,并提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock提供了比synchronized更多的功能。

Lock和synchronized的区别
  • 1.Lock不是java语言内置的关键字,synchronized是Java语言的关键字,是内置特性。Lock是一个接口,可以通过其实现类实现异步访问。
  • 2.采用synchronized关键字,不需要用户去手动释放锁,当synchronized关键字修饰的方法或代码块执行完之后,有JVM自动让线程释放锁。但是Lock则必须让用户手动释放锁,如果没有主动释放锁,就可能会出现死锁现象。
使用Lock实现sale_ticket案例
import java.util.concurrent.locks.ReentrantLock;
/**
 * @author 长名06
 * @version 1.0
 * 多线程编程步骤,第一步 创建资源类,定义属性和方法
 * 第二步,创建多个线程,调用资源类的操作方法
 */
public class SaleTickets {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 40; i++){
                ticket.sale();
            }
        }, "aa").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++){
                ticket.sale();
            }
        }, "bb").start();

        new Thread(() -> {
            for (int i = 0; i < 40; i++){
                ticket.sale();
            }
        }, "cc").start();

    }
}

class Ticket {
    //票数
    private static int number = 30;

    private final ReentrantLock lock = new ReentrantLock();

    public void sale() {
        lock.lock();//开启锁
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "\t 卖出一张票"
                        + "剩下票数为" + --number);
            }
        }finally {
            lock.unlock();//释放锁
        }

    }
}
关于Thread#start()&start0()
public synchronized void start() {//这个方法,完成线程的启动,但是实际创建线程是start0()方法完成的
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();//这里这个方法的调用,才是完成线程的创建
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

private native void start0();//此方法,java代码无法完成,而是用native关键字修饰的方法,就是使用jvm底层的JNI(Java Native Interface)机制调用c语言库完成的。这个创建线程的方法,以及是否创建,创建顺序,不是由java程序决定的,而是由程序运行的OS决定的。
lock接口方法
public interface Lock {
	void lock();//获取锁
	void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
	void unlock();//释放锁
    Condition newCondition();
}
lock方法

lock()方法使用最多的方法,功能,获取锁,如果锁被其他线程获取,则进行等待。
使用Lock,必须主动去释放锁,并且发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}finally{}块中进行,并且将释放锁的操作在finally块中,用来保证锁一定被释放,防止死锁的发生。通常使用Lock实现同步的,是以如下形式的。

lock.lock();//开启锁
try {
    //...具体代码
}catch(Exception e){
    
}
finally {
    lock.unlock();//释放锁
}
newCondition方法

关键字synchronizedwait()/notify()这两个方法一起使用,可实现等待/通知模式。Lock锁的newCondition()方法返回Condition对象,Condition类也可以实现等待/通知的模式。wait()和notify()是Object类下的方法,notify()被调用时,JVM会随机的唤醒某个等待的线程,使用Condition类可进行选择性通知,Condition常用的方法,awiat()方法,会使当前线程等待,同时会释放当前线程持有的锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。signal()用于唤醒等待的线程。

注意,在使用Condition接口(使用其具体的实现类)的await()和signa()方法前,需要线程持有相关的Lock锁,调用await()后线程会释放这个锁,在signal()调用后会从当前Condition对象的等待队列中,唤醒一个线程,唤醒的线程尝试获得锁,一旦获得锁成功,就执行。

小结

Lock和synchronized有以下几点不同

  • 1.Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现的。
  • 2.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。Lock在发生异常时,如果没有主动通过unLock()会释放锁,则很可能会造成死锁现象,因此使用Lock时需要在finally块中释放锁。
  • 3.Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一致等待,不能够使用中断。
  • 4.通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  • 5.Lock可以提高多个线程进行读操作的效率。
    在性能上来说,如果资源竞争不激烈,二者的性能是差不多的,而当资源竞争激烈时,此时Lock的性能要远远优于synchronized。

只是为了记录自己的学习历程,且本人水平有限,不对之处,请指正。