三道java新手入门面试题,通往自由的道路–锁+Volatile

三道Java新手入门面试题攻略

一、什么是锁?

锁是一种同步机制,用于控制多个线程对共享资源的访问。当多个线程试图访问同一共享资源时,可能会导致数据不一致或者其他问题,而锁就可以保证同一时刻只有一个线程访问该共享资源,避免多线程并发访问发生问题。

Java提供了两种锁机制:synchronized关键字和Lock接口。

synchronized关键字

  1. synchronized方法

如果一个方法被synchronized关键字修饰,那么该方法称为同步方法。同一个对象的不同线程在调用该方法时,只能有一条线程能执行,其他线程需要等待该线程执行完毕后再进行调用。

示例代码:

public class SyncDemo implements Runnable{
    private int count = 0;

    public synchronized void increase() {
        count++;
    }

    public void run() {
        for (int i = 0;i < 10000;i++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SyncDemo syncDemo = new SyncDemo();
        Thread thread1 = new Thread(syncDemo);
        Thread thread2 = new Thread(syncDemo);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("Count: " + syncDemo.count);
    }
}
  1. synchronized块

除了同步方法外,synchronized关键字还可以放在代码块中,利用锁的机制保证多个线程对于共享资源的互斥访问。

示例代码:

public class SyncDemo implements Runnable{
    private static int count = 0;
    private Object lock = new Object();

    public void increase() {
        synchronized (lock) {
            count++;
        }
    }

    public void run() {
        for (int i = 0;i < 10000;i++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SyncDemo syncDemo = new SyncDemo();
        Thread thread1 = new Thread(syncDemo);
        Thread thread2 = new Thread(syncDemo);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("Count: " + count);
    }
}

Lock接口

Lock接口提供了比synchronized更细粒度的锁机制,它允许多个线程同时访问共享资源,但同一时刻只能有一个线程修改资源。它还提供了比synchronized更灵活的同步方式,例如可以选择公平锁或非公平锁,甚至是可以在条件变量上等待或唤醒线程。

ReentrantLock是Lock接口的一个具体实现,使用方式与synchronized类似。

示例代码:

public class LockDemo implements Runnable{
    private static int count = 0;
    private Lock lock = new ReentrantLock();

    public void increase() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public void run() {
        for (int i = 0;i < 10000;i++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        LockDemo lockDemo = new LockDemo();
        Thread thread1 = new Thread(lockDemo);
        Thread thread2 = new Thread(lockDemo);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("Count: " + count);
    }
}

二、什么是Volatile?

Volatile是Java中的另一种同步机制,它保证对该变量的读写操作都是原子性的,即不允许出现多线程并发访问时的数据不一致问题。和synchronized关键字不同的是,Volatile是采用的可见性的思想,即对变量的更改会马上被其他线程所感知。

示例代码:

public class VolatileDemo implements Runnable {
    private volatile boolean flag = true;

    public void stop() {
        flag = false;
    }

    public void run() {
        while (flag) {
            System.out.println(Thread.currentThread().getName() + " is running...");
        }
        System.out.println(Thread.currentThread().getName() + " is stopped.");
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileDemo demo = new VolatileDemo();
        Thread thread = new Thread(demo);
        thread.start();
        Thread.sleep(1000);
        demo.stop();
    }
}

三、锁和Volatile的区别

锁和Volatile都可以用来保证线程安全,但是它们的实现机制不同:

  • 锁是采用了互斥访问的方式限制对共享资源的访问,而Volatile是通过保证可见性来避免数据不一致的问题。
  • 锁可以确保资源的唯一控制权,但是需要等待获取锁的线程释放锁之后其他线程才能继续执行,因此通常会对系统性能和响应时间产生影响;而Volatile则不存在这个问题,对系统性能的开销很小。
  • 锁适用于一些复杂的互斥访问场景,例如多线程交互时,为了避免死锁或者其他等待导致的问题;而Volatile适用于对性能要求更高的单个变量访问场景,例如flag变量。

示例代码:

共享变量a,要求a的值在所有线程之间同步,运用synchronzed和Volatile的线程池示例如下:

public class SyncAndVolatileDemo {

    private int a = 0;

    public synchronized void syncIncrease() {
        a++;
    }

    private volatile int b = 0;

    public void volatileIncrease() {
        b++;
    }

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

        SyncAndVolatileDemo demo = new SyncAndVolatileDemo();

        // 线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // 同步方法访问示例
        executorService.submit(() -> {
            for (int i = 0; i < 10000; i++) {
                demo.syncIncrease();
            }
        });

        // Volatile变量访问示例
        executorService.submit(() -> {
            for (int i = 0; i < 10000; i++) {
                demo.volatileIncrease();
            }
        });

        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.MINUTES);

        System.out.println("a's value: " + demo.a);
        System.out.println("b's value: " + demo.b);
    }
} 

在同步访问a和Volatile访问b的线程池任务中,经过多次测试可以看出,同步访问的a最后的结果正常,而Volatile访问的b有时会发生丢失/重复值的情况。这表明了锁机制比Volatile更加可靠,在多线程环境中可以更好地保证数据的完整性和一致性。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:三道java新手入门面试题,通往自由的道路–锁+Volatile - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • MyBatis入门实例教程之创建一个简单的程序

    首先我们需要明确一下MyBatis的基础知识。MyBatis是一个持久层框架,可以与关系型数据库进行交互。在使用MyBatis时,我们需要进行以下三步操作: 配置数据源:需要在MyBatis的配置文件中配置数据库的连接信息。 编写Mapper文件:Mapper文件是MyBatis的核心,用于描述SQL语句以及与Java对象之间的映射关系。 执行SQL语句:通…

    Java 2023年5月20日
    00
  • SpringSecurity OAuth2单点登录和登出的实现

    一、前言 本文主要介绍如何使用Spring Security OAuth2实现单点登录和登出的功能,同时提供两个完整的示例,让读者更加容易的理解和实践。 二、单点登录和登出的实现 2.1 单点登录 在Spring Security OAuth2中实现单点登录的功能需要涉及到以下几个组件: OAuth2认证服务器:负责认证用户并颁发令牌 OAuth2客户端:使…

    Java 2023年5月20日
    00
  • Struts2数据输入验证教程详解

    Struts2数据输入验证教程详解 什么是数据输入验证 在应用程序中,数据输入验证是一项重要的任务。它可以确保应用程序接受有效的、可靠的和正确的数据。数据输入验证通常采用静态和动态验证技术,在前台和后台进行验证。 静态验证技术用于验证数据格式是否为正确的格式,比如数字、日期等。而动态验证技术则用于验证数据是否是符合业务规则和逻辑的,例如输入的身份证号码必须符…

    Java 2023年5月20日
    00
  • 几种常用DB驱动和DB连接串小结

    关于“几种常用DB驱动和DB连接串小结”的攻略,以下是详细的介绍和示例说明。 1. 常见的DB驱动 在Java中常用的DB驱动主要有以下几种: 1.1 MySQL驱动 MySQL驱动目前最常用的是Connector/J,它是MySQL官方提供的Java驱动程序。可以从MySQL官网下载到最新的MySQL驱动。 1.2 Oracle驱动 Oracle官方提供的…

    Java 2023年6月16日
    00
  • 基于Socket类以及ServerSocket类的实例讲解

    前言 Socket和ServerSocket是Java网络编程中最基础的两个类,它们被广泛应用于开发客户端和服务端之间的网络通信。在这个攻略中,我们将详细讲解Socket和ServerSocket类的基础知识,包括它们的概念、使用方法和示例应用。 Socket类 概念 Socket类是Java中的一个基础类,用于客户端和服务端之间的网络通信。客户端Socke…

    Java 2023年6月15日
    00
  • jsp页面中两种方法显示当前时间的简单实例

    首先,我们需要从标准的HTML页面入手,将其中的固定时间改为当前时间。做法是通过在HTML页面中添加Javascript脚本来实现。 我们可以在页面上添加一个ID标识,用于在JavaScript中找到该元素,使用JavaScript中的 Date 对象,生成当前时间,并将该时间设置为HTML页面上的元素文本。 以下是一条示例代码: <p id=&quo…

    Java 2023年5月20日
    00
  • springboot手写一个自己的starter源码

    下面是详细讲解“springboot手写一个自己的starter源码”的完整攻略。 什么是Spring Boot Starter? Spring Boot Starter是Spring Boot常用的组件。它为应用程序引入必要的依赖项,基本上是一种分散的,可重用的依赖项配置。 例如,如果要使用Spring Boot编写Web应用程序,您需要在项目中添加spr…

    Java 2023年5月31日
    00
  • 使用JPA传递参数的方法

    使用JPA传递参数的方法有多种,可以通过注解、命名参数以及查询参数的方式来实现。下面我将详细讲解这三种方式。 1. 使用注解传递参数 使用注解传递参数的方式需要在SQL语句中使用占位符,同时在代码中使用@Param注解来将参数与占位符对应起来。 例如,我们需要查询某个用户的信息,并且需要使用到用户的id和姓名两个参数。SQL语句可以这样写: SELECT *…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部