通过B站人人都是程序员视频讲解进行的笔记整理,用于个人学习和复习。
目录
4、判断是否是公平锁,但是只能用来ReentrantLock上,不能用在synchronized上
InheritableThreadLocal(可继承的线程本地变量)
一、认识线程:
单线程的情况下,代码是由上到下依次 顺序执行 的,代码如下情况下,不论执行多少次控制台都是打印:1、2、3。
多线程情况下:执行以下代码:
Thread thread1 = new Thread(()->{
System.out.println(1);
});
Thread thread2 = new Thread(()->{
System.out.println(2);
});
Thread thread3 = new Thread(()->{
System.out.println(3);
});
thread1.start();
thread2.start();
thread3.start();
控制台结果: 就会出现多种情况,说明我们的多线程不是 顺序执行 的,而是 并行执行 的。最终的结果是不确定的,哪个线程优先获得cpu执行权,谁就先被执行。
Thread:
我们发现在多线程执行代码时,频繁出现了一个关键字 叫做 Thread(线程)。
什么是多线程?:
线程时进程的最进本执行单位。是CPU调度的最小单位。多线程的存在就是为了同步完成多项任务,从而提高资源的使用效率
图解:
我们在运行某个软件,那么这个运行中的软件就可以当作是一个进程,进程在运行中会产生多个线程,每个线程执行各自的任务,共同完成软件的各个功能的运行。
如下比喻:进程比作是一个工厂,而线程就是工厂里面的一台台的机器。CPU标识工厂内某一组工作人员,CPU核心数就是一个个的员工。 线程运行需要获取到CPU时间片(即一台机器启动运作,需要一个人去操作),所以多线程执行代码效率会比较快(当一个工厂内 有多台机器,多个工人,同步进行工作,产量自然就高),资源也会被充分使用(避免了一个工厂内,明明有这么多机器,这么多人员,你却只启动一台机器,使用一个员工去生产,自然而然就慢)。
什么时候使用线程:
在同一时间需要完成多项任务的时候,比如下载多个文件,多个图片等等,使用多线程可以多个文件同时下载,提高了资源使用率
创建线程的三种方式:
1、继承Thread类
/**
* 自定义一个类 MyThread 继承 Thread 类
* 重写 run() 方法,方法体内写线程需要执行的功能代码
* 线程启动后,会调用这里面的代码。
* */
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("这里执行了我的功能代码");
}
}
如何使用?
public class Test {
public static void main(String[] args) {
//创建MyThread实例
MyThread myThread = new MyThread();
//启动线程,会运行run()内的代码
myThread.start();
}
}
运行结果:
2、实现Runnable接口
/**
* 自定义一个任务类,这个类我们需要去实现Runnable接口
* 重写 run()
* */
public class Task implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable接口");
System.out.println("这里执行了我的功能代码");
}
}
如何使用?
这里需要注意 我们的Runnable它是一个接口,没有启动线程的能力,所以我们需要搭配着Thread来使用,这里 Thread 可以当作是执行Runnable实现类的启动器。
public class Test {
public static void main(String[] args) {
//创建Runnable的实现类对象
Task task = new Task();
//创建Thread,将实现类对象放入到Thread中.
Thread thread = new Thread(task);
//启动线程执行
thread.start();
}
}
运行结果:
3、实现Callable接口:
这种方式能获取到线程执行完返回的返回值。
还有就是Callable接口是带泛型的,我们实现Callable接口的时候,指定的泛型是什么类型,返回值就是什么类型。不指定泛型返回值类型是Object。
与Runnable不同 这个接口的实现类需要实现 call() 方法。
/**
* 自定义一个任务类,这个类我们需要去实现Callable接口
* 指定泛型是Strung
* 所以返回值类型就是String
* 重写 call()
*/
public class Result implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("执行了Callable的run方法的功能代码");
return "我是返回值";
}
}
如何使用?
使用方法和Runnable有些相似,不过需要在其基础上,加上一个FuntureTask<T> 对象
public class Test {
public static void main(String[] args) {
//创建Runnable的实现类对象
Result result = new Result();
//创建FutureTask实例,承载 Callable的实现实例对象
FutureTask<String> futureTask = new FutureTask<>(result);
//创建Thread线程,承载FutureTask
Thread thread = new Thread(futureTask);
//启动线程
thread.start();
try {
//获取返回值
String resultStr = futureTask.get();
//打印返回值
System.out.println(resultStr);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
运行结果:
总结:
获取当前正在执行任务的线程:
Thread.currentThread()
如何使用?
public class Test {
public static void main(String[] args) {
//获取当前正在执行任务的线程
Thread thread = Thread.currentThread();
System.out.println(thread);
}
}
运行结果:
看Thread的toString()方法源码便知:
public String toString() {
ThreadGroup group = getThreadGroup();
if (group != null) {
return "Thread[" + getName() + "," + getPriority() + "," +
group.getName() + "]";
} else {
return "Thread[" + getName() + "," + getPriority() + "," +
"" + "]";
}
}
获取和设置线程的名称:
因为是成员方法,所以首先要获取到线程的实例对象,创建一个也好,获取当前线程也好,都行。
这里 thread 代表线程对象
获取线程的名称:thread.getName();
设置线程的名称:thread.setName(String newName);
public class Test {
public static void main(String[] args) {
//获取当前正在执行任务的线程
Thread thread = Thread.currentThread();
//获取线程名称
String threadName = thread.getName();
//输出线程名称
System.out.println(threadName);
//设置线程名称
thread.setName("newName");
//获取线程名称
String threadNewName = thread.getName();
//输出线程名称
System.out.println(threadNewName);
}
}
运行结果:
run() 方法与 start方法的区别:
获取线程优先级:
线程优先级,默认值(NORM_PRIORITY)是5 、最小值(MIN_PRIORITY)是1 、最大值(MAX_PRIORITY)是10。
thread.getPriority(int priority)
public class Test {
public static void main(String[] args) {
//获取当前正在执行任务的线程
Thread thread = Thread.currentThread();
//获取线程优先级
int priority = thread.getPriority();
//输出线程优先级
System.out.println(priority);
//设置线程优先级
thread.setPriority(10);
//获取线程优先级
int newPriority = thread.getPriority();
//输出线程优先级
System.out.println(newPriority);
}
}
运行结果:
使当前正在执行的线程进入休眠状态:
Thread.sleep(long millis) 是一个静态方法,参数是以毫秒为单位的整数。
Thread.sleep(long millis,int nanos) 是一个静态方法,这个两参数的方法,第一个参数指休眠多少毫秒,第二个参数值多少微秒,总时长为millis毫秒 + nanos微秒
public class Test {
public static void main(String[] args) {
//睡眠前 打印当前时间
System.out.println("睡眠前:"+ LocalDateTime.now());
try {
//睡眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//睡眠后 打印当前时间
System.out.println("睡眠后:"+ LocalDateTime.now());
}
}
结果:
优雅地停止线程:
1、停止正在运行的线程
thread.interrupt()
public class Test {
public static void main(String[] args) {
//创建一个线程
Thread thread = new Thread(()->{
while(true){
System.out.println("线程正在执行,时间:"+ LocalDateTime.now());
}
});
//启动线程
thread.start();
try {
//然后主线程睡眠一秒
Thread.sleep(1000);
//终止上面的thread线程
thread.interrupt();
//让主线程继续执行
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行以上代码,发现thread线程并没有停止,反正继续在执行。这是为啥呢?
原因:这个方法只是给thread线程一个标记,一个表示这个线程应该停止的标记。需要我根据这个标记去判断是否该停止这个线程。 如何去判断呢? 有两种方法:
isInterrupted() 方法为非静态方法,执行某个线程的这个方法,会返回一个boolean值,true表示这个线程被标记了,false表示未被标记。
interrupted() 方法,是一个静态方法,静态方法,哪个线程使用的这个方法,就作用于哪个线程,interrupted()方法获取线程的标记状态,如果状态是false返回false结果不做处理,如果状态是true,返回true结果,然后再将标记由true改为false。
两种方法,根据需求去使用。
执行以下代码,以isInterrupted()方法为例子,成功终止线程thread
public class Test {
public static void main(String[] args) {
//创建一个线程
Thread thread = new Thread(()->{
while(true){
System.out.println("线程正在执行,时间:"+ LocalDateTime.now());
Thread thread1 = Thread.currentThread();
if (thread1.isInterrupted()){
//结束循环
System.out.println("循环结束");
break;
}
}
});
//启动线程
thread.start();
try {
//然后主线程睡眠一秒
Thread.sleep(1000);
//终止上面的thread线程
thread.interrupt();
//让主线程继续执行
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2、停止休眠中的线程
情况如下:
public class Test {
public static void main(String[] args) {
//创建一个线程
Thread thread = new Thread(()->{
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//启动线程
thread.start();
try {
//然后主线程睡眠一秒,上面设置了thread线程睡眠5秒
Thread.sleep(1000);
//到这里,主线程睡眠完了,thread线程还有4秒需要睡眠,
//这是我们终止上面的thread线程
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
该异常是中断休眠中的线程引发的,报出这个异常,会让线程终止。上面代码 主线程去终止睡眠中的thread线程。
让线程放弃执行权:
Thread.yield() :该方法是一个静态方法,执行了该方法的线程,放弃本次执行机会,重新去抢CPU时间片。(就像:几个人抢一支笔写字,我抢到了笔,但是我不写字,我又扔回去,然后又继续去抢)
我们可以通过这个方法,完成当达成某种条件的时候,再让该线程往后执行,未达到条件时,就算抢到了CPU时间片我们也不去执行该线程,而是放弃执行的机会。
场景:
等待线程死亡join方法:
thread.join() 方法,这是一个成员方法,该线程执行到thread.join()方法,表示该线程需要等到thread线程执行完了或者终止结束了,才接着往下执行。
场景:
//定义线程1执行的任务
public class Task implements Runnable{
@Override
public void run() {
System.out.println("我是线程1");
System.out.println("这里执行了我的功能代码,睡3秒钟"+ LocalDateTime.now());
try {
Thread.sleep(3000);
System.out.println("我执行完了,我结束了"+LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//定义线程2执行的任务
public class Task2 implements Runnable{
private Thread thread;
public Task2(Thread thread){
this.thread = thread;
}
@Override
public void run() {
System.out.println("我是线程2,我开始执行我的任务");
System.out.println("等待线程1结束");
try {
//等待线程1结束
thread.join();
System.out.println("线程1结束了, 我也结束了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//开启两个线程
public class Test {
public static void main(String[] args) {
//创建线程1
Thread thread1 = new Thread(new Task());
//创建线程2 将线程1作为参数给到线程2,用于执行join方法。
Thread thread2 = new Thread(new Task2(thread1));
//开启线程1
thread1.start();
//开启线程2
thread2.start();
}
}
运行结果:
守护线程(后台线程)
thread.setDaemon(boolean no) 将线程设置为守护线程。
true:是守护线程 false:不是守护线程
thread.isDaemon 判断该线程是不是守护线程。
true:是守护线程 false:不是守护线程
守护线程的声明周期随着主线程的结束而结束。
使用:
public class DaemonTask implements Runnable{
@Override
public void run() {
try {
while(true){
//重复运行中。
Thread.sleep(100);
System.out.println("我是守护线程,守护中.....");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
//创建线程
Thread thread = new Thread(new DaemonTask());
//设置为守护线程
thread.setDaemon(true);
//开启线程
thread.start();
boolean daemon = thread.isDaemon();
System.out.println(daemon);
try {
//保持主线程不接受,持续执行中
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
判断线程是否存活:
thread.isAlive() 判断线程是否存活 true:存活 false:死亡
场景使用:
public class Task implements Runnable{
@Override
public void run() {
try {
System.out.println("线程开始了");
Thread.sleep(3000);
System.out.println("线程结束了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
//创建线程
Thread thread = new Thread(new Task());
//开启线程
thread.start();
try {
//查看线程存活状态
System.out.println("线程存活状态:"+thread.isAlive());
//保持主线程不接受,持续执行中
Thread.sleep(5000);
//查看线程存活状态
System.out.println("线程存活状态:"+thread.isAlive());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
线程组
线程组在平时开发中比较少用
二、线程锁
synchronized关键字
synchronized关键字可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。(只有拿到相应的锁的线程,才能进入到方法体内,执行代码,因为锁只有一把,所以每次只能有一个线程拿到锁)
使用形式:
同步代码块
synchronized(object){
//方法体
}
object参数可以是任意的对象,把这个对象当作是一把锁。
同步方法
访问修饰符 synchronized 返回值类型 方法名(参数类型 参数名称){
//方法体
}
静态同步方法
访问修饰符 static synchronized 返回值类型 方法名(参数类型 参数名称){
//方法体
}
三种方法在不同的场景使用,功能都一样,就是方法体内的代码 同一时刻只能有又一个线程执行。
使用场景:
/**
* 自定义一个任务类,模拟多个线程卖票场景
* */
public class Task implements Runnable{
//定义票数
private int quantity = 10;
@Override
public void run() {
while(quantity>0){
System.out.println("卖"+quantity+"号票");
quantity--;
}
}
}
public class Test {
public static void main(String[] args) {
//创建任务对象
Task task = new Task();
//创建线程,装载任务
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
Thread thread3 = new Thread(task);
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:(可能出现卖重复票的,也可能正常卖票,但是我们工作中,是不允许出现重复卖的情况,所以要避免掉。)
修改:在我们的Task类中添加synchronized关键字。
/**
* 自定义一个任务类,模拟多个线程卖票场景
* */
public class Task implements Runnable{
//定义票数
private int quantity = 10;
@Override
public void run() {
while(quantity>0){
synchronized (this){
System.out.println("卖"+quantity+"号票");
quantity--;
}
}
}
}
但是出现了新的问题:
出现原因:
解决方案,在synchronized内外双重判断。
/**
* 自定义一个任务类,模拟多个线程卖票场景
* */
public class Task implements Runnable{
//定义票数
private int quantity = 10;
@Override
public void run() {
while(quantity>0){
synchronized (this){
if (quantity>0){
System.out.println("卖"+quantity+"号票");
quantity--;
}
}
}
}
}
同步锁有哪些?
什么是同步锁
同步锁是为了保证每个线程都能正常执行原子不可更改操作,同步监听对象/同步锁/同步监听器/互斥锁的一个标记锁。
两种类型:1、对象类型 2、类类型
对象类型:
例如:Student student = new Student();
Object object = new Object();
this(指代当前对象)
synchronized修饰的成员方法 同步锁是该方法的实例对象
//创建同步锁对象
Object object = new Object();
//同步代码块
synchronized(object){
}
类类型:
例如:Student.class
Object.class
synchronized修饰的静态方法 同步锁是该方法的类
//同步代码块
synchronized(Object.class){
}
同一把锁
/**
* 定义一个加锁任务,模拟多个线程争夺同一把锁。
* */
public class Task implements Runnable{
@Override
public void run() {
//这里使用同步代码块为例子,给代码块加上类类型的锁
synchronized (Task.class){
try {
System.out.println("线程开始执行");
System.out.println("现在的时间是:"+LocalDateTime.now());
System.out.println("线程睡3秒");
Thread.sleep(3000);
System.out.println("线程醒了现在的时间是:"+LocalDateTime.now());
System.out.println("请下一个线程执行");
System.out.println("------------------------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
//定义两个Task任务实例
Task task1 = new Task();
//定义两个Thread实例
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task1);
//启动两个线程
thread1.start();
thread2.start();
}
}
运行结果:
死锁是如何产生的?
什么是死锁:
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或者系统产生了死锁,这些永远在互相等待的线程称为死锁。(两个或两个以上的线程争夺彼此的锁,造成阻塞,程序用永远处于阻塞状态)。
图: (开发中,我们就要注意,避免死锁的产生)
死锁产生的四个条件:
1、两个或两个以上的线程
2、两个或两个以上的锁
3、两个或两个以上的线程持有不用锁
4、持有不同锁的线程争夺对方的锁
代码实现:
/**
* 定义一个加锁任务,线程1一次拿object1 object2 锁。
* */
public class Task implements Runnable{
//定义两个成员变量,在这里也可以称为两把对象锁。
private Object object1;
private Object object2;
//构造方法
public Task(Object object1,Object object2){
this.object1 = object1;
this.object2 = object2;
}
@Override
public void run() {
//这里使用同步代码块为例子,加上object1 对象锁
synchronized (object1){
try {
System.out.println("Task:我拿到了object1锁了");
//这个线程去拿object2锁的之前,要确保object2名花有主了
//所以这里先睡一会,给足时间另外一个任务去拿object2锁。
Thread.sleep(1000);
System.out.println("Task:我想拿object2锁");
synchronized (object2){
System.out.println("Task:我拿到了object2锁了");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 定义一个加锁任务,线程2一次拿object2 object1 锁。
* */
public class Task2 implements Runnable{
//定义两个成员变量,在这里也可以称为两把对象锁。
private Object object1;
private Object object2;
//构造方法
public Task2(Object object1,Object object2){
this.object1 = object1;
this.object2 = object2;
}
@Override
public void run() {
//这里使用同步代码块为例子,去拿object2 对象锁
synchronized (object2){
System.out.println("Task2:我拿到了object2锁了");
System.out.println("Task2:我想拿object1锁");
synchronized (object1){
System.out.println("Task2:我拿到了object1锁了");
}
}
}
}
public class Test {
public static void main(String[] args) {
//创建两把对象锁
Object object1 = new Object();
Object object2 = new Object();
//定义Task任务实例,和Task2任务实例
Task task = new Task(object1,object2);
Task2 task2 = new Task2(object1,object2);
//定义两个Thread实例
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task2);
//启动线程
thread1.start();
thread2.start();
}
}
执行结果:
等待唤醒机制
1、等待(wait)
对象锁.wait() :使当前线程等待,只要被唤醒 ,使用这个方法前提,是当前线程必须拿到这个对象锁。
2、唤醒单个线程(notify)
对象锁.notify():唤醒对象锁上等待的单个线程,使用这个方法前提,是当前线程必须拿到这个对象锁。
3、唤醒所有线程(notifyAll)
对象锁.notifyAll():唤醒对象锁上等待的全部线程,使用这个方法前提,是当前线程必须拿到这个对象锁。
代码实现:
/**
* 定义一个加锁任务
* */
public class Task implements Runnable{
@Override
public void run() {
//同步代码块,this锁,指的是当前实例对象就是这把对象锁
synchronized (this){
try {
System.out.println("进入到代码块啦~~");
System.out.println("进入等待,等待被唤醒");
//进入到了等待状态
this.wait();
//需要被唤醒才能执行一下代码
System.out.println("线程被唤醒了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
//定义Task任务实例
Task task = new Task();
//创建Thread
Thread thread = new Thread(task);
//启动
thread.start();
try {
//主线程睡眠一段时间后再唤醒
Thread.sleep(3000);
System.out.println("主线程休眠结束,唤醒");
synchronized (task) {
task.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
wait和sleep的区别
两个方法都会让线程进入到阻塞等待状态,那么它们有什么不同呢?
线程间通讯 (wait、notify应用)
模拟一个场景,生产工厂和消费工厂,生产工厂负责生产产品,消费工厂负责消费产品,但是为了防止产品生产太多了,或者产品生产不够卖了,编写一个程序,使产品的库存保持再在一定范围。
//产品类
public class Data {
private int sum = 0;
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
}
//生产者任务
public class Producer implements Runnable {
private Data data;
public Producer(Data data){
this.data = data;
}
@Override
public void run() {
while(true){
synchronized (data){
//如果sum>=10 则停止生产
if (data.getSum()>=10){
try {
//唤醒消费线程
data.notify();
//停止生产线程,即当前线程
data.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//否则进行生产
int sum = data.getSum() + 1;
data.setSum(sum);
System.out.println("生产者生成了一个产品,当前产品数量为:"+sum);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
//消费者任务
public class Consumer implements Runnable{
private Data data;
public Consumer(Data data){
this.data = data;
}
@Override
public void run() {
while (true){
synchronized (data){
//如果sum<=0 则停止消费
if (data.getSum()<=0){
try {
//唤醒生产线程
data.notify();
//停止消费线程,即当前线程
data.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//否则进行消费
int sum = data.getSum() - 1;
data.setSum(sum);
System.out.println("消费者消费了一个产品,当前产品数量为:"+sum);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Test {
public static void main(String[] args) {
Data data = new Data();
Thread producerThread = new Thread(new Producer(data));
Thread consumerThread = new Thread(new Consumer(data));
producerThread.start();
consumerThread.start();
}
}
运行结果: 产品保持在0-10个
Lock(显式锁)
java5.0之后新增了一个同步锁对象,Lock。用于完善synchronized的不足。
Lock具备和synchronized一样的作用,可以实现线程同步,但是比synchronized更加强大,体现在更加灵活,可以自由地获取锁,释放锁。
非阻塞式获取锁:
使用 lock对象.tryLock() ,尝试获取锁,成功返回true,失败返回false。
中断等待锁的线程:
可中断 & 不可中断
不可中断 :
/**
* 定义一个加锁任务,验证是否可中断锁的线程
* */
public class Task implements Runnable{
//创建一把lock锁,可重入锁
private Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
try {
//睡眠3秒
Thread.sleep(3000);
//输出线程名
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
//创建任务
Task task = new Task();
//创建线程
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
//启动线程
thread1.start();
thread2.start();
//睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断线程
thread2.interrupt();
}
}
运行结果:
可中断:
Lock#lockInterruptibly
/**
* 定义一个加锁任务,验证是否可中断锁的线程
* */
public class Task implements Runnable{
//创建一把lock锁,可重入锁
private Lock lock = new ReentrantLock();
@Override
public void run() {
try {
//这个方法有异常抛出
lock.lockInterruptibly();
} catch (InterruptedException e) {
//抛出异常后执行这里的代码
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+"被中断");
return;
}
try {
//睡眠3秒
Thread.sleep(3000);
//输出线程名
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
//创建任务
Task task = new Task();
//创建线程
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
//启动线程
thread1.start();
thread2.start();
//睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断线程
thread2.interrupt();
}
}
运行结果:
Lock锁的等待唤醒机制:
那么Lock锁的等待和唤醒机制的相关方法有是什么呢?与synchronized唤醒机制对应的有啥哪些方法?
演示:
/**
* 定义一个任务,演示lock的等待和唤醒
* */
public class Task implements Runnable{
//创建一把lock锁和一个Condition做对象。这两个对象需要外部提供
private Lock lock;
private Condition condition;
public Task(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
lock.lock();
System.out.println("线程进行等待");
condition.await();
//线程释放后执行到输出线程名
System.out.println(Thread.currentThread().getName());
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//创建任务
Task task = new Task(lock,condition);
//创建线程
Thread thread1 = new Thread(task);
//启动线程
thread1.start();
//睡眠3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//需要获取锁
lock.lock();
//随机唤醒单个线程
condition.signal();
//唤醒全部线程
//condition.signalAll();
//释放锁
lock.unlock();
}
}
运行结果:
生产者和消费者(Condition应用)
实现:
//生产者任务
public class Producer implements Runnable {
private Data data;
public Producer(Data data){
this.data = data;
}
@Override
public void run() {
Lock lock = data.getLock();
while(true){
try {
lock.lock();
if(data.getSum()>=10) {
//消费者释放
data.getConsumerCondition().signalAll();
//表示不需要生产了
data.getProducerCondition().await();
}else {
//否则进行生产
int sum = data.getSum() + 1;
data.setSum(sum);
System.out.println("生产者生产了一个产品,当前产品数量为:" + sum);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
//消费者任务
public class Consumer implements Runnable{
private Data data;
public Consumer(Data data){
this.data = data;
}
@Override
public void run() {
Lock lock = data.getLock();
while(true){
try {
lock.lock();
if(data.getSum()<=0) {
//生产者释放
data.getProducerCondition().signalAll();
//表示不需要消费了
data.getConsumerCondition().await();
}else {
//否则进行消费
int sum = data.getSum() - 1;
data.setSum(sum);
System.out.println("消费者消费了一个产品,当前产品数量为:" + sum);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public class Test {
public static void main(String[] args) {
Data data = new Data();
Thread producerThread = new Thread(new Producer(data));
Thread consumerThread = new Thread(new Consumer(data));
producerThread.start();
consumerThread.start();
}
}
运行结果:
可重入锁和不可重入锁:
重入:重复进入同步作用域(重复使用同步锁)
不可重入锁定义:
public class UnReentranLock implements Lock {
/**
* 定义绑定的线程
*/
private Thread thread;
@Override
public void lock() {
synchronized (this){
//当已有线程拿到锁
while(thread!=null){
try {
//使当前线程等待
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
//绑定当前线程
this.thread = Thread.currentThread();
}
}
@Override
public void unlock() {
synchronized (this){
//当绑定的线程不是当前线程时
if(thread!=Thread.currentThread()){
return;
}
//解绑线程
thread = null;
//唤醒所有线程
notifyAll();
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
公平 & 非公平锁
公平 :每个线程获取锁的机会是平等的
非公平:每个线程获取锁的机会是不平等的。
1、非公平锁synchronized演示:
/**
* 定义一个任务
* */
public class Task implements Runnable{
@Override
public void run() {
while (true){
synchronized (this){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
}
public class Test {
public static void main(String[] args) {
//定义一个任务
Task task = new Task();
//创建线程
Thread thread0 = new Thread(task);
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
Thread thread3 = new Thread(task);
thread0.start();
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:当然其他线程也会拿到,只是拿到的几率比上一个拿到锁的线程几率小。
2、非公平锁ReentrantLock演示
/**
* 定义一个任务
* */
public class Task implements Runnable{
//参数不写 默认是false,即为非公平锁
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public class Test {
public static void main(String[] args) {
//定义一个任务
Task task = new Task();
//创建线程
Thread thread0 = new Thread(task);
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
Thread thread3 = new Thread(task);
thread0.start();
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
3、公平锁ReentrantLock演示
/**
* 定义一个任务
* */
public class Task implements Runnable{
//参数写 true 公平锁
private Lock lock = new ReentrantLock(true);
@Override
public void run() {
while (true){
lock.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public class Test {
public static void main(String[] args) {
//定义一个任务
Task task = new Task();
//创建线程
Thread thread0 = new Thread(task);
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
Thread thread3 = new Thread(task);
thread0.start();
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
4、判断是否是公平锁,但是只能用来ReentrantLock上,不能用在synchronized上
isFair() 方法
public class Test {
public static void main(String[] args) {
ReentrantLock lock1 = new ReentrantLock(false);
ReentrantLock lock2 = new ReentrantLock(true);
System.out.println(lock1.isFair());
System.out.println(lock2.isFair());
}
}
运行结果:
synchronized和Lock的区别:
读&写 锁
读锁演示:
/**
* 定义一个任务,读写锁
* */
public class Task implements Runnable{
//定义读写锁
private ReadWriteLock lock = new ReentrantReadWriteLock();
@Override
public void run() {
//夺取读锁
Lock lock = this.lock.readLock();
lock.lock();
try {
//睡眠3秒
Thread.sleep(3000);
//输出内容
System.out.println("时间:"+LocalDateTime.now()+"===="+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
//创建任务
Task task = new Task();
//创建线程
Thread thread0 = new Thread(task);
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
//启动线程
thread0.start();
thread1.start();
thread2.start();
}
}
运行结果:
写锁演示:
/**
* 定义一个任务,读写锁
* */
public class Task implements Runnable{
//定义读写锁
private ReadWriteLock lock = new ReentrantReadWriteLock();
@Override
public void run() {
//夺取读锁
Lock lock = this.lock.writeLock();
lock.lock();
try {
//睡眠3秒
Thread.sleep(3000);
//输出内容
System.out.println("时间:"+LocalDateTime.now()+"===="+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class Test {
public static void main(String[] args) {
//创建任务
Task task = new Task();
//创建线程
Thread thread0 = new Thread(task);
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
//启动线程
thread0.start();
thread1.start();
thread2.start();
}
}
运行结果:
读写锁使用时的互斥关系:
一个读写锁对象,如果需要给一个线程加读锁,会先去看有没有线程在使用它的写锁,如果有线程在使用它写锁,读锁会阻塞,等写锁关闭之后,才能开读锁。(相当于你要获取数据,但是发现有人在改数据,因此就算你现在获取到了数据,说不定别人跟着就改了数据,这样会让你拿到的数据不一定是最新的,所有宁愿等一下吧,等改数据的人改完了数据,我再去读,确保数据是最新的)
如果一个需要给一个线程加写锁,却发现有其他线程用了读锁,那么这个线程也会阻塞,等待读数据操作完毕,读锁释放,然后才给线程加写锁。
总结:先读后读读(不阻塞) 先读后写(阻塞) 先写后读(阻塞) 先写后写(阻塞)。
LockSupport:等待唤醒工具类
演示:
/**
* 定义一个任务
* */
public class Task implements Runnable{
@Override
public void run() {
System.out.println("等待前");
LockSupport.park();
System.out.println("唤醒了");
}
}
public class Test {
public static void main(String[] args) {
//定义任务
Task task = new Task();
//创建线程
Thread thread = new Thread(task);
//启动线程
thread.start();
try {
//睡眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒指定的线程
LockSupport.unpark(thread);
}
}
运行结果:
线程状态以及生命周期:
线程的状态一共有六种:
NEW(新建)、RUNNABLE(就绪)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(记时等待)、TERMINATED(死亡)
ThreadLocal(线程本地变量):
存储在 当前线程 里的变量。 每个线程中的ThreadLocal是分开的。
结构:
InheritableThreadLocal<T>(可继承的线程本地变量)
这里的继承指的是 在线程A种创建了线程B,则线程B可获取到线程A的InheritableThreadLocal