java面试系列--J2SE基础(七)

本文深入探讨Java线程同步的方法,包括synchronized、Lock、ReentrantLock等,以及锁的等级——方法锁、对象锁、类锁。通过实例详细解释了同步块的使用和静态方法同步,帮助理解Java线程同步的关键概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

22. 线程同步的方法:sychronized、lock、reentrantLock等。

synchronized:
在资源竞争一般,偶尔需要同步,synchronized是很合适的;因为编译程序通常会尽可能的进行优化synchronize。所以synchronized可读性非常好,容易理解和使用,但是对竞争资源激烈时会降低性能;

ReentrantLock: 

提供了多样化的同步,可以根据具体情况获取锁(lock()、tryLock()、tryLock(long timeout, TimeUnit unit) )。在资源竞争不激烈的情形下,性能稍微比synchronized差。但是对竞争资源非常激烈的时候,ReentrantLock依然能保持较好的性能。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/** 
 * @author:Mars 
 * @version Revision 1.0.0 
 * @see: 
 * @创建日期:2018年1月30日 
 * @功能说明: 
 */ 
public class Demo implements Runnable{
    
    int j = 0;
    static int count = 0;
    ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        
        System.err.println(count);
        
        Demo demo = new Demo();
        Thread thread = new Thread(demo);
        Thread thread1 = new Thread(demo);
        Thread thread2 = new Thread(demo);
        
        thread.start();
        thread1.start();
        thread2.start();
        
        thread.join();
        thread1.join();
        thread2.join();
        
        System.err.println(count);

    }

    @Override
    public void run() {
        int i = 0;
        //不加锁,执行结果多种多样
        //method1(i);
        
        //加锁方式一
        //method2(i);
        
        //加锁方式二
        //method3(i);
        
        //method4(i);
        method5(i);
        
    }

    @SuppressWarnings("unused")
    private void method5(int i){
        while (i < 5000) {
            if (reentrantLock.tryLock()) {
                count++;
                reentrantLock.unlock();
                i++;
            }
        }
    }
    
    @SuppressWarnings("unused")
    private void method4(int i) {
        while (i < 5000) {
            try {
                if (reentrantLock.tryLock(10, TimeUnit.SECONDS)) {
                    count++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (reentrantLock.isLocked()) {
                reentrantLock.unlock();
            }
            i++;
        }
    }
    
    @SuppressWarnings("unused")
    private void method3(int i){
        while(i<5000){
            reentrantLock.lock();
            count++;
            reentrantLock.unlock();
            i++;
        }
    }

    @SuppressWarnings("unused")
    private void method2(int i) {
        while(i<5000){
            synchronized (this) {
                count++;
            }
            i++;
        }
    }

    @SuppressWarnings("unused")
    private void method1(int i) {
        while (i < 5000) {
            count++;
            i++;
        }
    }

}
下面的demo可以实际的看到各个线程互相竞争,交叉执行

import java.util.concurrent.locks.ReentrantLock;

/** 
 * @author:Mars 
 * @version Revision 1.0.0 
 * @see: 
 * @创建日期:2018年1月30日 
 * @功能说明: 
 */
public class Demo2 implements Runnable {

    static int count = 0;
    ReentrantLock reentrantLock = new ReentrantLock();
    String lastThreadName = "";

    public static void main(String[] args) throws Exception {

        Demo2 demo = new Demo2();
        Thread thread = new Thread(demo);
        Thread thread1 = new Thread(demo);
        Thread thread2 = new Thread(demo);

        thread.start();
        thread1.start();
        thread2.start();

        thread.join();
        thread1.join();
        thread2.join();

        System.err.println(count);

    }

    @Override
    public void run() {
        int i = 0;
        method2(i);
    }

    private void method2(int i) {
        while (i < 5000) {
            synchronized (this) {
                if (!lastThreadName.equals(Thread.currentThread().getName())) {
                    lastThreadName = Thread.currentThread().getName();
                    System.err.println(Thread.currentThread().getName());
                }
                count++;
            }
            i++;
        }
    }

}

23. 锁的等级:方法锁、对象锁、类锁。

Java中锁的机制 synchronized: 
在修饰代码块的时候需要一个 reference对象作为锁的对象. 
在修饰方法的时候默认是 当前对象作为锁的对象. 
在修饰类时候默认是 当前类的Class对象作为锁的对象.

对象锁(方法锁),是针对一个对象的,它只在该对象的某个内存位置声明一个标识该对象是否拥有锁,所有它只会锁住当前的对象,一般一个对象锁是对一个非静态成员变量进行synchronized修饰,或者对一个非静态成员方法进行synchronized进行修饰,对于对象锁,不同对象访问同一个被synchronized修饰的方法的时候不会阻塞

类锁是锁住整个类,当有多个线程来声明这个类的对象时候将会被阻塞,直到拥有这个类锁的对象呗销毁或者主动释放了类锁,这个时候在被阻塞的线程被挑选出一个占有该类锁,声明该类的对象。其他线程继续被阻塞住。

(1)当同一个对象在线程1中访问一个方法,在线程2中再去访问另外一个加锁方法,则同样也会被阻塞.
(2)对于类锁,则会把整个类锁住,也就说只能有一个对象拥有当前类的锁。当一个对象拥有了类锁之后,另外一个对象还想竞争锁的话则会被阻塞。两个对象A,B,如果A正在访问一个被类锁修饰的方法,那么B则不能访问。因为类锁只能在同一时刻被一个对象拥有。相对于对象锁,则是不同。还是A,B两个对象,如果A正在访问对象锁修饰的function,那么这个时候B也可以同时访问。
(3)对于对象锁,当一个对象拥有锁之后,访问一个加了对象锁的方法,而该方法中又调用了该类中其他加了对象锁的方法,那么这个时候是不会阻塞住的。这是java通过可重入锁机制实现的。可重入锁指的是当一个对象拥有对象锁之后,可以重复获取该锁。因为synchronized块是可重入的,所以当你访问一个对象锁的方法的时候,在该方法中继续访问其他对象锁方法是不会被阻塞的。

延伸阅读:Java同步块 

 http://ifeve.com/synchronized-blocks/

Java 同步块(synchronized block)用来标记方法或者代码块是同步的。Java同步块用来避免竞争。本文介绍以下内容:

  • Java同步关键字(synchronzied)
  • 实例方法同步
  • 静态方法同步
  • 实例方法中同步块
  • 静态方法中同步块
  • Java同步示例

Java 同步关键字(synchronized)

Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。

有四种不同的同步块:

  1. 实例方法
  2. 静态方法
  3. 实例方法中的同步块
  4. 静态方法中的同步块

上述同步块都同步在不同对象上。实际需要那种同步块视具体情况而定。

实例方法同步

下面是一个同步的实例方法:

1public synchronized void add(int value){
2this.count += value;
3 }

注意在方法声明中同步(synchronized )关键字。这告诉Java该方法是同步的。

Java实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。只有一个线程能够在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。一个实例一个线程。

静态方法同步

静态方法同步和实例方法同步方法一样,也使用synchronized 关键字。Java静态方法同步如下示例:

1public static synchronized void add(int value){
2 count += value;
3 }

同样,这里synchronized 关键字告诉Java这个方法是同步的。

静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。

对于不同类中的静态同步方法,一个线程可以执行每个类中的静态同步方法而无需等待。不管类中的那个静态同步方法被调用,一个类只能由一个线程同时执行。

实例方法中的同步块

有时你不需要同步整个方法,而是同步方法中的一部分。Java可以对方法的一部分进行同步。

在非同步的Java方法中的同步块的例子如下所示:

1public void add(int value){
2 
3    synchronized(this){
4       this.count += value;
5    }
6  }

示例使用Java同步块构造器来标记一块代码是同步的。该代码在执行时和同步方法一样。

注意Java同步块构造器用括号将对象括起来。在上例中,使用了“this”,即为调用add方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法本身的实例作为监视器对象。

一次只有一个线程能够在同步于同一个监视器对象的Java方法内执行。

下面两个例子都同步他们所调用的实例对象上,因此他们在同步的执行效果上是等效的。

01public class MyClass {
02 
03   public synchronized void log1(String msg1, String msg2){
04      log.writeln(msg1);
05      log.writeln(msg2);
06   }
07 
08   public void log2(String msg1, String msg2){
09      synchronized(this){
10         log.writeln(msg1);
11         log.writeln(msg2);
12      }
13   }
14 }

在上例中,每次只有一个线程能够在两个同步块中任意一个方法内执行。

如果第二个同步块不是同步在this实例对象上,那么两个方法可以被线程同时执行。

静态方法中的同步块

和上面类似,下面是两个静态方法同步的例子。这些方法同步在该方法所属的类对象上。

01public class MyClass {
02    public static synchronized void log1(String msg1, String msg2){
03       log.writeln(msg1);
04       log.writeln(msg2);
05    }
06 
07    public static void log2(String msg1, String msg2){
08       synchronized(MyClass.class){
09          log.writeln(msg1);
10          log.writeln(msg2);
11       }
12    }
13  }

这两个方法不允许同时被线程访问。

如果第二个同步块不是同步在MyClass.class这个对象上。那么这两个方法可以同时被线程访问。

Java同步实例

在下面例子中,启动了两个线程,都调用Counter类同一个实例的add方法。因为同步在该方法所属的实例上,所以同时只能有一个线程访问该方法。

01public class Counter{
02     long count = 0;
03 
04     public synchronized void add(long value){
05       this.count += value;
06     }
07  }
08  public class CounterThread extends Thread{
09 
10     protected Counter counter = null;
11 
12     public CounterThread(Counter counter){
13        this.counter = counter;
14     }
15 
16     public void run() {
17    for(int i=0; i<10; i++){
18           counter.add(i);
19        }
20     }
21  }
22  public class Example {
23 
24    public static void main(String[] args){
25      Counter counter = new Counter();
26      Thread  threadA = new CounterThread(counter);
27      Thread  threadB = new CounterThread(counter);
28 
29      threadA.start();
30      threadB.start();
31    }
32  }

创建了两个线程。他们的构造器引用同一个Counter实例。Counter.add方法是同步在实例上,是因为add方法是实例方法并且被标记上synchronized关键字。因此每次只允许一个线程调用该方法。另外一个线程必须要等到第一个线程退出add()方法时,才能继续执行方法。

如果两个线程引用了两个不同的Counter实例,那么他们可以同时调用add()方法。这些方法调用了不同的对象,因此这些方法也就同步在不同的对象上。这些方法调用将不会被阻塞。如下面这个例子所示:

01public class Example {
02 
03   public static void main(String[] args){
04     Counter counterA = new Counter();
05     Counter counterB = new Counter();
06     Thread  threadA = new CounterThread(counterA);
07     Thread  threadB = new CounterThread(counterB);
08 
09     threadA.start();
10     threadB.start();
11   }
12 }

注意这两个线程,threadA和threadB,不再引用同一个counter实例。CounterA和counterB的add方法同步在他们所属的对象上。调用counterA的add方法将不会阻塞调用counterB的add方法。

延伸阅读:对象级别锁 vs 类级别锁 – Java

https://www.cnblogs.com/keeplearnning/p/7003992.html

  1. 在Java中为了保证没有两个线程可以同时执行一个同步的方法,这就需要相同的锁。
  2. 同步关键字只能用于方法,代码块和final字段。这些方法或块既可以是静态的也可以是非静态的。
  3. 当有一个线程进入Java同步方法或块的时候获取锁,离开Java同步方法或块的时候释放锁。线程完成同步方法,或由于任何错误或异常,锁都会被释放。
  4. Java同步关键词是可重入的,这意味着如果一个Java同步方法调用另一个需要相同的锁的同步方法,当前线程持有锁,能直接进入,不需要有获取锁。
  5. 如果使用Java同步块的对象为空Java同步将抛出NullPointerException。例如,在上面的代码示例,如果锁被初始化为null,同步(锁)将抛出NullPointerException。
  6. Java同步会消耗您的应用程序性能。所以绝对必要时才使用同步。另外,考虑使用同步代码块来同步代码中的关键部分。
  7. 静态同步和非静态同步方法可能同时或并发地运行,因为它们锁定在不同的对象上。
  8. 根据Java语言规范不能使用同步关键词在构造函数上,这是违法的,会导致编译错误。
  9. 不要同步非final字段。因为非final字段的引用可能随时改变,不同的线程可能在不同的对象上同步,即根本不同步。最好是使用字符串类,它已经是不可变的,并且声明为最终的。

打赏

如果觉得我的文章对你有帮助,有钱就捧个钱场,没钱就捧个人场,欢迎点赞或转发 ,并请注明原出处,谢谢....



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值