47. 在 java 程序中怎么保证多线程的运行安全?
使用安全类(concurrent)下面的类。使用自动锁 synchronized。使用手动锁 Lock。保证一个或者多个操作在CPU执行的过程中不被中断。保证一个线程对共享变量的修改,另外一个线程能够立刻看到。保证程序执行的顺序按照代码的先后顺序执行。
synchronized示例:
public static void main(String[] args) { MyThread01 thread = new MyThread01(); Thread t1 = new Thread(thread); Thread t2 = new Thread(thread); Thread t3 = new Thread(thread); Thread t4 = new Thread(thread); Thread t5 = new Thread(thread); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } } class MyThread01 extends Thread { @Override public synchronized void run() { System.out.println(Thread.currentThread().getName()); }
Lock示例:
class MyTest { private Lock lock = new ReentrantLock(); public void testMethod() { lock.lock(); System.out.println(Thread.currentThread().getName()); lock.unlock(); } } class MyThread02 extends Thread { private MyTest test; public MyThread02(MyTest test) { this.test = test; } @Override public void run() { test.testMethod(); } } public class Test38 { public static void main(String[] args) { MyTest test = new MyTest(); MyThread02 t1 = new MyThread02(test); MyThread02 t2 = new MyThread02(test); MyThread02 t3 = new MyThread02(test); MyThread02 t4 = new MyThread02(test); MyThread02 t5 = new MyThread02(test); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } }
48. 多线程锁的升级原理是什么?
锁的级别从低到高:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。
无锁:在不锁定资源的情况下,所有线程都可以访问和修改相同的资源,但只有一个线程可以成功修改资源。 其他修改资源失败的线程将再次尝试,直到成功修改资源。
偏向锁:对象的代码一直由同一个线程执行,不存在多个线程竞争,线程在后续执行中自动获取锁,减少了获取锁带来的性能开销。
轻量级锁 :轻量级锁是指当锁为偏向锁时,被第二个线程访问,此时偏向锁会升级为轻量级锁,第二个线程会尝试通过旋转获取锁,线程不会阻塞,从而提高性能。
重量级锁:当一个线程获取锁时,所有等待获取该锁的其他线程都将被阻塞。
49. 什么是死锁?
死锁是由两个或多个进程在执行过程中争夺资源或相互通信造成的阻塞,这样它们就无法在没有外部力量的情况下继续执行。 此时,系统处于死锁状态或系统产生了死锁,这些总是相互等待的进程称为死锁进程。
示例:
public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { public void run() { Test39.test1(); } }); Thread t2 = new Thread(new Runnable() { public void run() { Test39.test2(); } }); t1.start(); t2.start(); } public static void test1() { synchronized (String.class) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程t1尝试获取integer"); synchronized (Integer.class) { } } } public static void test2() { synchronized (Integer.class) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程t2尝试获取string"); synchronized (String.class) { } } }
50. 怎么防止死锁?
死锁的四个必要条件:①互斥条件:一个资源每次只能被一个线程使用。②请求和保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。③不可剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺。④环路等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
示例:
public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { public void run() { Test40.test1(); } }); Thread t2 = new Thread(new Runnable() { public void run() { Test40.test2(); } }); t1.start(); t2.start(); } public static void test1() { synchronized (String.class) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程t1尝试获取integer"); synchronized (Integer.class) { System.out.println("线程t1获取到了integer"); } } } public static void test2() { synchronized (String.class) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程t2尝试获取Integer"); synchronized (Integer.class) { System.out.println("线程t2获取到Integer"); } } }
51. ThreadLocal 是什么?有哪些使用场景?
①ThreadLocal是解决线程安全问题的一个好方法,通过为每个线程提供一个独立的变量副本,可以解决并发访问变量的冲突。 ②Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
示例:
public static void main(String[] args) { ThreadLocal<Integer> threadLocal = new MyThreadLocal(); for (int i = 0; i < 3; i++) { new Thread(new Runnable() { @Override public void run() { for (int j = 1; j <= 3; j++) { threadLocal.set(threadLocal.get() + 1); System.out.println("线程" + j + ":" + threadLocal.get()); } } }).start(); } } private static class MyThreadLocal extends ThreadLocal { @Override protected Integer initialValue() { return 0; } }