三道Java新手入门面试题攻略
一、什么是锁?
锁是一种同步机制,用于控制多个线程对共享资源的访问。当多个线程试图访问同一共享资源时,可能会导致数据不一致或者其他问题,而锁就可以保证同一时刻只有一个线程访问该共享资源,避免多线程并发访问发生问题。
Java提供了两种锁机制:synchronized关键字和Lock接口。
synchronized关键字
- 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);
}
}
- 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技术站