多线程_题库详解

1 并行与并发的区别是什么?

并行:指两个或两个以上事件或活动在同一时刻发生。如多个任务在多个 CPU 或 CPU 的多个核上同时执行,不存在 CPU 资源的竞争、等待行为。

并行与并发的区别
并行指多个事件在同一个时刻发生;并发指在某时刻只有一个事件在发生,某个时间段内由于 CPU 交替执行,可以发生多个事件。

并行没有对 CPU 资源的抢占;并发执行的线程需要对CPU 资源进行抢占。
并行执行的线程之间不存在切换;并发操作系统会根据任务调度系统给线程分配线程的 CPU 执行时间,线程的执行会进行切换。

Java 中的多线程
通过 JDK 中的 java.lang.Thread 可以实现多线程。
Java 中多线程运行的程序可能是并发也可能是并行,取决于操作系统对线程的调度和计算机硬件资源( CPU 的个数和 CPU 的核数)。
CPU 资源比较充足时,多线程被分配到不同的 CPU 资源上,即并行;CPU 资源比较紧缺时,多线程可能被分配到同个 CPU 的某个核上去执行,即并发。
不管多线程是并行还是并发,都是为了提高程序的性能。

2 线程的run()方法和start()区别?

启动一个线程需要调用 Thread 对象的 start() 方法
调用线程的 start() 方法后,线程处于可运行状态,此时它可以由 JVM 调度并执行,这并不意味着线程就会立即运行
run() 方法是线程运行时由 JVM 回调的方法,无需手动写代码调用
直接调用线程的 run() 方法,相当于在调用线程里继续调用方法,并未启动一个新的线程

3 线程?进程?为什么要有线程?

进程:
程序执行时的一个实例
每个进程都有独立的内存地址空间
系统进行资源分配和调度的基本单位
进程里的堆,是一个进程中最大的一块内存,被进程中的所有线程共享的,进程创建时分配,主要存放 new 创建的对象实例
进程里的方法区,是用来存放进程中的代码片段的,是线程共享的
在多线程 OS 中,进程不是一个可执行的实体,即一个进程至少创建一个线程去执行代码

为什么要有线程?
每个进程都有自己的地址空间,即进程空间。一个服务器通常需要接收大量并发请求,为每一个请求都创建一个进程系统开销大、请求响应效率低,因此操作系统引进线程。

线程:
进程中的一个实体
进程的一个执行路径
CPU 调度和分派的基本单位
线程本身是不会独立存在

当前线程 CPU 时间片用完后,会让出 CPU 等下次轮到自己时候在执行,系统不会为线程分配内存,线程组之间只能共享所属进程的资源
线程只拥有在运行中必不可少的资源(如程序计数器、栈)
线程里的程序计数器就是为了记录该线程让出 CPU 时候的执行地址,待再次分配到时间片时候就可以从自己私有的计数器指定地址继续执行
每个线程有自己的栈资源,用于存储该线程的局部变量和调用栈帧,其它线程无权访问

关系:
一个程序至少一个进程,一个进程至少一个线程,进程中的多个线程是共享进程的资源
Java 中当我们启动 main 函数时候就启动了一个 JVM 的进程,而 main 函数所在线程就是这个进程中的一个线程,也叫做主线程
一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器,栈区域
如下图
在这里插入图片描述

区别:
本质:进程是操作系统资源分配的基本单位;线程是任务调度和执行的基本单位

内存分配:系统在运行的时候会为每个进程分配不同的内存空间,建立数据表来维护代码段、堆栈段和数据段;除了 CPU 外,系统不会为线程分配内存,线程所使用的资源来自其所属进程的资源

资源拥有:进程之间的资源是独立的,无法共享;同一进程的所有线程共享本进程的资源,如内存,CPU,IO 等

开销:每个进程都有独立的代码和数据空间,程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行程序计数器和栈,线程之间切换的开销小

通信:进程间 以IPC(管道,信号量,共享内存,消息队列,文件,套接字等)方式通信 ;同一个进程下,线程间可以共享全局变量、静态变量等数据进行通信,做到同步和互斥,以保证数据的一致性

调度和切换:线程上下文切换比进程上下文切换快,代价小

执行过程:每个进程都有一个程序执行的入口,顺序执行序列;线程不能够独立执行,必须依存在应用程序中,由程序的多线程控制机制控制

健壮性:每个进程之间的资源是独立的,当一个进程崩溃时,不会影响其他进程;同一进程的线程共享此线程的资源,当一个线程发生崩溃时,此进程也会发生崩溃,稳定性差,容易出现共享与资源竞争产生的各种问题,如死锁等

可维护性:线程的可维护性,代码也较难调试,bug 难排查

进程与线程的选择:
需要频繁创建销毁的优先使用线程。因为进程创建、销毁一个进程代价很大,需要不停的分配资源;线程频繁的调用只改变 CPU 的执行
线程的切换速度快,需要大量计算,切换频繁时,用线程
耗时的操作使用线程可提高应用程序的响应
线程对 CPU 的使用效率更优,多机器分布的用进程,多核分布用线程
需要跨机器移植,优先考虑用进程
需要更稳定、安全时,优先考虑用进程
需要速度时,优先考虑用线程
并行性要求很高时,优先考虑用线程

Java 编程语言中线程是通过 java.lang.Thread 类实现的。
Thread 类中包含 tid(线程id)、name(线程名称)、group(线程组)、daemon(是否守护线程)、priority(优先级) 等重要属性。

4 什么是守护线程?

Java线程分为用户线程和守护线程。
守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。

注意:
setDaemon(true) 必须在 start() 之前设置,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程,继续执行
守护线程创建的线程也是守护线程
守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题

/**
 * 测试守护线程
 * @author ConstXiong
 * @date 2019-09-03 12:15:59
 */
public class TestDaemonThread {
	public static void main(String[] args) {
		testDaemonThread();
	}
	//
	public static void testDaemonThread() {
		Thread t = new Thread(() -> {
			//创建线程,校验守护线程内创建线程是否为守护线程
			Thread t2 = new Thread(() -> {
				System.out.println("t2 : " + 
              (Thread.currentThread().isDaemon() ? "守护线程"      
              : "非守护线程"));
			});
			t2.start();
			//当所有用户线程执行完,守护线程会被直接杀掉,程序停止运  int i = 1;
			while(true) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("t : " + 
               (Thread.currentThread().isDaemon() ? "守护线       
                程" : "非守护线程") + " , 执行次数 : " + i);
				if (i++ >= 10) {
					break;
				}
			}
		});
		//setDaemon(true) 必须在 start() 之前设置,否则会抛出
        IllegalThreadStateException异常,该线程仍默认为用户线程,
        继续执行
		t.setDaemon(true);
		t.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//主线程结束
		System.out.println("主线程结束");
	}
}

执行结果
t2 : 守护线程
t : 守护线程 , 执行次数 : 1
主线程结束
t : 守护线程 , 执行次数 : 2

结论:
上述代码线程t,未打印到 t : daemon thread , time : 10,说明所有用户线程停止,进程会停掉所有守护线程,退出程序
当 t.start(); 放到 t.setDaemon(true); 之前,程序抛出IllegalThreadStateException,t 仍然是用户线程,打印如下

Exception in thread "main" t2 : 非守护线程java.lang.IllegalThreadStateException at java.lang.Thread.setDaemon(Thread.java:1359)at constxiong.concurrency.a008.TestDaemonThread.testDaemonThread(TestDaemonThread.java:39)at constxiong.concurrency.a008.TestDaemonThread.main(TestDaemonThread.java:11)
t : 非守护线程 , 执行次数 : 1
t : 非守护线程 , 执行次数 : 2
t : 非守护线程 , 执行次数 : 3
t : 非守护线程 , 执行次数 : 4
t : 非守护线程 , 执行次数 : 5
t : 非守护线程 , 执行次数 : 6
t : 非守护线程 , 执行次数 : 7
t : 非守护线程 , 执行次数 : 8
t : 非守护线程 , 执行次数 : 9
t : 非守护线程 , 执行次数 : 10

5 如何创建、启动 Java 线程?

Java 中有 4 种常见的创建线程的方式。
1) 重写 Thread 类的 run() 方法。
表现形式有两种:new Thread 对象匿名重写 run() 方法

public static void main(String[] args) {
    new Thread("t"){
        @Override
        public void run() {
            System.out.println("线程启动开始......");
        }
    }.start();
}
执行结果
thread t > 0
thread t > 1
thread t > 2         

2)继承 Thread 对象,重写 run() 方法

public class Test2 {

    public static void main(String[] args) {
        new ThreadExtend().start();
    }
}


class ThreadExtend extends Thread {
    @Override
    public void run() {
        System.out.println("线程启动开始......");
    }
}
执行结果
thread t > 0
thread t > 1
thread t > 2

3)实现 Runnable 接口,重写 run() 方法。
表现形式有两种:new Runnable 对象,匿名重写 run() 方法

public class Test3 {
    public static void main(String[] args) {
    }
    public  static void newRunable(){
        new Thread(new Runnable() {
            @Override
            public void run() {
           }
        }).start();

        new Thread(()->{
             System.out.println("线程启动开始");
        }).start();
    }
}
执行结果
thread t1 > 0
thread t2 > 0
thread t1 > 1
thread t2 > 1
thread t1 > 2
thread t2 > 2

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

public class Test4 {

    public static void main(String[] args) {
        new Thread(new RunnableExtend()).start();
    }
}

class RunnableExtend implements Runnable {
    @Override
    public void run() {

    }
}
执行结果
thread t > 0
thread t > 1
thread t > 2

5)实现 Callable 接口,使用 FutureTask 类创建线程

/**
 * @program: Test
 * @description:
 * @author: chuige
 * @create: 2021-10-09 15:23
 **/
public class Test5 {
    public static void main(String[] args) {
        FutureTask<String> ft = new FutureTask<String>(new  
        Callable<String>() {
            @Override
            public String call() throws Exception {
                return  "1213";
            }
        });
        new Thread(ft).start();
    }
}
执行结果
执行结果:ConstXiong

6)使用线程池创建、启动线程

public class Test6 {
    public static void main(String[] args) {
        ExecutorService singleService = 
        Executors.newSingleThreadExecutor();
        singleService.submit(()->{
              System.out.println("线程开始运行");
        });
        singleService.shutdown();
    }
}
执行结果
单线程线程池执行任务

6 什么是并发编程?

并发:
在程序设计的角度,希望通过某些机制让计算机可以在一个时间段内,执行多个任务。
一个或多个物理 CPU 在多个程序之间多路复用,提高对计算机资源的利用率。
任务数多余 CPU 的核数,通过操作系统的任务调度算法,实现多个任务一起执行。
有多个线程在执行,计算机只有一个 CPU,不可能真正同时运行多个线程,操作系统只能把 CPU 运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。

并发编程:
用编程语言编写让计算机可以在一个时间段内执行多个任务的程序。

在这里插入图片描述

7 为什么要用并发编程?

“摩尔定律” 失效,硬件的单元计算能力提升受限;硬件上提高了 CPU 的核数和个数。并发编程可以提升 CPU 的计算能力的利用率。
提升程序的性能,如:响应时间、吞吐量、计算机资源使用率等。
并发程序可以更好地处理复杂业务,对复杂业务进行多任务拆分,简化任务调度,同步执行任务。

8 并发编程的缺点?

Java 中的线程对应是操作系统级别的线程,线程数量控制不好,频繁的创建、销毁线程和线程间的切换,比较消耗内存和时间。
容易带来线程安全问题。如线程的可见性、有序性、原子性问题,会导致程序出现的结果与预期结果不一致。
多线程容易造成死锁、活锁、线程饥饿等问题。此类问题往往只能通过手动停止线程、甚至是进程才能解决,影响严重。
对编程人员的技术要求较高,编写出正确的并发程序并不容易。
并发程序易出问题,且难调试和排查;问题常常诡异地出现,又诡异地消失。

9 导致并发程序出问题的根本原因?

CPU、内存、IO 设备的读写速度差异巨大,表现为 CPU 的速度 > 内存的速度 > IO 设备的速度。
程序的性能瓶颈在于速度最慢的 IO 设备的读写,也就是说当涉及到 IO 设备的读写,再怎么提升 CPU 和内存的速度也是起不到提升性能的作用。

为了更好地利用 CPU 的高性能
计算机体系结构,给 CPU 增加了缓存,均衡 CPU 和内存的速度差异
操作系统,增加了进程与线程,分时复用 CPU,均衡 CPU 和 IO 设备的速度差异
编译器,增加了指令执行重排序,更好地利用缓存,提高程序的执行速度

基于以上优化,给并发编程带来了三大问题。
CPU 缓存,在多核 CPU 的情况下,带来了可见性问题
可见性:一个线程对共享变量的修改,另一个线程能够立刻看到修改后的值
看下面代码,启动两个线程,一个线程当 stop 变量为 true 时,停止循环,一个线程启动就设置 stop 变量为 true。

在这里插入图片描述

这个就是因为 CPU 缓存导致的可见性导致的问题。线程 2 设置 stop 变量为 true,线程 1 在 CPU 1上执行,读取的 CPU 1 缓存中的 stop 变量仍然为 false,线程 1 一直在循环执行。
示意如图:

在这里插入图片描述

可以通过 volatile、synchronized、Lock接口、Atomic 类型保障可见性。
操作系统对当前执行线程的切换,带来了原子性问题
原子性:一个或多个指令在 CPU 执行的过程中不被中断的特性
看下面的一段代码,线程 1 和线程 2 分别对变量 count 增加 10000,但是结果 count 的输出却不是 20000

package constxiong.concurrency.a014;
/**
 * 测试原子性问题
 * @author ConstXiong
 */public class TestAtomic {s
	//计数变量
	static volatile int count = 0;
	public static void main(String[] args) throws   
	InterruptedException {
		//线程 1 给 count 加 10000
		Thread t1 = new Thread(() -> {
			for (int j = 0; j <10000; j++) {
				count++;
			}
			System.out.println("thread t1 count 加 10000 结
			束");
		});
		
		//线程 2 给 count 加 10000
		Thread t2 = new Thread(() -> {
			for (int j = 0; j <10000; j++) {
				count++;
			}
			System.out.println("thread t2 count 加 10000 结
			束");
		});
		//启动线程 1
		t1.start();
		//启动线程 2
		t2.start();
		//等待线程 1 执行完成
		t1.join();
		//等待线程 2 执行完成
		t2.join();
		//打印 count 变量
		System.out.println(count);
	}
}

打印结果:
thread t2 count 加 10000 结束
thread t1 count 加 10000 结束
11377
这个就是因为线程切换导致的原子性问题。
Java 代码中 的 count++ ,至少需要三条 CPU 指令:
指令 1:把变量 count 从内存加载到 CPU 的寄存器
指令 2:在寄存器中执行 count + 1 操作
指令 3:+1 后的结果写入 CPU 缓存或内存
即使是单核的 CPU,当线程 1 执行到指令 1 时发生线程切换,线程 2 从内存中读取 count 变量,此时线程 1 和线程 2 中的 count 变量值是相等,都执行完指令 2 和指令 3,写入的 count 的值是相同的。从结果上看,两个线程都进行了 count++,但是 count 的值只增加了 1。
指令执行与线程切换

在这里插入图片描述

编译器指令重排优化,带来了有序性问题
有序性:程序按照代码执行的先后顺序
看下面这段代码,复现指令重排带来的有序性问题。

package constxiong.concurrency.a014;
import java.util.HashMap;import java.util.HashSet;import java.util.Map;import java.util.Set;
/**
 * 测试有序性问题
 * @author ConstXiong
 */
 public class TestOrderliness {
	
	static int x;//静态变量 x
	static int y;//静态变量 y
	
	public static void main(String[] args) throws 
	InterruptedException {
		    Set<String> valueSet = new HashSet<String>();//
		    记录出现的结果的情况
		Map<String, Integer> valueMap = new HashMap<String, 
		Integer>();//存储结果的键值对
		//循环 1000 万次,记录可能出现的 v1 和 v2 的情况
		for (int i = 0; i <10000000; i++) {
			//给 x y 赋值为 0
			x = 0; 
			y = 0; 
			valueMap.clear();//清除之前记录的键值对
			Thread t1 = new Thread(() -> {
				int v1 = y;//将 y 赋值给 v1 ----> Step1
				x = 1;//设置 x 为 1  ----> Step2
				valueMap.put("v1", v1);//v1 值存入 valueMap ----> Step3
			}) ;
			
			Thread t2 = new Thread(() -> {
				int v2 = x;//将 x 赋值给 v2  ----> Step4
				y = 1;//设置 y 为 1  ----> Step5
				valueMap.put("v2", v2);//v2 值存入 valueMap ----> Step6
			});
			
			//启动线程 t1 t2
			t1.start();
			t2.start();
			//等待线程 t1 t2 执行完成
			t1.join();
			t2.join();
			
			//利用 Set 记录并打印 v1 和 v2 可能出现的不同结果
			valueSet.add("(v1=" + valueMap.get("v1") + 
			",v2=" + 
            valueMap.get("v2") + ")");
			System.out.println(valueSet);
		}
	}
}

打印结果出现四种情况:
v1=0,v2=0 的执行顺序是 Step1 和 Step 4 先执行
v1=1,v2=0 的执行顺序是 Step5 先于 Step1 执行
v1=0,v2=1 的执行顺序是 Step2 先于 Step4 执行
v1=1,v2=1 出现的概率极低,就是因为 CPU 指令重排序造成的。Step2 被优化到 Step1 前,Step5 被优化到 Step4 前,至少需要成立一个。
指令重排,可能会发生在两个没有相互依赖关系之间的指令。

10 Java 多线程的运行安全?

线程的安全性问题体现在:
原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
有序性:程序执行的顺序按照代码的先后顺序执行

导致原因:
缓存导致的可见性问题
线程切换带来的原子性问题
编译优化带来的有序性问题

解决办法:
JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
synchronized、volatile、LOCK,可以解决可见性问题
Happens-Before 规则可以解决有序性问题

11 如何优雅地停止一个线程?

线程终止有两种情况:
线程的任务执行完成
线程在执行任务过程中发生异常

这两者属于线程自行终止,如何让线程 A 把线程 B 终止呢?
Java 中 Thread 类有一个 stop() 方法,可以终止线程,不过这个方法会让线程直接终止,在执行的任务立即终止,未执行的任务无法反馈,所以 stop() 方法已经不建议使用。

既然 stop() 方法如此粗暴,不建议使用,我们如何优雅地结束线程呢?
线程只有从 runnable 状态(可运行/运行状态) 才能进入terminated 状态(终止状态),如果线程处于 blocked、waiting、timed_waiting 状态(休眠状态),就需要通过 Thread 类的 interrupt() 方法,让线程从休眠状态进入 runnable 状态,从而结束线程。

当线程进入 runnable 状态之后,通过设置一个标识位,线程在合适的时机,检查该标识位,发现符合终止条件,自动退出 run () 方法,线程终止。
如我们模拟一个系统监控任务线程,代码如下

package constxiong.concurrency.a007;
/**
 * 模拟系统监控
 * @author ConstXiong
 */
public class TestSystemMonitor {
	public static void main(String[] args) {
		testSystemMonitor();//测试系统监控器
	}
	/**
	 * 测试系统监控器
	 */
	public static void testSystemMonitor() {
		SystemMonitor sm = new SystemMonitor();
		sm.start();
		try {
			//运行 10 秒后停止监控
			Thread.sleep(10 * 1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("监控任务启动 10 秒后,停止...");
		sm.stop();
	}
}

/**
 * 系统监控器
 * @author ConstXiong
 */
class SystemMonitor {
	private Thread t;
	/**
	 * 启动一个线程监控系统
	 */
	void start() {
		t = new Thread(() -> {
			while (!Thread.currentThread().isInterrupted()) 
			{//判断当前线程是否被打断
				System.out.println("正在监控系统...");
				try {
					Thread.sleep(3 * 1000L);//执行 3 秒
					System.out.println("任务执行 3 秒");
					System.out.println("监控的系统正常!");
				} catch (InterruptedException e) {
					System.out.println("任务执行被中断...");
				}
			}
		});
		t.start();
	}
	void stop() {
		t.interrupt();
	}
}
执行结果 
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
监控任务启动 10 秒后,停止...
任务执行被中断...
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...

从代码和执行结果我们可以看出,系统监控器 start() 方法会创建一个线程执行监控系统的任务,每个任务查询系统情况需要 3 秒钟,在监控 10 秒钟后,主线程向监控器发出停止指令。
但是结果却不是我们期待的,10 秒后并没有终止了监控器,任务还在执行。

原因在于,t.interrupt() 方法让处在休眠状态的语句 Thread.sleep(3 * 1000L); 抛出异常,同时被捕获,此时 JVM 的异常处理会清除线程的中断状态,导致任务一直在执行。

处理办法是,在捕获异常后,继续重新设置中断状态,代码如下

package constxiong.concurrency.a007;
/**
 * 模拟系统监控
 * @author ConstXiong
 */public class TestSystemMonitor {
	
	public static void main(String[] args) {
		testSystemMonitor();//测试系统监控器
	}
	
	/**
	 * 测试系统监控器
	 */
	public static void testSystemMonitor() {
		SystemMonitor sm = new SystemMonitor();
		sm.start();
		try {
			//运行 10 秒后停止监控
			Thread.sleep(10 * 1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("监控任务启动 10 秒后,停止...");
		sm.stop();
	}
}
/**
 * 系统监控器
 * @author ConstXiong
 */
class SystemMonitor {
	
	private Thread t;
	/**
	 * 启动一个线程监控系统
	 */
	void start() {
		t = new Thread(() -> {
			while (!Thread.currentThread().isInterrupted())  
			{//判断当前线程是否被打断
				System.out.println("正在监控系统...");
				try {
					Thread.sleep(3 * 1000L);//执行 3 秒
					System.out.println("任务执行 3 秒");
					System.out.println("监控的系统正常!");
				} catch (InterruptedException e) {
					System.out.println("任务执行被中断...");
					Thread.currentThread().interrupt();//重新
					设置线程为中断状态
				}
			}
		});
		t.start();
	}
	void stop() {
		t.interrupt();
	}
}

执行结果如预期
正在监控系统…
任务执行 3 秒
监控的系统正常!
正在监控系统…
任务执行 3 秒
监控的系统正常!
正在监控系统…
任务执行 3 秒
监控的系统正常!
正在监控系统…
监控任务启动 10 秒后,停止…
任务执行被中断…

到这里还没有结束,我们用 Thread.sleep(3 * 1000L);
去模拟任务的执行,在实际情况中,一般是调用其他服务的代码,如果出现其他异常情况没有成功设置线程的中断状态,线程将一直执行下去,显然风险很高。所以,需要用一个线程终止的标识来代替 Thread.currentThread().isInterrupted()。
修改代码如下

package constxiong.concurrency.a007;
/**
 * 模拟系统监控
 * @author ConstXiong
 */public class TestSystemMonitor {
	
	public static void main(String[] args) {
		testSystemMonitor();//测试系统监控器
	}
	/**
	 * 测试系统监控器
	 */
	public static void testSystemMonitor() {
		SystemMonitor sm = new SystemMonitor();
		sm.start();
		try {
			//运行 10 秒后停止监控
			Thread.sleep(10 * 1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("监控任务启动 10 秒后,停止...");
		sm.stop();
	}
	
}
/**
 * 系统监控器
 * @author ConstXiong
 */
class SystemMonitor {
	
	private Thread t;
	
	private volatile boolean stop = false;
	
	/**
	 * 启动一个线程监控系统
	 */
	void start() {
		t = new Thread(() -> {
			while (!stop) {//判断当前线程是否被打断
				System.out.println("正在监控系统...");
				try {
					Thread.sleep(3 * 1000L);//执行 3 秒
					System.out.println("任务执行 3 秒");
					System.out.println("监控的系统正常!");
				} catch (InterruptedException e) {
					System.out.println("任务执行被中断...");
					Thread.currentThread().interrupt();//重新
					设置线程为中断状态
				}
			}
		});
		t.start();
	}

	void stop() {
		stop = true;
		t.interrupt();
	}
}
执行结果
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
任务执行 3 秒
监控的系统正常!
正在监控系统...
监控任务启动 10 秒后,停止...
任务执行被中断...
到这里基本算是优雅地让线程终止了。

12 synchronized关键字的作用?

Java 中关键字 synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
测试代码:

/**
 * @program: Test
 * @description:
 * @author: chuige
 * @create: 2021-10-10 08:11
 **/
public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        new Thread(()->{
            System.out.println("线程一开始");
            test.synMethod();
        }).start();

        new Thread(()->{
            System.out.println("线程二开始");
            test.synOtherMethod();
        }).start();
    }

    public  void syn(){
        synchronized (this){
            System.out.println("锁定this方法");
            try {
                Thread.sleep(20000);
            }
            catch (Exception e){
            };
        }
    }

    public synchronized void synMethod(){
        System.out.println("锁定方法");
        try {
            Thread.sleep(20000);
        }
        catch (Exception e){
        };
    }

    public  synchronized void synOtherMethod(){
        System.out.println("未锁定其他方法");
    }

    public static synchronized void synStaticMethod(){
        System.out.println("锁定静态方法");
        try {
            Thread.sleep(20000);
        }
        catch (Exception e){
        };
    }

    public static synchronized void testStaticOther(){
        System.out.println("锁定其他静态方法");
    }
}

通过JDK 反汇编指令 javap -c -v TestSynchronized
在这里插入图片描述
在这里插入图片描述

13 volatile关键字的作用是什么?

Java 中 volatile 关键字是一个类型修饰符。JDK 1.5 之后,对其语义进行了增强。
保证了不同线程对共享变量进行操作时的可见性,即一个线程修改了共享变量的值,共享变量修改后的值对其他线程立即可见
通过禁止编译器、CPU 指令重排序和部分 happens-before 规则,解决有序性问题

volatile 可见性的实现
在生成汇编代码指令时会在 volatile 修饰的共享变量进行写操作的时候会多出 Lock 前缀的指令
Lock 前缀的指令会引起 CPU 缓存写回内存
一个 CPU 的缓存回写到内存会导致其他 CPU 缓存了该内存地址的数据无效
volatile 变量通过缓存一致性协议保证每个线程获得最新值
缓存一致性协议保证每个 CPU 通过嗅探在总线上传播的数据来检查自己缓存的值是不是修改
当 CPU 发现自己缓存行对应的内存地址被修改,会将当前 CPU 的缓存行设置成无效状态,重新从内存中把数据读到 CPU 缓存

看一下我们之前的一个可见性问题的测试例子

public class VolatileTest extends Thread {

    private static boolean stop = false;

    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            System.out.println("线程一正在执行");
            while (!stop) {
                System.out.println("线程一执行");
            }
        }).start();

        Thread.sleep(10);

        new Thread(() -> {
            System.out.println("线程二正在执行");
            stop = true;
        }).start();
    }
}

程序会一直循环运行下去
这个就是因为 CPU 缓存导致的可见性导致的问题。
线程 2 设置 stop 变量为 true,线程 1 在 CPU 1上执行,读取的 CPU 1 缓存中的 stop 变量仍然为 false,线程 1 一直在循环执行。
示意如图:
在这里插入图片描述
给 stop 变量加上 valatile 关键字修饰就可以解决这个问题。

14 Java中的锁是什么?

在并发编程中,经常会遇到多个线程访问同一个共享变量,当同时对共享变量进行读写操作时,就会产生数据不一致的情况。
为了解决这个问题
JDK 1.5 之前,使用 synchronized 关键字,拿到 Java 对象的锁,保护锁定的代码块。JVM 保证同一时刻只有一个线程可以拿到这个 Java 对象的锁,执行对应的代码块。
JDK 1.5 开始,引入了并发工具包 java.util.concurrent.locks.Lock,让锁的功能更加丰富。

常见的锁
synchronized 关键字锁定代码库
可重入锁 java.util.concurrent.lock.ReentrantLock
可重复读写锁 java.util.concurrent.lock.ReentrantReadWriteLock

Java 中不同维度的锁分类
可重入锁
指在同一个线程在外层方法获取锁的时候,进入内层方法会自动获取锁。JDK 中基本都是可重入锁,避免死锁的发生。上面提到的常见的锁都是可重入锁。

公平锁 / 非公平锁
公平锁,指多个线程按照申请锁的顺序来获取锁。如 java.util.concurrent.lock.ReentrantLock.FairSync
非公平锁,指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程先获得锁。如 synchronized、java.util.concurrent.lock.ReentrantLock.NonfairSync

独享锁 / 共享锁
独享锁,指锁一次只能被一个线程所持有。synchronized、java.util.concurrent.locks.ReentrantLock 都是独享锁
共享锁,指锁可被多个线程所持有。ReadWriteLock 返回的 ReadLock 就是共享锁

悲观锁 / 乐观锁
悲观锁,一律会对代码块进行加锁,如 synchronized、java.util.concurrent.locks.ReentrantLock
乐观锁,默认不会进行并发修改,通常采用 CAS 算法不断尝试更新
悲观锁适合写操作较多的场景,乐观锁适合读操作较多的场景

粗粒度锁 / 细粒度锁
粗粒度锁,就是把执行的代码块都锁定
细粒度锁,就是锁住尽可能小的代码块,java.util.concurrent.ConcurrentHashMap 中的分段锁就是一种细粒度锁
粗粒度锁和细粒度锁是相对的,没有什么标准

偏向锁 / 轻量级锁 / 重量级锁
JDK 1.5 之后新增锁的升级机制,提升性能。
通过 synchronized 加锁后,一段同步代码一直被同一个线程所访问,那么该线程获取的就是偏向锁
偏向锁被一个其他线程访问时,Java 对象的偏向锁就会升级为轻量级锁
再有其他线程会以自旋的形式尝试获取锁,不会阻塞,自旋一定次数仍然未获取到锁,就会膨胀为重量级锁

自旋锁
自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环占有、浪费 CPU 资源

15 锁如何使用?有什么注意事项?

Java 中常见的锁有 synchronized
可重入锁 java.util.concurrent.lock.ReentrantLock
可重复读写锁 java.util.concurrent.lock.ReentrantReadWriteLock

synchronized 有 3种用法
修饰普通方法,执行方法代码,需要获取对象本身 this 的锁

package constxiong.concurrency.a18;
import java.util.ArrayList;
import java.util.List;
/**
 * 测试 synchronized 普通方法
 * @author ConstXiong
 * @date 2019-09-19 10:49:46
 */
public class TestSynchronizedNormalMethod {
     private int count = 0;
	//	private void add1000() {
	private synchronized void add1000() { 
   //使用 synchronized 修饰 add100 方法,即可获得正确的值 30000
		for (int i = 0; i <1000; i++) {
			count++;
		}
	}
	//启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性 加1000
	private void test() throws InterruptedException {
		List<Thread> threads = new ArrayList<Thread>(10);
		for (int i = 0; i <30; i++) {
			Thread t =  new Thread(() -> {
				add1000();
			});
			t.start();
			threads.add(t);
		}
		
		//等待所有线程执行完毕
		for (Thread t : threads) {
			t.join();
		}
		//打印 count 的值
		System.out.println(count);
	}
	
	public static void main(String[] args) throws 
	InterruptedException {
		//创建 TestSynchronizedNormalMethod 对象,调用 test 方法
		new TestSynchronizedNormalMethod().test();
	}
}

修饰静态方法,执行方法代码,需要获取 class 对象的锁

package constxiong.concurrency.a18;
import java.util.ArrayList;
import java.util.List;
/**
 * 测试 synchronized 静态方法
 * @author ConstXiong
 * @date 2019-09-19 10:49:46
 */public class TestSynchronizedStaticMethod {

	private static int count = 0;
	
    private static void add1000() {
    //	private synchronized static void  add1000() {
   //使用 synchronized 修饰 add100 方法,即可获得正确的值 30000
		for (int i = 0; i <1000; i++) {
			count++;
		}
	}
	
	public static void main(String[] args) throws 
	InterruptedException {
		//启动 30 个线程,每个线程 对 TestSynchronized 对象的 count   
		  属性加 1000
		List<Thread> threads = new ArrayList<Thread>(10);
		for (int i = 0; i <30; i++) {
			Thread t =  new Thread(() -> {
				add1000();
			});
			t.start();
			threads.add(t);
		}
		//等待所有线程执行完毕
		for (Thread t : threads) {
			t.join();
		}
		//打印 count 的值
		System.out.println(count);
	}
}
锁定 Java 对象,修饰代码块,显示指定需要获取的 Java 对象锁	
//启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性加 1000
```java
package constxiong.concurrency.a18;
import java.util.ArrayList;import java.util.List;
/**
 * 测试 synchronized 代码块
 * @author ConstXiong
 * @date 2019-09-19 10:49:46
 */
public class TestSynchronizedCodeBlock {
	private int count = 0;
	//锁定的对象
	private final Object obj = new Object();
	private void add1000() {
		//执行下面的加 1000 的操作,都需要获取 obj 这个对象的锁
		synchronized (obj) {
			for (int i = 0; i <1000; i++) {
				count++;
			}
		}
	}
	
	//启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性1000
	private void test() throws InterruptedException {
		List<Thread> threads = new ArrayList<Thread>(10);
		for (int i = 0; i <30; i++) {
			Thread t =  new Thread(() -> {
				add1000();
			});
			t.start();
			threads.add(t);
		}
		//等待所有线程执行完毕
		for (Thread t : threads) {
			t.join();
		}
		//打印 count 的值
		System.out.println(count);
	}
	
	public static void main(String[] args) throws 
	InterruptedException {
		//创建 TestSynchronizedNormalMethod 对象,调用 test 方法
		new TestSynchronizedCodeBlock().test();
	}
}

可重入锁

java.util.concurrent.lock.ReentrantLock 的使用示例
package constxiong.concurrency.a18;
import java.util.ArrayList;import java.util.List;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;
/**
 * 测试 ReentrantLock 
 * @author ConstXiong
 * @date 2019-09-19 11:26:50
 */
 public class TestReentrantLock {
    private int count = 0;
	private final Lock lock = new ReentrantLock();
	private void add1000() {
		lock.lock();
		try {
			for (int i = 0; i <1000; i++) {
				count++;
			}
		} finally {
			lock.unlock();
		}
	}
	
//启动 30 个线程,每个线程 对 TestSynchronized 对象的 count 属性加 
  1000
private void test() throws InterruptedException {
		List<Thread> threads = new ArrayList<Thread>(10);
		for (int i = 0; i <30; i++) {
			Thread t =  new Thread(() -> {
				add1000();
			});
			t.start();
			threads.add(t);
		}
		//等待所有线程执行完毕
		for (Thread t : threads) {
			t.join();
		}
		//打印 count 的值
		System.out.println(count);
	}
	
public static void main(String[] args) throws 
	InterruptedException {
		//创建 TestReentrantLock 对象,调用 test 方法
		new TestReentrantLock().test();
	}
}

可重复读写锁

java.util.concurrent.lock.ReentrantReadWriteLock 的使用示例
package constxiong.concurrency.a18;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
 /**
 * 测试可重入读写锁 ReentrantReadWriteLock
 * @author ConstXiong
 * @date 2019-09-19 11:36:19
 */public class TestReentrantReadWriteLock {
	
	//存储 key value 的 map
	private Map<String, Object> map = new HashMap<String, 
	Object>();
	
	//读写锁
	private final ReadWriteLock lock = new 
	ReentrantReadWriteLock();
	
	/**
	 * 根据 key 获取 value
	 * @param key
	 */
	public Object get(String key) {
		Object value = null;
		lock.readLock().lock();
		try {
			Thread.sleep(50L);
			value = map.get(key);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.readLock().unlock();
		}
		return value; 
	}
	
	/**
	 * 设置key-value
	 * @param key
	 */
	public void set(String key, Object value) {
		lock.writeLock().lock();
		try {
			Thread.sleep(50L);
			map.put(key, value);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.writeLock().unlock();
		}
	}
 
	//测试5个线程读数据,5个线程写数据
	public static void main(String[] args) {
		//创建测试可重入读写锁 TestReentrantReadWriteLock 对象
		TestReentrantReadWriteLock test = new 
		TestReentrantReadWriteLock();
		
		String key = "lock";//存入 map 中的 key
		Random r = new Random();//生成随机数作为 value
		
		for (int i = 0; i <5; i++) {
			//5 个线程读 map 中 key 的 value
			new Thread(() -> {
				for (int j = 0; j <10; j++) {   
			        System.out.println(
			        Thread.currentThread().getName()"sread 
			        value=" + test.get(key));
				}
			}).start();
			
			//5 个线程写 map 中 key 的 value
			new Thread(() -> {
				for (int j = 0; j <10; j++) {
					int value = r.nextInt(1000);
					test.set(key, value);
		            System.out.println(
		            Thread.currentThread().getName() 
                    + "write value=" + value);
				}
			}).start();
		}
	}	
}

锁的使用注意事项
synchronized 修饰代码块时,最好不要锁定基本类型的包装类,如 jvm 会缓存 -128 ~ 127 Integer 对象,每次向如下方式定义 Integer 对象,会获得同一个 Integer,如果不同地方锁定,可能会导致诡异的性能问题或者死锁
Integer i = 100;
synchronized 修饰代码块时,要线程互斥地执行代码块,需要确保锁定的是同一个对象,这点往往在实际编程中会被忽视
synchronized 不支持尝试获取锁、锁超时和公平锁
ReentrantLock 一定要记得在 finally{} 语句块中调用 unlock() 方法释放锁,不然可能导致死锁
ReentrantLock 在并发量很高的情况,由于自旋很消耗 CPU 资源
ReentrantReadWriteLock 适合对共享资源写操作很少,读操作频繁的场景;可以从写锁降级到读锁(防止数据被更改),无法从读锁升级到写锁

16 可重入锁与不可重入锁区别与性能差异?

可重入锁
指在同一个线程在外层方法获取锁的时候,进入内层方法会自动获取锁。
为了避免死锁的发生,JDK 中基本都是可重入锁。
下面我们来测试一下
synchronized 和java.util.concurrent.lock.ReentrantLock 锁的可重入性
测试 synchronized 加锁 可重入性

package constxiong.concurrency.a019;
/**
 * 测试 synchronized 加锁 可重入性
 * @author ConstXiong
 * @date 2019-09-20 15:55:27
 */public class TestSynchronizedReentrant {
	
	public static void main(String[] args) {
		new Thread(new SynchronizedReentrant()).start();
	}
	
}

class SynchronizedReentrant implements Runnable {

	private final Object obj = new Object();
	
	/**
	 * 方法1,调用方法2
	 */
	public void method1() {
		synchronized (obj) {
			 
		System.out.println(Thread.currentThread().getName() + " method1()");
		method2();
	 }
}
	
/**
 * 方法2,打印前获取 obj 锁
 * 如果同一线程,锁不可重入的话,method2 需要等待 method1 释放 
 * obj 锁
 */
public void method2() {
	 synchronized (obj) {
	     System.out.println(Thread.currentThread().getName() 
	       + " method2()");
	  }
}

@Override
public void run() {
	//线程启动 执行方法1
	method1();
  }
}

打印结果:
Thread-0 method1()
Thread-0 method2()

测试 ReentrantLock 的可重入性

package constxiong.concurrency.a019;
import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;
/**
 * 测试 ReentrantLock 的可重入性
 * @author ConstXiong
 * @date 2019-09-20 16:24:52
 */public class TestLockReentrant {

	public static void main(String[] args) {
		new Thread(new LockReentrant()).start();
	}
}

class LockReentrant implements Runnable {

	private final Lock lock = new ReentrantLock();
	/**
	 * 方法1,调用方法2
	 */
	public void method1() {
		lock.lock();
		try {
           System.out.println(Thread.currentThread().getName() 
           + " method1()");
			  method2();
		} finally {
			lock.unlock();
		}
	}
	
    /**
	 * 方法2,打印前获取 obj 锁
	 * 如果同一线程,锁不可重入的话,method2 需要等待 method1 释放 
	 * obj 锁
	 */
	public void method2() {
		lock.lock();
		try {
	       System.out.println(Thread.currentThread().getName() + "method2()");
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void run() {
		//线程启动 执行方法1
		method1();
	}
}

打印结果:
Thread-0 method1()
Thread-0 method2()

测试不可重入锁
我在 JDK 中没找到可重入锁,所以考虑自己实现一下。两种方式:通过 synchronized wait notify 实现;通过 CAS + 自旋方式实现
synchronized wait notify 方式实现
package constxiong.concurrency.a019;

/**
 * 不可重入锁,通过 synchronized wait notify 实现
 * @author ConstXiong
 * @date 2019-09-20 16:53:34
 */
 public class NonReentrantLockByWait {

	//是否被锁
	private volatile boolean locked = false;
	
	//加锁
	public synchronized void lock() {
		//当某个线程获取锁成功,其他线程进入等待状态
		while (locked) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//加锁成功,locked 设置为 true
		locked = true;
	}
	
	//释放锁
	public synchronized void unlock() {
		locked = false;
		notify();
	}
}

通过 CAS + 自旋 方式实现

package constxiong.concurrency.a019;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 不可重入锁,通过 CAS + 自旋 实现
 * @author ConstXiong
 * @date 2019-09-20 16:53:34
 */public class NonReentrantLockByCAS {
	
private AtomicReference<Thread> lockedThread = new AtomicReference<Thread>();

	public void lock() {
		Thread t = Thread.currentThread();
		//当 lockedThread 持有引用变量为 null 时,设置 lockedThread 持有引  用为 当前线程变量
		while (!lockedThread.compareAndSet(null, t)) {
			//自旋,空循环,等到锁被释放
		}
	}
	
	public void unlock() {
		//如果是本线程锁定的,可以成功释放锁
		lockedThread.compareAndSet(Thread.currentThread(), 
		null);
	}
}
 
测试类`

```java
package constxiong.concurrency.a019;
/**
 * 测试不可重入锁
 * @author ConstXiong
 * @date 2019-09-20 18:08:55
 */
public class TestLockNonReentrant{
	public static void main(String[] args) {
		new Thread(new LockNonReentrant()).start();
	}
}


class LockNonReentrant implements Runnable {
//	private final NonReentrantLockByWait lock = new 
    NonReentrantLockByWait();
private final NonReentrantLockByCAS lock = new NonReentrantLockByCAS();
	
	/**
	 * 方法1,调用方法2
	 */
	public void method1() {
		lock.lock();
		try {
		   System.out.println(Thread.currentThread().getName() 
		   + " 
           method1()");
			method2();
		} finally {
			lock.unlock();
		}
	}
	
	/**
	 * 方法2,打印前获取 obj 锁
	 * 如果同一线程,锁不可重入的话,method2 需要等待 method1 释放 
	 * obj 锁
	 */
	public void method2() {
		lock.lock();
		try {
		  System.out.println(Thread.currentThread().getName() 
		  +  " 
           method2()");
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void run() {
		//线程启动 执行方法1
		method1();
	}
}

测试结果,都是在 method1,调用 method2 的时候,导致了死锁,线程一直等待或者自旋下去。

17 浅析CompareAndSet(CAS)

CAS:Compare and Swap,比较并交换。
java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
在AtomicInteger.java里,CAS是这么被实际应用的,就拿里头的自增函数来说:

/**
* Atomically increments by one the current value.
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
    int current = get();
    int next = current + 1;
    if (compareAndSet(current, next))
        return next;
    }
}
```java
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect,   
    update);
}

从注释来看,就是在native函数里,判断当前对象和期望的expect(也就是前面的current)是否一致,如果一致则返回true,且还做了一步自增操作;
从this和valueOffset来看,this是具体的内存地址,而valueOffset是变量的在该对象里的内存地址,这是为了后续汇编代码的操作,所以这里的this结合valueOffset则就是AtomicInteger的实际值,而current是线程修改时用到的初值,如果实际值和初值一致,则说明当前并没有人修改AtomicInteger,故当前修改有效,那么就可以继续赋值为update。

private static final long valueOffset;
static {
    try {
         valueOffset = unsafe.objectFieldOffset
              (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
 }

如图的value就是当前AtomicInteger的值。
总的可以理解为如下操作:

if (this == expect) {
     this = update
     return true;
} else {
     return false;
}

在进一步的底层,使用的汇编语言上的锁 LOCK_IF_MP(mp),这里就不再深入说明了。

18 Java中的锁之间的区别是什么?

synchronized 和 java.util.concurrent.lock.Lock 之间的区别
实现层面不一样。synchronized 是 Java 关键字,JVM层面 实现加锁和释放锁;Lock 是一个接口,在代码层面实现加锁和释放锁
是否自动释放锁。synchronized 在线程代码执行完或出现异常时自动释放锁;Lock 不会自动释放锁,需要在 finally {} 代码块显式地中释放锁
是否一直等待。synchronized 会导致线程拿不到锁一直等待;Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
获取锁成功是否可知。synchronized 无法得知是否获取锁成功;Lock 可以通过 tryLock 获得加锁是否成功
功能复杂性。synchronized 加锁可重入、不可判断、非公平;Lock 可重入、可判断、可公平和不公平、细分读写锁提高效率

java.util.concurrent.lock.Lock 与java.util.concurrent.lock.ReadWriteLock 之间的区别
ReadWriteLock 定义了获取读锁和写锁的接口,读锁之间不互斥,非常适合读多、写少的场景

适用场景
JDK 1.6 开始,对 synchronized 方式枷锁进行了优化,加入了偏向锁、轻量级锁和锁升级机制,性能得到了很大的提升。性能与 ReentrantLock 差不多
读多写少的情况下,考虑使用 ReadWriteLock

synchronized、ReentrantLock、ReentrantReadWriteLock 启动 990 个线程读共享变量,10 个线程写共享变量

package constxiong.concurrency.a020;
import java.util.ArrayList;import java.util.List;import java.util.UUID;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantLock;import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * JDK 1.8 中锁性能的测试
 * @author ConstXiong
 */
public class TestLockPerformance {
	
	public static Object obj = new Object();//用于 synchronized 
	 获取锁
	public static Lock lock = new ReentrantLock();//可重入锁
	public static ReadWriteLock readWriteLock = new 
	ReentrantReadWriteLock();//读写锁
	public static final int READ = 0;
	public static final int WRITE = 1;
	
	// uuid,一个随机字符串
	public static String uuid = UUID.randomUUID().toString();

	public static void main(String[] args) throws 
	InterruptedException {
	    //		testSynchronized(1000);
		testReentrantLock(1000);
		//		testReadWriteLock(1000);
	}
	
    public static void testSynchronized(int threadNum) throws 
    InterruptedException {
		long t1 = System.currentTimeMillis();
		List<Thread> tList = new ArrayList<Thread>();
		//启动 threadNum - 向上取整 (0.01 * threadNum) 个线程读 
		uuid, 向上   取整 (0.01 * threadNum) 个线程写 uuid
		for (int i = 0; i <threadNum; i++) {
			Thread t;
			if (i % 100 == 0) {
				t = new Thread(new WorkerSynchronized(WRITE));
			} else {
				t = new Thread(new WorkerSynchronized(READ));
			}
			t.start();//启动线程
			tList.add(t);
		}
		
		for (Thread t : tList) {
			t.join();
		}
		
		long t2 = System.currentTimeMillis();
		System.out.println("testSynchronized 耗时:" + (t2 - '        
           t1));
	}
	
public static void testReentrantLock(int threadNum) throws InterruptedException {
		long t1 = System.currentTimeMillis();
		List<Thread> tList = new ArrayList<Thread>();
		//启动 threadNum - 向上取整 (0.01 * threadNum) 个线程读 
		uuid, 向上取整 (0.01 * threadNum) 个线程写 uuid
		for (int i = 0; i <threadNum; i++) {
			Thread t;
			if (i % 100 == 0) {
				t = new Thread(new 
				WorkerReentrantLock(WRITE));
			} else {
				t = new Thread(new WorkerReentrantLock(READ));
			}
			t.start();//启动线程
			tList.add(t);
		}
		
		for (Thread t : tList) {
			t.join();
		}
		
		long t2 = System.currentTimeMillis();
		System.out.println("testReentrantLock 耗时:" + (t2 - 
		t1));
	}
	
public static void testReadWriteLock(int threadNUm) throws 
InterruptedException {
		long t1 = System.currentTimeMillis();
		List<Thread> tList = new ArrayList<Thread>();
		//启动 threadNum - 向上取整 (0.01 * threadNum) 个线程读 
		uuid, 向上取整 (0.01 * threadNum) 个线程写 uuid
		for (int i = 0; i <threadNUm; i++) {
			Thread t;
			if (i % 100 == 0) {
				t = new Thread(new 
				WorkerReadWriteLock(WRITE));
			} else {
				t = new Thread(new WorkerReadWriteLock(READ));
			}
			t.start();//启动线程
			tList.add(t);
		}
		
		for (Thread t : tList) {
			t.join();
		}
		
		long t2 = System.currentTimeMillis();
		System.out.println("testReadWriteLock 耗时:" + (t2 - 
		t1));
	}
	
}
//工作线程,使用 synchronized 关键字加锁
class WorkerSynchronized implements Runnable {
	//0-read;1-write
	private int type;
	
	WorkerSynchronized(int type) {
		this.type = type;
	}
	
	//加锁读 TestLockPerformance.uuid 变量,并打印
	private void read() {
		synchronized (TestLockPerformance.obj) {
			//休眠 20 毫秒,模拟任务执行耗时
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		   System.out.println(Thread.currentThread().getName() 
		   + " read uuid = " +  TestLockPerformance.uuid);
		}
	}
	
	//加锁写 TestLockPerformance.uuid 变量,并打印
	private void write() {
		synchronized (TestLockPerformance.obj) {
			//休眠 20 毫秒,模拟任务执行耗时
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			TestLockPerformance.uuid = 
			UUID.randomUUID().toString();
			
		    System.out.println(
		    Thread.currentThread().getName() + 
					" write uuid = " +  
					TestLockPerformance.uuid);
		}
	}
	
	@Override
	public void run() {
		//type = 0,线程读 TestLockPerformance.uuid 变量
		if (type == 0) {
			read();
		//type = 1,线程生成 uuid,写入 TestLockPerformance.uuid 
		   变量
		} else {
			write();
		}
	}
}
//工作线程,使用 ReentrantLock 加锁
class WorkerReentrantLock implements Runnable {
	//0-read;1-write
	private int type;
	
	WorkerReentrantLock(int type) {
		this.type = type;
	}
	
	//加锁读 TestLockPerformance.uuid 变量,并打印
	private void read() {
		TestLockPerformance.lock.lock();
		try {
			//休眠 20 毫秒,模拟任务执行耗时
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(
			Thread.currentThread().getName() + 
					" read uuid = " +  
					TestLockPerformance.uuid);
		} finally {
			TestLockPerformance.lock.unlock();
		}
		
	}
	
	//加锁写 TestLockPerformance.uuid 变量,并打印
	private void write() {
		TestLockPerformance.lock.lock();
		try {
			//休眠 20 毫秒,模拟任务执行耗时
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			TestLockPerformance.uuid = 
			UUID.randomUUID().toString();
			
		    System.out.println(
		    Thread.currentThread().getName() + 
					" write uuid = " +  
					TestLockPerformance.uuid);
		} finally {
			TestLockPerformance.lock.unlock();
		}
	}
	
	@Override
	public void run() {
		//type = 0,线程读 TestLockPerformance.uuid 变量
		if (type == 0) {
			read();
		//type = 1,线程生成 uuid,写入 TestLockPerformance.uuid 
		变量
		} else {
			write();
		}
	}
}

//工作线程,使用 ReentrantReadWriteLock 关键字加锁
class WorkerReadWriteLock implements Runnable {
	//0-read;1-write
	private int type;
	
	WorkerReadWriteLock(int type) {
		this.type = type;
	}
	
	//加锁读 TestLockPerformance.uuid 变量,并打印
	private void read() {
		TestLockPerformance.readWriteLock.readLock().lock();
		try {
			//休眠 20 毫秒,模拟任务执行耗时
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(
			Thread.currentThread().getName() + 
					" read uuid = " +  
					TestLockPerformance.uuid);
		} finally {
			TestLockPerformance.
			readWriteLock.readLock().unlock();
		}
	}
	
	//加锁写 TestLockPerformance.uuid 变量,并打印
	private void write() {
		TestLockPerformance.readWriteLock.writeLock().lock();
		try {
			//休眠 20 毫秒,模拟任务执行耗时
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			TestLockPerformance.uuid = 
			UUID.randomUUID().toString();		        
			        System.out.println(
			        Thread.currentThread().getName() + 
					" write uuid = " +  
					TestLockPerformance.uuid);
		} finally {
			
			TestLockPerformance.
			readWriteLock.writeLock().unlock();
		}
	}
	
	@Override
	public void run() {
		//type = 0,线程读 TestLockPerformance.uuid 变量
		if (type == 0) {
			read();
		//type = 1,线程生成 uuid,写入 TestLockPerformance.uuid  
		 变量
		} else {
			write();
		}
	}
}
 
调用测试方法 
testSynchronized(1000);
耗时
Thread-0 write uuid = b7fb63d7-79cc-4cc0-84ed-5a9cd4de6824
Thread-252 read uuid = b7fb63d7-79cc-4cc0-84ed-5a9cd4de6824
Thread-251 read uuid = b7fb63d7-79cc-4cc0-84ed-5a9cd4de6824
.
.
.
Thread-255 read uuid = d666bfe6-dc71-4df2-882a-d530a59d7e92
Thread-254 read uuid = d666bfe6-dc71-4df2-882a-d530a59d7e92
Thread-253 read uuid = d666bfe6-dc71-4df2-882a-d530a59d7e92
testSynchronized 耗时:22991
 
调用测试方法 
testReentrantLock(1000);
耗时
Thread-0 write uuid = 4352eb13-d284-47ec-8caa-fc81d91d08e1
Thread-1 read uuid = 4352eb13-d284-47ec-8caa-fc81d91d08e1
Thread-485 read uuid = 4352eb13-d284-47ec-8caa-fc81d91d08e1
.
.
.
Thread-997 read uuid = 9d7f0a78-5eb7-4506-9e98-e8e9a7a717a5
Thread-998 read uuid = 9d7f0a78-5eb7-4506-9e98-e8e9a7a717a5
Thread-999 read uuid = 9d7f0a78-5eb7-4506-9e98-e8e9a7a717a5
testReentrantLock 耗时:22935
 
调用测试方法 
testReadWriteLock(1000);
耗时
Thread-0 write uuid = 81c13f80-fb19-4b27-9d21-2e99f8c8acbd
Thread-277 read uuid = 81c13f80-fb19-4b27-9d21-2e99f8c8acbd
Thread-278 read uuid = 81c13f80-fb19-4b27-9d21-2e99f8c8acbd
.
.
.
Thread-975 read uuid = 35be0359-1973-4a4f-85b7-918053d841f7
Thread-971 read uuid = 35be0359-1973-4a4f-85b7-918053d841f7
Thread-964 read uuid = 35be0359-1973-4a4f-85b7-918053d841f7
testReadWriteLock 耗时:543

通过耗时测试可以看出,使用 synchronized 和 ReentrantLock 耗时相近;但是由于 990 个线程读,10 个线程写,使用 ReentrantReadWriteLock 耗时 543 毫秒。

19 synchronized锁的升级?

锁的级别从低到高:
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

锁分级别原因:
没有优化以前,synchronized 是重量级锁(悲观锁),使用 wait 和 notify、notifyAll 来切换线程状态非常消耗系统资源;线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能。所以 JVM 对 synchronized 关键字进行了优化,把锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。
锁升级的目的是为了减低锁带来的性能消耗,在 Java 6 之后优化 synchronized 为此方式。

无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。

偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带来的性能开销。偏向锁,指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。

轻量级锁:轻量级锁是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁,线程 B 会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。
当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。

重量级锁:指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

锁状态对比:
在这里插入图片描述
synchronized 锁升级的过程:
在锁对象的对象头里面有一个 threadid 字段,未访问时 threadid 为空
第一次访问 jvm 让其持有偏向锁,并将 threadid 设置为其线程 id
再次访问时会先判断 threadid 是否与其线程 id 一致。如果一致则可以直接使用此对象;如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁
执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁

20 什么是死锁?

线程死锁是指由于两个或者多个线程互相持有所需要的资源,导致这些线程一直处于等待其他线程释放资源的状态,无法继续执行,如果线程都不主动释放所占有的资源,将产生死锁。
当线程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

产生原因:
持有系统不可剥夺资源,去竞争其他已被占用的系统不可剥夺资源,形成程序僵死的竞争关系。
持有资源的锁,去竞争锁已被占用的其他资源,形成程序僵死的争关系。
信号量使用不当。

如线程A占有资源 1 的锁,去竞争资源 2 的锁;线程 B 占有资源 2 的锁,去竞争资源1的锁。
代码表现如下

package constxiong.concurrency.a022;
/**
 * 测试死锁
 * @author ConstXiong
 * @date 2019-09-23 19:28:23
 */public class TestDeadLock {
	 
	final static Object o1 = new Object();
	final static Object o2 = new Object();
	
	public static void main(String[] args) {
		//先持有 o1 的锁,再去获取 o2 的锁
		Thread t1 = new Thread() {
			@Override
			public void run() {
				synchronized (o1) {
					System.out.println("线程:" + 
                    Thread.currentThread().
                    getName() + " 获取到 o1 对象的锁");
					try {
						System.out.println("休眠1秒");
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println("线程:" + 
                   Thread.currentThread().
                   getName() + " 去获取 o2 对象的锁");
					synchronized (o2) {
						System.out.println("线程:" + 
						Thread.currentThread().getName() + " 成
						功获取 o2 对象的锁");
					}
				}
			}
			
		};
		
		//先持有 o2 的锁,再去获取 o1 的锁
		Thread t2 = new Thread() {
			
			@Override
			public void run() {
				synchronized (o2) {
					System.out.println("线程:" + 
                    Thread.currentThread().getName() + " 获取到 
                    o2 对象的锁");
					try {
						System.out.println("休眠1秒");
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println("线程:" + 
                    Thread.currentThread().
                    getName() + " 去获取 o1 对象的锁");
					synchronized (o1) {
					System.out.println("线程:" + 
                    Thread.currentThread().getName() + " 成功
                      获取 o1 对象的锁");
					}
				}
			}
			
		};
		
		
		t1.start();
		t2.start();
	}
	
}
 
测试结果,发生死锁,打印如下
线程:Thread-0 获取到 o1 对象的锁
休眠1秒
线程:Thread-1 获取到 o2 对象的锁
休眠1秒
线程:Thread-1 去获取 o1 对象的锁
线程:Thread-0 去获取 o2 对象的锁

21 如何避免死锁?

并发程序一旦死锁,往往我们只能重启应用。解决死锁问题最好的办法就是避免死锁。
死锁发生的条件
互斥,共享资源只能被一个线程占用
占有且等待,线程 t1 已经取得共享资源 s1,尝试获取共享资源 s2 的时候,不释放共享资源 s1
不可抢占,其他线程不能强行抢占线程 t1 占有的资源 s1
循环等待,线程 t1 等待线程 t2 占有的资源,线程 t2 等待线程 t1 占有的资源

避免死锁的方法
对于以上 4 个条件,只要破坏其中一个条件,就可以避免死锁的发生。
对于第一个条件 “互斥” 是不能破坏的,因为加锁就是为了保证互斥。
其他三个条件,我们可以尝试
一次性申请所有的资源,破坏 “占有且等待” 条件
占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 “不可抢占” 条件
按序申请资源,破坏 “循环等待” 条件

编程中的最佳实践:
使用 Lock 的 tryLock(long timeout, TimeUnit unit)的方法,设置超时时间,超时可以退出防止死锁
尽量使用并发工具类代替加锁
尽量降低锁的使用粒度
尽量减少同步的代码块

使用管理类一次性申请所有的资源,破坏 “占有且等待” 条件示例
示例

package constxiong.concurrency.a023;
import java.util.HashSet;import java.util.Set;
/**
 * 测试 一次性申请所有的资源,破坏 "占有且等待" 条件示例
 * @author ConstXiong
 * @date 2019-09-24 14:04:12
 */
public class TestBreakLockAndWait {

	//单例的资源管理类
	private final static Manger manager = new Manger();
	//资源1
	private static Object res1 = new Object();
	//资源2
	private static Object res2 = new Object();
	
	public static void main(String[] args) {
		new Thread(() -> {
			boolean applySuccess = false;
			while (!applySuccess) {
				//向管理类,申请res1和res2,申请失败,重试
				applySuccess = manager.applyResources(res1, 
				res2);
				if (applySuccess) {
					try {
						System.out.println("线程:" + 
                        Thread.currentThread().
                        getName() + " 申请 res1、res2 
                        资源成功");
						synchronized (res1) {
							System.out.println("线程:" + 
                            Thread.currentThread().
                            getName() + " 获取到 res1 
                            资源的锁");
							//休眠 1秒
							try {
								Thread.sleep(1000);
							} catch (Exception e) {
								e.printStackTrace();
							}
							synchronized (res2) {
								System.out.println("线程:" +                                                                                                                                                                                                
								Thread.
								currentThread().
                                getName() +  "  到 res2 资源的
                                锁");
							}
						}
					} finally {
						manager.returnResources(res1, res2);//
						归还资源
					}
				} else {
					 System.out.println("线程:" + 
                      Thread.currentThread().
                        getName() + " 申请 res1、res2 资源失败");
					//申请失败休眠 200 毫秒后重试
					try {
						Thread.sleep(200);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
		
		new Thread(() -> {
			boolean applySuccess = false;
			while (!applySuccess) {
				//向管理类,申请res1和res2,申请失败,重试
				applySuccess = manager.applyResources(res1, 
				res2);
				if (applySuccess) {
					try {
						System.out.println("线程:" + 
                       Thread.currentThread().
                           getName() + " 申请 res1、res2 
                        资源成功");
						synchronized (res2) {
							System.out.println("线程:" + 
                            Thread.currentThread().
                            getName() + " 获取到 res1 资源的锁");
							//休眠 1秒
							try {
								Thread.sleep(1000);
							} catch (Exception e) {
								e.printStackTrace();
							}
							synchronized (res1) {
								System.out.println("线程:" + 
                                Thread.currentThread() 
                                .getName() + " 获取到 res2 资源
                                的锁");
							}
						}
					} finally {
						manager.returnResources(res1, res2);//
						归还资源
					}
				} else {
					System.out.println("线程:" + 
                       Thread.currentThread().
                       getName() + " 申请 res1、res2 资源失败");
					//申请失败休眠 200 毫秒后重试
					try {
						Thread.sleep(200);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
		
	}
	
}
/**
 * 资源申请、归还管理类
 * @author ConstXiong
 * @date 2019-09-24 14:10:57
 */
class Manger {
	
	//资源存放集合
	private Set<Object> resources = new HashSet<Object>();
	
	/**
	 * 申请资源
	 * @param res1
	 * @param res2
	 * @return
	 */
	synchronized boolean applyResources(Object res1, Object 
	res2) {
		if (resources.contains(res1) || 
		resources.contains(res1)) {
			return false;
		} else {
			resources.add(res1);
			resources.add(res2);
			return true;
		}
	}
	
	/**
	 * 归还资源
	 * @param res1
	 * @param res2
	 */
	synchronized void returnResources(Object res1, Object    
	res2) {
		resources.remove(res1);
		resources.remove(res2);
	}
	
}
 
打印结果如下,线程-1 在线程-0 释放完资源后才能成功申请 res1 和 res2 的锁
线程:Thread-0 申请 res1、res2 资源成功
线程:Thread-0 获取到 res1 资源的锁
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-0 获取到 res2 资源的锁
线程:Thread-1 申请 res1、res2 资源失败
线程:Thread-1 申请 res1、res2 资源成功
线程:Thread-1 获取到 res1 资源的锁
线程:Thread-1 获取到 res2 资源的锁
 
使用 LocktryLock() 方法,获取锁失败释放所有资源,破坏 "不可抢占" 条件示例

package constxiong.concurrency.a023;
import java.util.Random;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;
/**
 * 测试 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件
 * @author ConstXiong
 * @date 2019-09-24 14:50:51
 */
public class TestBreakLockOccupation {
	
	private static Random r = new Random(); 

	private static Lock lock1 = new ReentrantLock();
	
	private static Lock lock2 = new ReentrantLock();
	
	public static void main(String[] args) {
		new Thread(() -> {
			//标识任务是否完成
			boolean taskComplete = false;
			while (!taskComplete) {
				lock1.lock();
				System.out.println("线程:" + 
                Thread.currentThread().getName() + " 获取锁 
                lock1 成功");
				try {
					//随机休眠,帮助造成死锁环境
					try {
						Thread.sleep(r.nextInt(30));
					} catch (Exception e) {
						e.printStackTrace();
					}
					
					//线程 0 尝试获取 lock2
					if (lock2.tryLock()) {
						System.out.println("线程:" + 
                        Thread.currentThread().
                               getName() + " 获取锁 lock2 成   
                               功");
						try {
							taskComplete = true;
						} finally {
							lock2.unlock();
						}
					} else {
						System.out.println("线程:" + 
                        Thread.currentThread().getName() + " 获
                        取锁 lock2 失败");
					}
				} finally {
					lock1.unlock();
				}
				
				//随机休眠,避免出现活锁
				try {
					Thread.sleep(r.nextInt(10));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
		
		new Thread(() -> {
			//标识任务是否完成
			boolean taskComplete = false;
			while (!taskComplete) {
				lock2.lock();
				System.out.println("线程:" + 
               Thread.currentThread().
                getName() + " 获取锁 lock2 成功");
				try {
					//随机休眠,帮助造成死锁环境
					try {
						Thread.sleep(r.nextInt(30));
					} catch (Exception e) {
						e.printStackTrace();
					}
					
					//线程2 尝试获取锁 lock1
					if (lock1.tryLock()) {
						System.out.println("线程:" + 
                        Thread.currentThread().
                        getName() + " 获取锁 lock1 成功");
						try {
							taskComplete = true;
						} finally {
							lock1.unlock();
						}
					} else {
						System.out.println("线程:" + 
                        Thread.currentThread().
                       getName() + " 获取锁 lock1 失败");
					}
				} finally {
					lock2.unlock();
				}
				
				//随机休眠,避免出现活锁
				try {
					Thread.sleep(r.nextInt(10));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
	
}
 
打印结果如下
线程:Thread-0 获取锁 lock1 成功
线程:Thread-1 获取锁 lock2 成功
线程:Thread-1 获取锁 lock1 失败
线程:Thread-1 获取锁 lock2 成功
线程:Thread-0 获取锁 lock2 失败
线程:Thread-1 获取锁 lock1 成功
线程:Thread-0 获取锁 lock1 成功
线程:Thread-0 获取锁 lock2 成功
 

按照一定的顺序加锁,破坏 “循环等待” 条件示例

package constxiong.concurrency.a023;
/**
 * 测试 按序申请资源,破坏 "循环等待" 条件
 * @author ConstXiong
 * @date 2019-09-24 15:26:23
 */public class TestBreakLockCircleWait {

	private static Object res1 = new Object();
	private static Object res2 = new Object();

	public static void main(String[] args) {
		new Thread(() -> {
			Object first = res1;
			Object second = res2;
			//比较 res1 和 res2 的 hashCode,如果 res1 的 hashcode 
			  > res2,交换 first 和 second。保证 hashCode 小的对象
			    先加锁
			if (res1.hashCode() > res2.hashCode()) {
				first = res2;
				second = res1;
			}
			synchronized (first) {
				System.out.println("线程:" + 
                Thread.currentThread().
                getName() + "获取资源 " + first + " 锁成功");
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
				}
				synchronized(second) {
					System.out.println("线程:" + 
                   Thread.currentThread().
                   getName() + "获取资源 " + second + " 锁成功");
				}
			}
		}).start();
		
		new Thread(() -> {
			Object first = res1;
			Object second = res2;
			//比较 res1 和 res2 的 hashCode,如果 res1 的 hashcode 
			> res2,交换 first 和 second。保证 hashCode 小的对象先
			   加锁
			if (res1.hashCode() > res2.hashCode()) {
				first = res2;
				second = res1;
			}
			synchronized (first) {
				System.out.println("线程:" + 
                 Thread.currentThread().
                getName() + "获取资源 " + first + " 锁成功");
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
				}
				synchronized(second) {
					System.out.println("线程:" + 
					Thread.currentThread().
					 getName() + "获取资源 " + second + " 锁成
					 功");
				}
			}
		}).start();
	}
	
}
 
打印结果如下
线程:Thread-0获取资源 java.lang.Object@7447157c 锁成功
线程:Thread-0获取资源 java.lang.Object@7a80f45c 锁成功
线程:Thread-1获取资源 java.lang.Object@7447157c 锁成功
线程:Thread-1获取资源 java.lang.Object@7a80f45c 锁成功

22 什么是活锁和饥饿?

活锁
任务没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。 处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资源却又相互释放不执行。

解决活锁的一个简单办法就是在下一次尝试获取资源之前,随机休眠一小段时间。
看一下,我们之前的一个例子,如果最后不进行随机休眠,就会产生活锁,现象就是很长一段时间,两个线程都在不断尝试获取和释放锁。

package constxiong.concurrency.a023;
import java.util.Random;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;
/**
 * 测试 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件
 * @author ConstXiong
 * @date 2019-09-24 14:50:51
 */
public class TestBreakLockOccupation {
	
	private static Random r = new Random(); 
	private static Lock lock1 = new ReentrantLock();
	private static Lock lock2 = new ReentrantLock();
	
	public static void main(String[] args) {
		new Thread(() -> {
			//标识任务是否完成
			boolean taskComplete = false;
			while (!taskComplete) {
				lock1.lock();
				System.out.println("线程:" +      
				Thread.currentThread().
				getName() + " 获取锁 lock1 成功");
				try {
					//随机休眠,帮助造成死锁环境
					try {
						Thread.sleep(r.nextInt(30));
					} catch (Exception e) {
						e.printStackTrace();
					}
					
					//线程 0 尝试获取 lock2
					if (lock2.tryLock()) {
						System.out.println("线程:" + 
						Thread.currentThread().getName() + " 获
						取锁 lock2 成功");
						try {
							taskComplete = true;
						} finally {
							lock2.unlock();
						}
					} else {
						System.out.println("线程:" + 
                        Thread.currentThread().getName() + " 获
                        取锁 lock2 失败");
					}
				} finally {
					lock1.unlock();
				}
				
				//随机休眠,避免出现活锁
				try {
					Thread.sleep(r.nextInt(10));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
		
		new Thread(() -> {
			//标识任务是否完成
			boolean taskComplete = false;
			while (!taskComplete) {
				lock2.lock();
				System.out.println("线程:" + 
				Thread.currentThread().getName() 
                + " 获取锁 lock2 成功");
				try {
					//随机休眠,帮助造成死锁环境
					try {
						Thread.sleep(r.nextInt(30));
					} catch (Exception e) {
						e.printStackTrace();
					}
					
					//线程2 尝试获取锁 lock1
					if (lock1.tryLock()) {
						System.out.println("线程:" + 
                        Thread.currentThread().getName() + " 获
                        取锁 lock1 成功");
						try {
							taskComplete = true;
						} finally {
							lock1.unlock();
						}
					} else {
						System.out.println("线程:" + 
                        Thread.currentThread().getName() + " 获
                        取锁 lock1 失败");
					}
				} finally {
					lock2.unlock();
				}
				
				//随机休眠,避免出现活锁
				try {
					Thread.sleep(r.nextInt(10));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
	
}

饥饿
一个线程因为 CPU 时间全部被其他线程抢占而得不到 CPU 运行时间,导致线程无法执行。
产生饥饿的原因:
优先级线程吞噬所有的低优先级线程的 CPU 时间
其他线程总是能在它之前持续地对该同步块进行访问,线程被永久堵塞在一个等待进入同步块
其他线程总是抢先被持续地获得唤醒,线程一直在等待被唤醒

package constxiong.concurrency.a024;
import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;
/**
 * 测试线程饥饿
 * @author ConstXiong
 */
public class TestThreadHungry {

	private static ExecutorService es =   
	Executors.newSingleThreadExecutor();
	
   public static void main(String[] args) throws 
   InterruptedException, ExecutionException {
		Future<String> future1 = es.submit(new 
		Callable<String>() {
			@Override
			public String call() throws Exception {
				System.out.println("提交任务1");
				Future<String> future2 = es.submit(new 
				Callable<String>() {
					@Override
					public String call() throws Exception {
						System.out.println("提交任务2");
						return "任务 2 结果";
					}
				});
				return future2.get();
			}
		});
		System.out.println("获取到" + future1.get());
	}
}

打印结果如下,线程池卡死。线程池只能容纳 1 个任务,任务 1 提交任务 2,任务 2 永远得不到执行。
提交任务1

23 Java中有哪些无锁技术来解决并发问题?如何使用?

除了使用 synchronized、Lock 加锁之外,Java 中还有很多不需要加锁就可以解决并发问题的工具类
原子工具类
JDK 1.8 中,java.util.concurrent.atomic 包下类都是原子类,原子类都是基于 sun.misc.Unsafe 实现的。
CPU 为了解决并发问题,提供了 CAS 指令,全称 Compare And Swap,即比较并交互
CAS 指令需要 3 个参数,变量、比较值、新值。当变量的当前值与比较值相等时,才把变量更新为新值
CAS 是一条 CPU 指令,由 CPU 硬件级别上保证原子性

java.util.concurrent.atomic 包中的原子分为:原子性基本数据类型、原子性对象引用类型、原子性数组、原子性对象属性更新器和原子性累加器
原子性基本数据类型:AtomicBoolean、AtomicInteger、AtomicLong
原子性对象引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference
原子性数组:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
原子性对象属性更新:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
原子性累加器:DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder

修改我们之前测试原子性问题的类,使用 AtomicInteger 的简单例子

package constxiong.concurrency.a026;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * 测试 原子类 AtomicInteger
 * 
 * @author ConstXiong
 */public class TestAtomicInteger {

	// 计数变量
	static volatile AtomicInteger count = new  
	AtomicInteger(0);

	public static void main(String[] args) throws 
	InterruptedException {
		// 线程 1 给 count 加 10000
		Thread t1 = new Thread(() -> {
			for (int j = 0; j <10000; j++) {
				count.incrementAndGet();
			}
			System.out.println("thread t1 count 加 10000 结
			束");
		});

		// 线程 2 给 count 加 10000
		Thread t2 = new Thread(() -> {
			for (int j = 0; j <10000; j++) {
				count.incrementAndGet();
			}
			System.out.println("thread t2 count 加 10000 结
			束");
		});

		// 启动线程 1
		t1.start();
		// 启动线程 2
		t2.start();

		// 等待线程 1 执行完成
		t1.join();
		// 等待线程 2 执行完成
		t2.join();

		// 打印 count 变量
		System.out.println(count.get());
	}
}
打印结果如预期
thread t2 count 加 10000 结束
thread t1 count 加 10000 结束
20000

线程本地存储
java.lang.ThreadLocal 类用于线程本地化存储。
线程本地化存储,就是为每一个线程创建一个变量,只有本线程可以在该变量中查看和修改值。
典型的使用例子就是,spring 在处理数据库事务问题的时候,就用了 ThreadLocal 为每个线程存储了各自的数据库连接 Connection。
使用 ThreadLocal 要注意,在不使用该变量的时候,一定要调用 remove() 方法移除变量,否则可能造成内存泄漏的问题。

示例

package constxiong.concurrency.a026;
/**
 * 测试 原子类 AtomicInteger
 * 
 * @author ConstXiong
 */public class TestThreadLocal {

// 线程本地存储变量
private static final ThreadLocal<Integer> THREAD_LOCAL_NUM = new 
ThreadLocal<Integer>() {
		@Override
		protected Integer initialValue() {//初始值
			return 0;
		}
	};

	public static void main(String[] args) {
		for (int i = 0; i <3; i++) {// 启动三个线程
			Thread t = new Thread() {
				@Override
				public void run() {
					add10ByThreadLocal();
				}
			};
			t.start();
		}
	}

	/**
	 * 线程本地存储变量加 5
	 */
	private static void add10ByThreadLocal() {
		try {
			for (int i = 0; i <5; i++) {
				Integer n = THREAD_LOCAL_NUM.get();
				n += 1;
				THREAD_LOCAL_NUM.set(n);
		        System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
			}
		} finally {
			THREAD_LOCAL_NUM.remove();// 将变量移除
		}
	}
}

每个线程最后一个值都打印到了 5

Thread-0 : ThreadLocal num=1
Thread-2 : ThreadLocal num=1
Thread-1 : ThreadLocal num=1
Thread-2 : ThreadLocal num=2
Thread-0 : ThreadLocal num=2
Thread-2 : ThreadLocal num=3
Thread-0 : ThreadLocal num=3
Thread-1 : ThreadLocal num=2
Thread-0 : ThreadLocal num=4
Thread-2 : ThreadLocal num=4
Thread-0 : ThreadLocal num=5
Thread-1 : ThreadLocal num=3
Thread-2 : ThreadLocal num=5
Thread-1 : ThreadLocal num=4
Thread-1 : ThreadLocal num=5

copy-on-write
根据英文名称可以看出,需要写时复制,体现的是一种延时策略。
Java 中的 copy-on-write 容器包括:CopyOnWriteArrayList、CopyOnWriteArraySet。
涉及到数组的全量复制,所以也比较耗内存,在写少的情况下使用比较适合。

简单的 CopyOnWriteArrayList 的示例,这里只是说明 CopyOnWriteArrayList 怎么用,并且是线程安全的。这个场景并不适合使用 CopyOnWriteArrayList,因为写多读少。

package constxiong.concurrency.a026;
import java.util.ArrayList;import 
java.util.List;import 
java.util.Random;import 
java.util.concurrent.CopyOnWriteArrayList;
/**
 * 测试 copy-on-write
 * @author ConstXiong
 */
 public class TestCopyOnWrite {

	private static final Random R = new Random();
	private static CopyOnWriteArrayList<Integer> cowList = new 
    CopyOnWriteArrayList<Integer>();//	private static ArrayList<Integer> cowList = new ArrayList<Integer>();
	
	public static void main(String[] args) throws InterruptedException {
		List<Thread> threadList = new ArrayList<Thread>();
		//启动 1000 个线程,向 cowList 添加 5 个随机整数
		for (int i = 0; i <1000; i++) {
			Thread t = new Thread(() -> {
				for (int j = 0; j <5; j++) {
					//休眠 10 毫秒,让线程同时向 cowList 添加整数,引出并发问题
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					cowList.add(R.nextInt(100));
				}
			}) ;
			t.start();
			threadList.add(t);
		}
		for (Thread t : threadList) {
			t.join();
		}
		System.out.println(cowList.size());
	}
}

打印结果
5000

如果把
private static CopyOnWriteArrayList cowList = new CopyOnWriteArrayList();
改为
private static ArrayList cowList = new ArrayList();
打印结果就是小于 5000 的整数了

其他 “Concurrent” 开头的并发工具类,如:ConcurrentHashMap、ConcurrentLinkedDeque、ConcurrentLinkedQueue…

24 什么是 Java 内存模型?

在了解什么是 Java 内存模型之前,先了解一下为什么要提出 Java 内存模型。
之前提到过并发编程有三大问题
CPU 缓存,在多核 CPU 的情况下,带来了可见性问题
操作系统对当前执行线程的切换,带来了原子性问题
译器指令重排优化,带来了有序性问题
为了解决并发编程的三大问题,提出了 JSR-133,新的 Java 内存模型,JDK 5 开始使用。

那么什么是 Java 内存模型呢?
现在说的 Java 内存模型,一般是指 JSR-133: Java Memory Model and Thread Specification Revision 规定的 Java 内存模型。
JSR-133 具体描述:jsr133.pdf
JSR-133 在 JCP 官网的具体描述

说明下
JSR:Java Specification Requests,Java 规范提案。
JCP:Java Community Process 是一个开放的国际组织,成立于1998年,主要由 Java 开发者以及被授权者组成,是使有兴趣的各方参与定义 Java 的特征和未来版本的正式过程。

简单总结下
Java 内存模型是 JVM 的一种规范
定义了共享内存在多线程程序中读写操作行为的规范
屏蔽了各种硬件和操作系统的访问差异,保证了 Java 程序在各种平台下对内存的访问效果一致

解决并发问题采用的方式:限制处理器优化和使用内存屏障
增强了三个同步原语(synchronized、volatile、final)的内存语义
定义了 happens-before 规则

什么是 happens-before 原则?
Java 中 happens-before 原则,是在 JSR-133 中提出的。
原文摘要:
• Each action in a thread happens-before every subsequent action in that thread.
• An unlock on a monitor happens-before every subsequent lock on that monitor.
• A write to a volatile field happens-before every subsequent read of that volatile.
• A call to start() on a thread happens-before any actions in the started thread.
• All actions in a thread happen-before any other thread successfully returns from a join() on that thread.
• If an action a happens-before an action b, and b happens before an action c, then a happensbefore c.
• the completion of an object’s constructor happens-before the execution of its finalize method (in the formal sense of happens-before).

翻译过来加上自己的理解就是:
程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。(这里涉及到 CPU 指令重排,所以需要加入内存屏障保证有序性)
管程锁定规则:对一个锁的解锁操作,先行发生于后续对这个锁的加锁操作。这里必须强调的是同一个锁。
volatile 变量规则:对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。
线程启动规则:Thread 对象的 start() 方法先行发生于此线程的每一个动作。
线程 join() 规则:被调用 join() 方法的线程的所有操作先行发生于 join() 的返回。
传递性规则:操作 a 先发生于操作 b,操作 b 先发生于操作 c,则操作 a 先发生于操作 c。
对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法。

25 sleep()和wait()有什么区别?

sleep() 是 Thread 类的静态本地方法;wait() 是Object类的成员本地方法
sleep() 方法可以在任何地方使用;
wait() 方法则只能在同步方法或同步代码块中使用,否则抛出异常Exception in thread “Thread-0” java.lang.IllegalMonitorStateException
sleep() 会休眠当前线程指定时间,释放 CPU 资源,不释放对象锁,休眠时间到自动苏醒继续执行;wait() 方法放弃持有的对象锁,进入等待队列,当该对象被调用 notify() / notifyAll() 方法后才有机会竞争获取对象锁,进入运行状态
JDK1.8 sleep() wait() 均需要捕获 InterruptedException 异常

测试代码

public class Test {

    private static Object obj = new Object();

    public static void main(String[] args) {
        //测试sleep()
        //测试 RunnableImpl1 wait(); RunnableImpl2 notify()
        Thread t1 = new Thread(new RunnableImpl1(obj));
        Thread t2 = new Thread(new RunnableImpl2(obj));
        t1.start();
        t2.start();

        //测试RunnableImpl3 wait(long timeout)方法
        Thread t3 = new Thread(new RunnableImpl3(obj));
        t3.start();
    }
}

class RunnableImpl1 implements Runnable {

    private Object obj;

    public RunnableImpl1(Object obj) {
        this.obj = obj;
    }

    public void run() {
        System.out.println("RunnableImpl1 run on RunnableImpl1");
        synchronized (obj) {
            System.out.println("RunnableImpl1 obj to wait on RunnableImpl1");
            try {
                obj.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("RunnableImpl1 obj continue to run on RunnableImpl1");
        }
    }
}

class RunnableImpl2 implements Runnable {

    private Object obj;

    public RunnableImpl2(Object obj) {
        this.obj = obj;
    }

    public void run() {
        System.out.println("RunnableImpl2 run on RunnableImpl2");
        System.out.println("RunnableImpl2 睡眠3秒...");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (obj) {
            System.out.println("RunnableImpl2 notify obj on RunnableImpl2");
            obj.notify();
        }
    }
}

class RunnableImpl3 implements Runnable {

    private Object obj;

    public RunnableImpl3(Object obj) {
        this.obj = obj;
    }

    public void run() {
        System.out.println("RunnableImpl3 run on RunnableImpl3");
        synchronized (obj) {
            System.out.println("RunnableImpl3 obj to wait on RunnableImpl3");
            try {
                obj.wait(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("RunnableImpl3 obj continue to run on RunnableImpl3");
        }
    }
}

打印结果

RunnableImpl1 run on RunnableImpl1
RunnableImpl2 run on RunnableImpl2
RunnableImpl1 obj to wait on RunnableImpl1
RunnableImpl2 睡眠3...
RunnableImpl3 run on RunnableImpl3
RunnableImpl3 obj to wait on RunnableImpl3
RunnableImpl2 notify obj on RunnableImpl2
RunnableImpl1 obj continue to run on RunnableImpl1
RunnableImpl3 obj continue to run on RunnableImpl3

26 Runnable和Callable有什么区别?

主要区别
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息

测试代码

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
 public class TestRunnableAndCallable {
 
	public static void main(String[] args) {
		testImplementsRunable();
		testImplementsCallable();
		testImplementsCallableWithException();
	}
	
	//测试实现Runnable接口的方式创建、启动线程
	public static void testImplementsRunable() {
		Thread t1 = new Thread(new CustomRunnable());
		t1.setName("CustomRunnable");
		t1.start();
	}
	
	//测试实现Callable接口的方式创建、启动线程
	public static void testImplementsCallable() {
		Callable<String> callable = new CustomCallable();
		FutureTask<String> futureTask = new FutureTask<String>(callable);
		Thread t2 = new Thread(futureTask);
		t2.setName("CustomCallable");
		t2.start();
		try {
			System.out.println(futureTask.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
	
	//测试实现Callable接口的方式创建、启动线程,抛出异常
	public static void testImplementsCallableWithException() {
		Callable<String> callable = new CustomCallable2();
		FutureTask<String> futureTask = new FutureTask<String>(callable);
		Thread t3 = new Thread(futureTask);
		t3.setName("CustomCallableWithException");
		t3.start();
		try {
			System.out.println(futureTask.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
	
}
 //实现Runnable接口,重写run方法
class CustomRunnable implements Runnable {
	public void run() {
		System.out.println(Thread.currentThread().getName());
        //throw new RuntimeException("aaa");
	}
	
}
 //实现Callable接口,重写call方法
class CustomCallable implements Callable<String> {
	public String call() throws Exception {
		System.out.println(Thread.currentThread().getName());
		return "Callable Result";
	}
	
}
//实现Callable接口,重写call方法无法计算抛出异常
class CustomCallable2 implements Callable<String> {
	public String call() throws Exception {
		System.out.println(Thread.currentThread().getName());
		throw new Exception("I can compute a result");
	}
}
CustomRunnable
CustomCallable
Callable Result

CustomCallableWithException
java.util.concurrent.ExecutionException: java.lang.Exception: I can compute a result
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.cmcc.RunnableCallable.Test.testImplementsCallableWithException(Test.java:54)
	at com.cmcc.RunnableCallable.Test.main(Test.java:20)
Caused by: java.lang.Exception: I can compute a result
	at com.cmcc.RunnableCallable.CustomCallable2.call(Test.java:89)
	at com.cmcc.RunnableCallable.CustomCallable2.call(Test.java:85)
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
	at java.util.concurrent.FutureTask.run(FutureTask.java)
	at java.lang.Thread.run(Thread.java:748)

27 notify()和notifyAll()有什么区别?

先解释两个概念。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁。

锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待

区别:
notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。

测试代码

public class TestNotifyNotifyAll {
 
	private static Object obj = new Object();

	public static void main(String[] args) {
		//测试 RunnableImplA wait()        
		Thread t1 = new Thread(new RunnableImplA(obj));
		Thread t2 = new Thread(new RunnableImplA(obj));
		t1.start();
		t2.start();
		//RunnableImplB notify()
		Thread t3 = new Thread(new RunnableImplB(obj));
		t3.start();
	   RunnableImplC notifyAll()//		
       Thread t4 = new Thread(new RunnableImplC(obj));//		
       t4.start();
	}
}
 
 
class RunnableImplA implements Runnable {
 
	private Object obj;
	
	public RunnableImplA(Object obj) {
		this.obj = obj;
	}
	
	public void run() {
		System.out.println("run on RunnableImplA");
		synchronized (obj) {
			System.out.println("obj to wait on RunnableImplA");
			try {
				obj.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("obj continue to run on RunnableImplA");
		}
	}
}
 
class RunnableImplB implements Runnable {
 
	private Object obj;
	
	public RunnableImplB(Object obj) {
		this.obj = obj;
	}
	
	public void run() {
		System.out.println("run on RunnableImplB");
		System.out.println("睡眠3秒...");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		synchronized (obj) {
			System.out.println("notify obj on RunnableImplB");
			obj.notify();
		}
	}
}
 
class RunnableImplC implements Runnable {
 
	private Object obj;
	
	public RunnableImplC(Object obj) {
		this.obj = obj;
	}
	
	public void run() {
		System.out.println("run on RunnableImplC");
		System.out.println("睡眠3秒...");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		synchronized (obj) {
			System.out.println("notifyAll obj on RunnableImplC");
			obj.notifyAll();
		}
	}
}

结果:仅调用一次 obj.notify(),线程 t1 或 t2 中的一个始终在等待被唤醒,程序不终止
run on RunnableImplA
obj to wait on RunnableImplA
run on RunnableImplA
obj to wait on RunnableImplA
run on RunnableImplB
睡眠3秒…
notify obj on RunnableImplB
obj continue to run on RunnableImplA

把 t3 注掉,启动 t4 线程。调用 obj.notifyAll() 方法

结果:t1、t2线程均可以执行完毕
run on RunnableImplA
obj to wait on RunnableImplA
run on RunnableImplA
obj to wait on RunnableImplA
run on RunnableImplC
睡眠3秒…
notifyAll obj on RunnableImplC
obj continue to run on RunnableImplA
obj continue to run on RunnableImplA

28 线程池中submit()和execute()方法有什么区别?

execute() 参数 Runnable ;
submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
execute() 没有返回值;
而 submit() 有返回值
submit() 的返回值 Future 调用get方法时,可以捕获处理异常

29 ThreadLocal有什么作用?有哪些使用场景?

ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方式,避免资源在多线程间共享。
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。

ThreadLocal 使用例子:

public class TestThreadLocal {
	
	//线程本地存储变量
	private static final ThreadLocal<Integer> THREAD_LOCAL_NUM = new ThreadLocal<Integer>() {
		@Override
		protected Integer initialValue() {
			return 0;
		}
	};
 
	public static void main(String[] args) {
		for (int i = 0; i <3; i++) {//启动三个线程
			Thread t = new Thread() {
				@Override
				public void run() {
					add10ByThreadLocal();
				}
			};
			t.start();
		}
	}
	
	/**
	 * 线程本地存储变量加 5
	 */
	private static void add10ByThreadLocal() {
		for (int i = 0; i <5; i++) {
			Integer n = THREAD_LOCAL_NUM.get();
			n += 1;
			THREAD_LOCAL_NUM.set(n);
		    System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
		}
	}
	
}

打印结果:启动了 3 个线程,每个线程最后都打印到 "ThreadLocal num=5",而不是 num 一直在累加直到值等于 15
Thread-0 : ThreadLocal num=1
Thread-1 : ThreadLocal num=1
Thread-0 : ThreadLocal num=2
Thread-0 : ThreadLocal num=3
Thread-1 : ThreadLocal num=2
Thread-2 : ThreadLocal num=1
Thread-0 : ThreadLocal num=4
Thread-2 : ThreadLocal num=2
Thread-1 : ThreadLocal num=3
Thread-1 : ThreadLocal num=4
Thread-2 : ThreadLocal num=3
Thread-0 : ThreadLocal num=5
Thread-2 : ThreadLocal num=4
Thread-2 : ThreadLocal num=5
Thread-1 : ThreadLocal num=5

30 synchronized和volatile的区别是什么?

作用:
synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。

区别:
synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。
synchronized 可以保证线程间的有序性(个人猜测是无法保证线程内的有序性,即线程内的代码可能被 CPU 指令重排序)、原子性和可见性;volatile 只保证了可见性和有序性,无法保证原子性。
synchronized 线程阻塞,volatile 线程不阻塞。
volatile 本质是告诉 jvm 当前变量在寄存器中的值是不安全的需要从内存中读取;sychronized 则是锁定当前变量,只有当前线程可以访问到该变量其他线程被阻塞。
volatile 标记的变量不会被编译器优化;
synchronized 标记的变量可以被编译器优化。

40 synchronized和Lock有什么区别?

实现层面不一样。synchronized 是 Java 关键字,JVM层面实现加锁和释放锁;
Lock 是一个接口,在代码层面实现加锁和释放锁
是否自动释放锁。synchronized 在线程代码执行完或出现异常时自动释放锁;
Lock 不会自动释放锁,需要再 finally {} 代码块显式地中释放锁
是否一直等待。synchronized 会导致线程拿不到锁一直等待;Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
获取锁成功是否可知。synchronized 无法得知是否获取锁成功;
Lock 可以通过 tryLock 获得加锁是否成功
功能复杂性。synchronized 加锁可重入、不可中断、非公平;Lock 可重入、可判断、可公平和不公平、细分读写锁提高效率

41 synchronized和ReentrantLock区别是什么?

synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放

补充一个相同点:都可以做到同一线程,同一把锁,可重入代码块。

42 ReadWriteLock如何使用?

ReadWriteLock,读写锁。
ReentrantReadWriteLock 是 ReadWriteLock 的一种实现。

特点:
包含一个 ReadLock 和 一个 WriteLock 对象
读锁与读锁不互斥;读锁与写锁,写锁与写锁互斥
适合对共享资源有读和写操作,写操作很少,读操作频繁的场景
可以从写锁降级到读锁。获取写锁->获取读锁->释放写锁
无法从读锁升级到写锁
读写锁支持中断
写锁支持Condition;读锁不支持Condition

示例1--根据 key 获取 value 值

private ReadWriteLock lock = new ReentrantReadWriteLock();
//定义读写锁
//根据 key 获取 value 值
public Object getValue(String key){
//使用读写锁的基本结构
lock.readLock().lock();//加读锁
Object value = null;
try{
		value = cache.get(key);
		if(value == null){
			lock.readLock().unlock();//value值为空,释放读锁
			lock.writeLock().lock();//加写锁,写入value值
			try{
				//重新检查 value值是否已经被其他线程写入
				if(value == null){
					value = "value";//写入数据
				}
			}finally{
				lock.writeLock().unlock();
			}
			lock.readLock().lock();
		}
	}finally{
		lock.readLock().unlock();
	}
	return value;
}

示例2–多线程环境下的读写锁使用

package constxiong.interview;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
 /**
 * 测试可重入 读写锁
 * @author ConstXiong
 * @date 2019-06-10 11:19:42
 */public class TestReentrantReadWriteLock {
	
	private Map<String, Object> map = new HashMap<String, Object>();
	private ReadWriteLock lock = new ReentrantReadWriteLock();
	
	/**
	 * 根据 key 获取 value
	 * @param key
	 * @return
	 */
	public Object get(String key) {
		Object value = null;
		lock.readLock().lock();
		try {
			Thread.sleep(50L);
			value = map.get(key);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.readLock().unlock();
		}
		return value; 
	}
	
	/**
	 * 设置key-value
	 * @param key
	 * @return
	 */
	public void set(String key, Object value) {
		lock.writeLock().lock();
		try {
			Thread.sleep(50L);
			map.put(key, value);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.writeLock().unlock();
		}
	}
 
	//测试5个线程读数据,5个线程写数据
	public static void main(String[] args) {
		final TestReentrantReadWriteLock test = new TestReentrantReadWriteLock();
		final String key = "lock";
		final Random r = new Random();
		for (int i = 0; i <5; i++) {
			new Thread(){
				@Override
				public void run() {
					for (int j = 0; j <10; j++) {
						System.out.println(Thread.currentThread().getName() 

                                + " read value=" + test.get(key));
					}
				}
			}.start();
			
			new Thread(){
				@Override
				public void run() {
					for (int j = 0; j <10; j++) {
						int value = r.nextInt(1000);
						test.set(key, value);
						System.out.println(Thread.currentThread().getName()   

                          + " write value=" + value);
					}
				}
			}.start();
		}
	}
	
}

43 JDK中Atomic开头的原子类实现原子性的原理是什么?

JDK Atomic开头的类,是通过 CAS 原理解决并发情况下原子性问题。
CAS 包含 3 个参数,CAS(V, E, N)。V 表示需要更新的变量,E 表示变量当前期望值,N 表示更新为的值。只有当变量 V 的值等于 E 时,变量 V 的值才会被更新为 N。如果变量 V 的值不等于 E ,说明变量 V 的值已经被更新过,当前线程什么也不做,返回更新失败。
当多个线程同时使用 CAS 更新一个变量时,只有一个线程可以更新成功,其他都失败。失败的线程不会被挂起,可以继续重试 CAS,也可以放弃操作。
CAS 操作的原子性是通过 CPU 单条指令完成而保障的。JDK 中是通过 Unsafe 类中的 API 完成的。
在并发量很高的情况,会有大量 CAS 更新失败,所以需要慎用。

未使用原子类,测试代码

package constxiong.interview;
 /**
 * JDK 原子类测试
 * @author ConstXiong
 * @date 2019-06-11 11:22:01
 */public class TestAtomic {
 
	private int count = 0;
	
	public int getAndIncrement() {
		return count++;
	}
	//	private AtomicInteger count = new AtomicInteger(0);
	//	//	public int getAndIncrement() {
	//		return  
    count.getAndIncrement();
//	}
	
	public static void main(String[] args) {
		final TestAtomic test = new TestAtomic();
		for (int i = 0; i <3; i++) {
			new Thread(){
				@Override
				public void run() {
					for (int j = 0; j <10; j++) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName() 
                         + " 获取递增值:" + test.getAndIncrement());
					}
				}
			}.start();
		}
	}
}

打印结果中,包含重复值
Thread-0 获取递增值:1
Thread-2 获取递增值:2
Thread-1 获取递增值:0
Thread-0 获取递增值:3
Thread-2 获取递增值:3
Thread-1 获取递增值:3
Thread-2 获取递增值:4
Thread-0 获取递增值:5
Thread-1 获取递增值:5
Thread-1 获取递增值:6
Thread-2 获取递增值:8
Thread-0 获取递增值:7
Thread-1 获取递增值:9
Thread-0 获取递增值:10
Thread-2 获取递增值:10
Thread-0 获取递增值:11
Thread-2 获取递增值:13
Thread-1 获取递增值:12
Thread-1 获取递增值:14
Thread-0 获取递增值:14
Thread-2 获取递增值:14
Thread-1 获取递增值:15
Thread-2 获取递增值:15
Thread-0 获取递增值:16
Thread-1 获取递增值:17
Thread-0 获取递增值:19
Thread-2 获取递增值:18
Thread-0 获取递增值:20
Thread-1 获取递增值:21
Thread-2 获取递增值:22

测试代码修改为原子类

package constxiong.interview;
import java.util.concurrent.atomic.AtomicInteger;
 /**
 * JDK 原子类测试
 * @author ConstXiong
 * @date 2019-06-11 11:22:01
 */public class TestAtomic {
 //	private int count = 0;//	//	public int getAndIncrement() {//		return count++;//	}
	
	private AtomicInteger count = new AtomicInteger(0);
	
	public int getAndIncrement() {
		return count.getAndIncrement();
	}
	
	public static void main(String[] args) {
		final TestAtomic test = new TestAtomic();
		for (int i = 0; i <3; i++) {
			new Thread(){
				@Override
				public void run() {
					for (int j = 0; j <10; j++) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()                   + " 获取递增值:" + 
						test.getAndIncrement());
					}
				}
			}.start();
		}
	}
}

打印结果中,不包含重复值
Thread-0 获取递增值:1
Thread-2 获取递增值:2
Thread-1 获取递增值:0
Thread-0 获取递增值:3
Thread-1 获取递增值:4
Thread-2 获取递增值:5
Thread-0 获取递增值:6
Thread-1 获取递增值:7
Thread-2 获取递增值:8
Thread-0 获取递增值:9
Thread-2 获取递增值:10
Thread-1 获取递增值:11
Thread-0 获取递增值:12
Thread-1 获取递增值:13
Thread-2 获取递增值:14
Thread-0 获取递增值:15
Thread-1 获取递增值:16
Thread-2 获取递增值:17
Thread-0 获取递增值:18
Thread-1 获取递增值:19
Thread-2 获取递增值:20
Thread-0 获取递增值:21
Thread-2 获取递增值:23
Thread-1 获取递增值:22
Thread-0 获取递增值:24
Thread-1 获取递增值:25
Thread-2 获取递增值:26
Thread-0 获取递增值:27
Thread-2 获取递增值:28
Thread-1 获取递增值:29

44 介绍一下ForkJoinPool的使用

ForkJoinPool 是 JDK1.7 开始提供的线程池。为了解决 CPU 负载不均衡的问题。如某个较大的任务,被一个线程去执行,而其他线程处于空闲状态。

ForkJoinTask 表示一个任务,ForkJoinTask 的子类中有 RecursiveAction 和 RecursiveTask。
RecursiveAction 无返回结果;RecursiveTask 有返回结果。
重写 RecursiveAction 或 RecursiveTask 的 compute(),完成计算或者可以进行任务拆分。

调用 ForkJoinTask 的 fork() 的方法,可以让其他空闲的线程执行这个 ForkJoinTask;
调用 ForkJoinTask 的 join() 的方法,将多个小任务的结果进行汇总。

无返回值打印任务拆分

package constxiong.interview;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;
 /**
 * 测试 ForkJoinPool 线程池的使用
 * @author ConstXiong
 * @date 2019-06-12 12:05:55
 */
 public class TestForkJoinPool {
 
	public static void main(String[] args) throws Exception {
		testNoResultTask();//测试使用 ForkJoinPool 无返回值的任务执行
	}
	
	/**
	 * 测试使用 ForkJoinPool 无返回值的任务执行
	 * @throws Exception
	 */
	public static void testNoResultTask() throws Exception {
		ForkJoinPool pool = new ForkJoinPool();
		pool.submit(new PrintTask(1, 200));
		pool.awaitTermination(2, TimeUnit.SECONDS);
		pool.shutdown();
	}
}
	/**
 * 无返回值的打印任务
 * @author ConstXiong
 * @date 2019-06-12 12:07:02
 */
class PrintTask extends RecursiveAction {
	
	private static final long serialVersionUID = 1L;
	private static final int THRESHOLD = 49;
	private int start;
	private int end;
	
	public PrintTask(int start, int end) {
		super();
		this.start = start;
		this.end = end;
	}
	
 
	@Override
	protected void compute() {
		//当结束值比起始值 大于 49 时,按数值区间平均拆分为两个任务;否则
           直接打印该区间的值
		if (end - start <THRESHOLD) {
			for (int i = start; i <= end; i++) {
				System.out.println(Thread.currentThread().getName() + ", i =   " + i);
			}
		} else {
			int middle = (start + end) / 2;
			PrintTask firstTask = new PrintTask(start, middle);
			PrintTask secondTask = new PrintTask(middle + 1, end);
			firstTask.fork();
			secondTask.fork();
		}
	}
}
 
有返回值计算任务拆分、结果合并
package constxiong.interview;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;
 /**
 * 测试 ForkJoinPool 线程池的使用
 * @author ConstXiong
 * @date 2019-06-12 12:05:55
 */
 public class TestForkJoinPool {
 public static void main(String[] args) throws Exception {
		testHasResultTask();//测试使用 ForkJoinPool 有返回值的任务执行,对结果进行合并。计算 1 到 200 的累加和
	}
	
	/**
	 * 测试使用 ForkJoinPool 有返回值的任务执行,对结果进行合并。计算 1 到 
       200 的累加和
	 * @throws Exception
	 */
	public static void testHasResultTask() throws Exception {
		int result1 = 0;
		for (int i = 1; i <= 200; i++) {
			result1 += i;
		}
		System.out.println("循环计算 1-200 累加值:" + result1);
		
		ForkJoinPool pool = new ForkJoinPool();
		ForkJoinTask<Integer> task = pool.submit(new CalculateTask(1, 
         200));
		int result2 = task.get();
		System.out.println("并行计算 1-200 累加值:" + result2);
		pool.awaitTermination(2, TimeUnit.SECONDS);
		pool.shutdown();
	}
	
}


 /**
 * 有返回值的计算任务
 * @author ConstXiong
 * @date 2019-06-12 12:07:25
 */
class CalculateTask extends RecursiveTask<Integer> {
 
	private static final long serialVersionUID = 1L;
	private static final int THRESHOLD = 49;
	private int start;
	private int end;
	
	public CalculateTask(int start, int end) {
		super();
		this.start = start;
		this.end = end;
	}
 
	@Override
	protected Integer compute() {
	//当结束值比起始值 大于 49 时,按数值区间平均拆分为两个任务,进行     两个任务的累加值汇总;否则直接计算累加值
		if (end - start <= THRESHOLD) {
			int result = 0;
			for (int i = start; i <= end; i++) {
				result += i;
			}
			return result;
		} else {
			int middle = (start + end) / 2;
			CalculateTask firstTask = new CalculateTask(start, middle);
			CalculateTask secondTask = new CalculateTask(middle + 1, end);
			firstTask.fork();
			secondTask.fork();
			return firstTask.join() + secondTask.join();
		}
	}
}

45 LongAdder与AtomicLong有什么区别?

AtomicLong 是基于 CAS 方式自旋更新的;
LongAdder 是把 value 分成若干cell,并发量低的时候,直接 CAS 更新值,成功即结束。并发量高的情况,CAS更新某个cell值和需要时对cell数据扩容,成功结束;更新失败自旋 CAS 更新 cell值。取值的时候,调用 sum() 方法进行每个cell累加。
AtomicLong 包含有原子性的读、写结合的api;LongAdder 没有原子性的读、写结合的api,能保证结果最终一致性。
低并发场景AtomicLong 和 LongAdder 性能相似,高并发场景 LongAdder 性能优于 AtomicLong。

46 乐观锁与悲观锁是什么?

悲观锁(Pessimistic Lock):线程每次在处理共享数据时都会上锁,其他线程想处理数据就会阻塞直到获得锁。
乐观锁(Optimistic Lock):线程每次在处理共享数据时都不会上锁,在更新时会通过数据的版本号等机制判断其他线程有没有更新数据。乐观锁适合读多写少的应用场景

两种锁各有优缺点:
乐观锁适用于读多写少的场景,可以省去频繁加锁、释放锁的开销,提高吞吐量
在写比较多的场景下,乐观锁会因为版本不一致,不断重试更新,产生大量自旋,消耗 CPU,影响性能。这种情况下,适合悲观锁

47 使用对象的wait()方法需要注意什么?

wait() 方法是线程间通信的方法之一
必须在 synchronized 方法或 synchronized 修饰的代码块中使用,否则会抛出 IllegalMonitorStateException
只能在加锁的对象调用 wait() 方法
加锁的对象调用 wait() 方法后,线程进入等待状态,直到在加锁的对象上调用 notify() 或者 notifyAll() 方法来唤醒之前进入等待的线程

48 volatile关键字能否保证线程安全?

单纯使用 volatile 关键字是不能保证线程安全的
volatile 只提供了一种弱的同步机制,用来确保将变量的更新操作通知到其他线程
volatile 语义是禁用 CPU 缓存,直接从主内存读、写变量。表现为:更新 volatile 变量时,JMM 会把线程对应的本地内存中的共享变量值刷新到主内存中;读 volatile 变量时,JMM 会把线程对应的本地内存设置为无效,直接从主内存中读取共享变量
当把变量声明为 volatile 类型后,JVM 增加内存屏障,禁止 CPU 进行指令重排

49 如何保证多个线程同时启动?

可以 wait()notify() 实现;也可以使用发令枪 CountDownLatch 实现。
CountDownLatch 实现较简单,如下:
package constxiong.interview;
import java.util.concurrent.CountDownLatch;
/**
 * 测试同时启动多个线程
 * @author ConstXiong
 */
 public class TestCountDownLatch {

	private static CountDownLatch cld = new CountDownLatch(10);
	
	public static void main(String[] args) {
		for (int i = 0; i <10; i++) {
			Thread t = new Thread(new Runnable() {
				public void run() {
					try {
						cld.await();//将线程阻塞在此,等待所有线程都调用start()方法,一起执行
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + 
                   ":" + System.currentTimeMillis());
				}
			});
			t.start();
			cld.countDown();
		}
	}
	
}

50 同步和异步有何异同,分别在什么情况下使用?

同步:发送一个请求,等待返回,然后再发送下一个请求
异步:发送一个请求,不等待返回,随时可以再发送下一个请求

使用场景
如果数据存在线程间的共享,或竞态条件,需要同步。如多个线程同时对同一个变量进行读和写的操作

当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就可以使用异步,提高效率、加快程序的响应

51 sleep()和yield()有什么区别?

sleep() 方法给其他线程运行机会时不考虑线程的优先级;
yield() 方法只会给相同优先级或更高优先级的线程运行的机会线程执行 sleep() 方法后进入阻塞状态;
线程执行 yield() 方法转入就绪状态,可能马上又得得到执行
sleep() 方法声明抛出 InterruptedException;
yield() 方法没有声明抛出异常
sleep() 方法需要指定时间参数;
yield() 方法出让 CPU 的执行权时间由 JVM 控制

51 说说与线程相关的方法

加锁对象的 wait() 方法,使一个线程处于等待状态,并且释放所持有的对象的锁
加锁对象的 notify() 方法,由 JVM 唤醒一个处于等待状态的线程,具体哪个线程不确定,且与优先级无关
加锁对象的 notityAll() 方法,唤醒所有处入等待状态的线程,让它们重新竞争对象的锁
线程的 sleep() 方法,使一个正在运行的线程处于睡眠状态,是静态方法,调用此方法要捕捉 InterruptedException 异常
JDK 1.5 开始通过 Lock 接口提供了显式锁机制,丰富了锁的功能,可以尝试加锁和加锁超时。Lock 接口中定义了加锁 lock()、释放锁 unlock() 方法 和 newCondition() 产生用于线程之间通信的 Condition 对象的方法
JDK 1.5 开始提供了信号量 Semaphore 机制,信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须调用 Semaphore 对象的 acquire() 方法得到信号量的许可;在完成对资源的访问后,线程必须调用 Semaphore 对象的 release() 方法向信号量归还许可

52 说说对于sychronized同步锁的理解

每个 Java 对象都有一个内置锁
线程运行到非静态的 synchronized 同步方法上时,自动获得实例对象的锁
持有对象锁的线程才能运行 synchronized 同步方法或代码块时
一个对象只有一个锁
一个线程获得该锁,其他线程就无法获得锁,直到第一个线程释放锁。任何其他线程都不能进入该对象上的 synchronized 方法或代码块,直到该锁被释放。
释放锁是指持锁线程退出了 synchronized 同步方法或代码块
类可以同时拥有同步和非同步方法
只有同步方法,没有同步变量和类
在加锁时,要明确需要加锁的对象
线程可以获得多个锁
同步应该尽量缩小范围

53 Java中实现线程通信方式有哪些?

对象的 wait(long timeout)、wait(long timeout, int nanos)、wait() 方法,组合对象的 notify()、notifyAll()
显示锁:Lock.newCondition()、Condition await 系列方法、Condition signal()、signalAll()

类似 wait 与 notify

信号量:Semaphore acquire 系列方法、release()系列方法

public static void main(String... args) {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MILLISECONDS, new 
    LinkedBlockingQueue<>(), r -> {
        Thread thread = new Thread(r);
        thread.setName("count-down-thread-" + thread.getId());
        return thread;
    });

    Semaphore semaphore = new Semaphore(5);

    for (int i = 0; i < 8; i++) {
        final int index = i;
        threadPoolExecutor.execute(() -> {
            try {
                semaphore.acquire();
                System.out.println(String.format("%s 号学生开始打电话....", index));

                int computeResult = new Random().nextInt(10);
                try {
                    Thread.sleep(computeResult * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(String.format("%s 号学生挂了电话", index));
                semaphore.release();
            }
        });
    }
}
4 号学生开始打电话....
2 号学生开始打电话....
0 号学生开始打电话....
3 号学生开始打电话....
1 号学生开始打电话....
1 号学生挂了电话
5 号学生开始打电话....
2 号学生挂了电话
6 号学生开始打电话....
3 号学生挂了电话
7 号学生开始打电话....
5 号学生挂了电话
0 号学生挂了电话
4 号学生挂了电话
6 号学生挂了电话
7 号学生挂了电话
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吹老师个人app编程教学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值