多线程中隐藏的面试题

在找工作中面试大家都经历过了吧--反正我是没经历过

面试大家都熟悉吧,每个人想找到工作,在现在找工作肯定需要面试,然而在面试中,往往面试官会问你一些问题来看你是不是他所需要的人

你想用你所学的Java找到一份自己理想的工作,而在Java中多线程是很重要的,所有我们来谈谈在多线程中隐藏的面试题吧

一、Java多线程实的几种实现方式,什么是线程安全?

Java多线程实现方式主要有四种:

1.继承Thread类,启动线程的唯一方法就是通过Thread类的start()方法,实例后调用start()方法启动

2.实现Runnable接口,重写run()方法

3.实现Callable接口

4.通过Futrue Task包装器

线程安全就是线程访问时采用了加锁机制,当一个线程访问该类的某个数据是,进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可以使用,不会出现数据不一致或者数据污染。

二、synchronized的特性

2.1 原子性

所谓原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。但是像i++i+=1等操作字符就不是原子性的,它们是分成读取、计算、赋值几步操作,原值在这些步骤还没完成时就可能已经被赋值了,那么最后赋值写入的数据就是脏数据,无法保证原子性。

被synchronized修饰的类或对象的所有操作都是原子的,因为在执行操作之前必须先获得类或对象的锁,直到执行完才能释放,这中间的过程无法被中断(除了已经废弃的stop()方法),即保证了原子性。

注意!面试时经常会问比较synchronized和volatile,它们俩特性上最大的区别就在于原子性,volatile不具备原子性。

2.2 可见性

可见性是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的。

synchronized和volatile都具有可见性,其中synchronized对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。

而volatile的实现类似,被volatile修饰的变量,每当值需要修改时都会立即更新主存,主存是共享的,所有线程可见,所以确保了其他线程读取到的变量永远是最新值,保证可见性。

2.3 有序性

有序性值程序执行的顺序按照代码先后执行。

synchronizedvolatile都具有有序性,Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。

2.4 可重入性

synchronizedReentrantLock都是可重入锁。当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。

synchronized的用法

synchronized可以修饰静态方法、成员函数,同时还可以直接定义代码块,但是归根结底它上锁的资源只有两类:一个是对象,一个是

在Java的开发过程中,往往都会熟悉几种设计模式,在设计模式中单例设计模式是我们第一个接触的设计模式,也是常用的一种设计模式。单例设计模式分为饿汉式和懒汉式两种,在这两种中懒汉式中存在着线程不安全问题。

三、懒汉式线程不安全,改为线程安全

      让线程变安全,有几种方式。在这几种方式中,同步代码块和同步方法是我们最常用的解决线程安全的方式

      在这里我先给大家复习一下单例设计模式中的懒汉式--害怕你们不知道懒汉式是什么东东

      书写懒汉式的步骤

      ①私有化类的构造器

      ②声明当前类对象,没有初始化

      ③此对象也必须声明为是static

      ④提供公共的静态方法,返回当前类的对象

class Singleton{
    //①私有化类的构造器
    private Singleton{

    }
    //②内部提供当前类的实例
    //③此实例必须静态化
    private static Singleton single;
    //④提供公共的静态方法,返回当前类的对象
    public static Singleton getSingleton(){
       if(single == null){
        single = new Singleton();
        }
        return single;
    }
}

    我们复习了单例设计模式中的懒汉式,而上面这个懒汉式是线程不安全的,下面我们用同步代码块的方式将懒汉式改为线程安全的

     想必都知道单例设计模式中的懒汉式的流程了吧,这我就偷个懒

class Singleton{
    //①私有化类的构造器
    private Singleton{

    }
    //②内部提供当前类的实例
    //③此实例必须静态化
    private static Singleton single = null;
    //④提供公共的静态方法,返回当前类的对象
    public static Singleton getSingleton(){
        synchronized(Singleton.class){
       if(single == null){
        single = new Singleton();
        }
        return single;
       }
    }
}

   然而这种方式虽然虽然能将懒汉式变为线程安全的,但是效率太慢,凡事都是讲究效率的。会这种方式就足够了!

在Java的多线程中,同步监视器都听说过吧,同步监视器与同步代码块是密不可分的,同步监视器俗称为锁。而在解决线程安全的时候,锁也可以用来解决线程的安全问题。

四、syschronized 与Lock的异同?

       相同点:二者都可以解决线程安全问题

       不同点:synchronized机制在执行完相应的同步代码以后,自动释放同步监视器

                     Lock需要手动启动同步(lock()),同时结束同步也需要手动的实现(unlock())

      synchronized与 Lock的对比

      1.Lock是显示锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放。

      2.Lock只有代码块锁,synchronized有代码块锁和方法锁

      3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并具有更好的扩展性(提供更多的子类)

在jdk 5.0中Lock是新增的解决线程安全的一种方式,我们以后在开发中往往会经常的使用它,虽然现在大多数公司在开发中所用的jdk是1.8,但是随着时代的进步,我们在开发中解决问题的方法也有很多种选择。在Java多线程有许多的方法,其中sleep()和wait()方法是经常使用的方法。当他们的成功被调用,线程都会出现阻塞的状态。

五、sleep()和wait()的异同

       相同点:一旦执行方法,都可以使得当前线程进入阻塞状态

       不同点: 

                   1)两个方法声明的位置不同:Thread类中声明sleep(),Object中声明wait()

                   2)调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块中调用

                   3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放同步监视器,wait()会释放同步监视器。

这种场景,酒枯认为会出现在多线程中的线程通信中,我们想要线程排队慢慢来的时候,我们就可以使用者两种方法,并使用notify()来唤醒线程。当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。Java对于多线程的安全问题提供了专业的解决方式:同步机制同步锁机制就是是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,无法访问它,而在其被解锁之时,另一个任务就可以锁定并使用它。

六、Java是如何解决线程安全问题的,有几种方式?

1.同步代码块

          同步代码块中的锁可以自己指定,很多时候指定为this或类名.class。任何对象都可以作为同步锁,所有对象都自动含有单一的锁(监视器)。
例如使用实现Runnable接口的方式实现多线程,由于多个线程共用一个对象,此时同步锁可以是任意对象。若是使用继承Thread的方式实现多线程,由于多个线程间不是同一个对象,此时只能使用类名.class或者静态对象。总而言之必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全。

2.同步方法

  • 同步方法的锁:静态方法(类名.class)、非静态方法(this)
  • 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this)

3.Lock(锁)

从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。ReentrantLock类实现了 Lock ,它拥有与synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放。使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

七、volatile的原理,作用,能替代锁吗?

       volatile是轻量级的synchronized,它让多处理器开发中保证了共享变量“可见性”。可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。如果一个字段被声明为volatile,Java多线程内存迷行确保所有线程看到这个变量的值是一致的。具有几条特性。

    volatle 无法保证复合操作的原子性。Java 只保证了基本数据类型变量的性。赋值操作才是原子性,当然,可以通过锁、synchronized来确保原子性。其实严格的说,对任意单个volatile变的读/写具有原子性,但类似于volatile++ 这种复合操作是不具有原子性

    volatile可以保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会自己被更新到主内存中,当其他线程读取共享变量时,他会直接从主内存中读取。当然,synchronized和锁能都保证可见性。

     volatile 可以保证有序性,禁止指令重排序。

综上,volatile 可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用"内存屏障*来实现的。

在使用场景中,轻量级锁volatle是不能取代synchronized.但也可以在有限的一些情形下可以用vlaile变量代替锁。要使vlatile变量提供理想的线程安全,必须同时满足下面两个条件:
    对变量的写操作不依赖于当前值。

    该变量没有包含在具有其他变量的不变式中。

  酒枯推荐

  推荐使用浏览器:Google Chrome

  推荐使用Java环境:eclipse、IDEA

  本周推荐学习:HTML和CSS

  推荐学习视频链接: https://www.bilibili.com/video/BV1CK411G7m4?from=search&seid=9778818247834758128

  推荐使用HTML环境:VScode(Visual Studio Code)

 

猜你喜欢

转载自blog.csdn.net/weixin_52011642/article/details/110570074