Java同步问题面试参考指南

同步

在多线程程序中,同步修饰符用来控制对临界区代码的访问。其中一种方式是用synchronized关键字来保证代码的线程安全性。在Java中,synchronized修饰的代码块或方法不会被多个线程并发访问。它强制要求线程在进入一个方法之前获得一个锁,在离开方法时释放该锁。它保证了在同一时刻只有一个线程能执行被其修饰的方法。

如果我们把一个方法或代码块定义为同步的,就意味着在同一个对象中,只会有一个对同步方法的调用。如果在一个线程内部调用了一个同步方法,则其他线程会一直阻塞,直到第一个线程完成方法调用。

在进入一个对象的同步方法之前,需要申请对该对象上锁,完成方法调用后释放锁供其他线程申请。同步方法遵循happens-before机制,它保证了对象状态的改变在其他线程中都是可见的。

当标记一个代码块为同步时,需要用一个对象作为参数。当一个运行线程执行到该代码块时,要等到其他运行线程退出这个对象的同步代码区。然而,一个线程可以进入另一个对象的同步代码区。但是同一个对象的非同步方法可以不用申请锁。

如果定义一个静态方法为同步,则是在类上同步,而不是在对象上同步。也即如果一个静态同步方法在执行时,整个类被锁住,对该类中的其他静态方法调用会阻塞。

1)当一个线程进入了一个实例的同步方法,则其他任何线程都不能进入该实例的任何一个同步方法。

2)当一个线程进入了一个类的静态同步方法,则其他任何线程都不能进入该类的任何一个静态同步方法。

注意:

  1. 同步的静态方法和非静态方法之间没有关系。也即静态同步方法和非静态同步方法可以同时执行,除非非静态同步方法显式在该类上同步(例如,synchronized(MyClass.class){…})
  2. 类的构造函数不能定义成同步的。

监视器或内部锁

锁限制了对某个对象状态的访问,同时保证了happens-before关系。

每个对象都有一个锁对象,一个线程在访问对象之前必须申请锁,完成以后释放锁。其他线程不能访问对象,知道获得该对象的锁。这保证了一个线程改变了对象的状态后,新的状态对其他在同一个监视器上线程可见。

当线程释放锁时,会将cache中的内容更新到主内存,这也就使得该对象的状态变化对其他线程是可见的——这就是happens-before关系。

synchronized和Volatile,包括Thread.start()和Thread.join()方法,都能保证happens-before关系。

同步语句和同步方法获取的锁相同,某个线程可以请求同一个锁多次。

一个线程获得了对象锁后,不会影响其他线程访问对象的字段或调用对象的非同步方法。

同步语句首先尝试获取对象的锁,获取成功后立即开始执行同步代码块,执行完后释放锁。

如果方法是对象成员或对象实例,线程将锁住该实例。如果方法是静态的,线程锁住的是该类对应的Class对象。同步方法用SYNCHRONIZED标记,该标记被方法调用指令识别。

原子变量

来看语句 int c++,它包含多个操作,e.g. 从内存读取c的值,将c的值加1,然后写回内存。这个操作对单线程来说是正确的,但是在多线程环境却可能出错。它存在竞态条件,在多线程环境中可能多个线程同时读取c的值

原子访问保证所有操作作为一个整体一次完成。一个原子操作要么完全执行要么完全不执行。

以下这些操作能认为是原子操作:

  1. 对引用类型和大部分基本数据类型(long和double类型除外)的读和写操作。
  2. 声明为volatile类型变量的读和写操作(包括long和double变量)。

Java并发包java.util.concurrent.atomic定 义了对单个变量进行原子操作的类。所有类都有get和set方法,就像对volatile变量的读写一样。这就意味着,一个写操作happens- before其他任何对该变量的读操作。原子方法compareAndSet同样有这些特性,就像对整型变量做原子的算术运算一样。

在Java 5.0的并发包中,定义了支持原子操作的类。Java虚拟机编译这些类时利用硬件提供的CAS(Compare and set)来实现。

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
  • AtomicReference

volatile变量

volatile只能用来修饰变量。用volatile修饰的变量可能被异步地修改,所以编译器会对它们特殊处理。

volatile修饰符保证读取某个字段的任何线程都能看到该变量最近被写入的值。

使用volatile修饰的变量降低了内存一致性的风险,因为任何对volatile变量的写操作都能被其他线程可见。另外,当一个线程访问volatile变量时,不止能看到对该变量最近的修改,还能修改该变量的代码所带来的其他影响。

在多线程环境中,对象在不同线程中都保存有副本。但是volatile变量却没有,它们在堆中只有一个实例。这样对volatile变量的修改就能立即对其他线程可见。另外,本地线程缓存没有完成后刷新的工作。

volatile能够保证可见性,但是也带来了竞态条件。它不会锁定等待完成某个操作。例如:

1 volatile int i=0;

两 个线程同时执行 i +=5 时,会得到5-10之间的某个值(译者注:原文为i +=5 invoking by two simultaneously thread give result 5 or 10 but it guarantee to see immediate changes 感觉有问题)

使用场景:用一个volatile布尔变量作为一个线程终止的标志。

静态和volatile变量之间的差别

声明一个静态变量,意味着该类的多个实例将共享该变量,静态变量与类关联而不是与对象关联。线程可能会有静态变量的本地缓存值。

当两个线程同时更新静态(非volatile)变量的值时,可能有一个线程的缓存中是一个过期的值。虽然多线程能够访问的是同一个静态变量,每个线程还是可能会保存自己的缓存副本。

一个volatile变量则在内存中只保留一个副本,该副本在多个线程中共享。

volatile变量和同步之间的差别

在线程内存和主内存之间,volatile只是同步了一个变量的值,synchronized则同步了(synchronized块中)所有变量的值,并且会锁住和释放一个监视器。所以,synchronized比volatile会有更多的开销。

volatile变量不允许有一个本地副本与主内存中的值不同。一个声明为volatile的变量必须保证所有线程中的副本同步,不管哪个线程修改了变量的值,另外其他线程都能立即看到该值。

锁对象

锁对象的作用像synchronized代码使用的隐式锁一样。像隐式锁一样,同时只能有一个线程持有锁。锁还支持wait/notify机制,通过他们之间的condition对象。

锁对象相对于隐式锁最大的优点是,他们能从尝试获得锁的状态返回。如果锁当前不可用或者在一个超时时间之前,tryLock()方法能够返回。在获得锁之前,如果其他线程发送了一个中断,lockInterruptibly()方法能返回。

Java内存回收

在 Java中,创建的对象存放在堆中。Java堆被称为内存回收堆。内存收集不能强制执行。当内存收集器运行时,它释放掉那些不可达对象占用的内存。垃圾收集线程作为一个优先级较低的守护线程运行。你能通过System.gc()提示虚拟机进行垃圾回收,但是不能强迫其执行。

如何写一个死锁程序

在多线程环境中,死锁意味着两个或多个线程一直阻塞,等待其他线程释放锁。下面是死锁的一个示例:

public class DeadlockSample {
private final Object obj1 = new Object();
private final Object obj2 = new Object();

public static void main(String[] args) {
  DeadlockSample test = new DeadlockSample();
  test.testDeadlock();
}

private void testDeadlock() {
  Thread t1 = new Thread(new Runnable() {
  public void run() {
    calLock12();
  }
});
Thread t2 = new Thread(new Runnable() {
  public void run() {
    calLock21();
  }
});
t1.start();
t2.start();

}
private void calLock12() {
synchronized (obj1) {
sleep();
synchronized (obj2) {
sleep();
}
}
}
private void calLock21() {
synchronized (obj2) {
sleep();
synchronized (obj1) {
sleep();
}
}
}
private void sleep() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Java中的引用类型

java.lang.ref包能用来声明软引用(soft reference),弱引用(weak reference)和虚引用(phantom reference)。

    • 垃圾收集器不会回收强引用。
    • 在内存不足时才会回收软引用,所以用它实现缓存可以避免内存不足。
    • 垃圾收集器将会在下一次垃圾收集时回收弱引用。弱引用能被用来实现特殊的map。java.util.WeakHashMap中的key就是弱引用。
    • 虚引用会被立即回收。能被用来跟踪对象被垃圾回收的活动。
      --转至《码农网》
时间: 2024-10-13 15:36:04

Java同步问题面试参考指南的相关文章

Java面试参考指南(二)

访问修饰符 对于基本的OOPS(面向对象)概念,请看Java面试参考指南的第一部分.访问修饰符规定了一个类如何访问另一个类及它的成员(包括方法和变量). Java中有下列访问修饰符: private:私有变量和方法(非外部类)只能被声明它们的类的实例所使用. default:类中的数据.方法和它本身能够被声明为默认default.类中所有default成员都可以被本包中的其它类所访问. protected:相比default有更高的访问权限.只有成员变量和方法能够被声明为protected.父类

Java面试参考指南——同步

在多线程程序中,同步修饰符用来控制对临界区代码的访问.其中一种方式是用synchronized关键字来保证代码的线程安全性.在Java中,synchronized修饰的代码块或方法不会被多个线程并发访问.它强制要求线程在进入一个方法之前获得一个锁,在离开方法时释放该锁.它保证了在同一时刻只有一个线程能执行被其修饰的方法. 如果我们把一个方法或代码块定义为同步的,就意味着在同一个对象中,只会有一个对同步方法的调用.如果在一个线程内部调用了一个同步方法,则其他线程会一直阻塞,直到第一个线程完成方法调

Java 面试参考指南 — 同步

同步 在多线程程序中,同步修饰符用来控制对临界区代码的访问.其中一种方式是用synchronized关键字来保证代码的线程安全性.在Java中,synchronized修饰的代码块或方法不会被多个线程并发访问.它强制要求线程在进入一个方法之前获得一个锁,在离开方法时释放该锁.它保证了在同一时刻只有一个线程能执行被其修饰的方法. 如果我们把一个方法或代码块定义为同步的,就意味着在同一个对象中,只会有一个对同步方法的调用.如果在一个线程内部调用了一个同步方法,则其他线程会一直阻塞,直到第一个线程完成

Java面试参考指南(一)

Java是一种基于面向对象概念的编程语言,使用高度抽象化来解决现实世界的问题.    面向对象的方法将现实世界中的对象进行概念化,以便于在应用之间进行重用.例如:椅子.风扇.狗和电脑等. Java里的类(Class)是一个蓝图.模板,或者称之为原型,它定义了同一类事物的相同属性和行为.实例(Instance)是某个类的一个具 体实现,同一个类所有的实例拥有相同的属性.举例来说,你可以定义一个类叫做“房子(House)”,这个类拥有一个属性叫做“房间数 (number of room)”,这样你就

死磕 java同步系列之AQS终篇(面试)

问题 (1)AQS的定位? (2)AQS的重要组成部分? (3)AQS运用的设计模式? (4)AQS的总体流程? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为Java中几乎所有的锁和同步器提供一个基础框架. 在之前的章节中,我们一起学习了ReentrantLock.ReentrantReadWriteLock.Semaphore.CountDownLatch的源码,今天我们一起来对AQS做个总结. 状态变量state AQS中定义了一个状态变量state

Java集合相关面试问题和答案

Java集合相关面试问题和答案 面试试题 1.Java集合框架是什么?说出一些集合框架的优点? 每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector.Stack.HashTable和Array.随着集合的广泛使用,Java1.2提出了囊括所有集合接口.实现和算法的集合框架.在保证线程安全的情况下使用泛型和并发集合类,Java已经经历了很久.它还包括在Java并发包中,阻塞接口以及它们的实现.集合框架的部分优点如下: (1)使用核心集合类降低开发成本,而非实现我们自己的集合类.

Java研发岗位面试归类B(附答案)

本文承接上文:Java研发岗位面试归类A(附答案): http://www.cnblogs.com/wp5719/p/5870243.html 答案自己网上找的,如有纰漏或错误,烦请指教. 七.数据库MySql 1.MySql的存储引擎的不同. MyISAM:拥有较高的插入,查询速度,但不支持事务. InnoDB:5.5版本后Mysql的默认数据库,事务型数据库的首选引擎,支持ACID事务,支持行级锁定. BDB:源自Berkeley DB,事务型数据库的另一种选择,支持COMMIT和ROLLB

Nmap参考指南中文版

Nmap参考指南中文版 来源: http://www.nmap.com.cn/doc/manual.shtm 译注 该Nmap参考指南中文版由Fei Yang <[email protected]>和Lei Li<[email protected]> 从英文版本翻译而来. 我们希望这将使全世界使用中文的人们更了解Nmap,但我们不能保证该译本和官方的 英文版本一样完整,也不能保证同步更新. 它可以在Creative Commons Attribution License下被修改并重

《Java程序员面试笔试宝典》终于在万众期待中出版啦~

<Java程序员面试笔试宝典>终于在万众期待中出版啦~它是知名畅销书<程序员面试笔试宝典>的姊妹篇,而定价只要48元哦,恰逢求职季节,希望本书的出版能够让更多的求职者能够走进理想的企业 在这里,众多知名企业面试官将为你撕开神秘的求职面纱: 在这里,各种求职达人将现身说法为你揭开求职谜团: 在这里,各种类型的企业招聘细节都会被展露无疑: 在这里,我们将为你抽丝剥茧,还原IT行业的真相: 在这里,我们将为你指点迷津,告诉你职场上的风风雨雨: 在这里,我们将为你点石成金,成为那盏指引你前