由于在我现阶段的学习过程中,接触的大都是单线程程序,所以对该板块的知识点总是差点味道。不过这些都是基础,很多东西最开始是不需要明白为什么的!!!
文章目录
-
- 1、线程实现
-
- 1.1、继承Thread类(重点)
- 1.2、实现Runnable接口(重点)
- 1.3、实现Callable接口(了解)
- 1.4、多线程的底层
- 2、Lambda表达式
-
- 2.1、学会五种类的定义
- 2.2、Lambda表达式的简化
- 3、线程状态(重要)
-
- 3.1、线程的五种状态
- 3.2、线程相关的方法
- 4、线程同步
-
- 4.1、synchronized(Obj){}
- 4.2、Lock
- 5、线程通信问题
- 6、思维导图
1、线程实现
1.1、继承Thread类(重点)
实现过程:
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
1.2、实现Runnable接口(重点)
实现过程:
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
两种方式的比较:
继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start(
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
1.3、实现Callable接口(了解)
实现过程:
- 实现Callable接口,需要返回值类型
- 重写call()方法,需要抛出异常
- 创建目标对象
- 创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(t1);
- 获取结果:boolean r1 = result1.get();
- 关闭服务:ser.shutdownNow();
1.4、多线程的底层
多线程的底层实现是静态代理(复习设计模式的时候再复习该知识点)
2、Lambda表达式
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
2.1、学会五种类的定义
外部类:与主启动类同级下定义的一个新的类
静态内部类:在一个class的内部定义,并且使用了static修饰了类(与方法的成员变量同级)
局部内部类:在方法的内部定义,局部,可以理解为与局部变量同级的类
匿名内部类:不在出现class关键字,直接匿名,采用new接口的方式,直接书写方法体
Lambda表达式:类似于匿名内部类,直接将方法体,写成对应的Lambda形式
2.2、Lambda表达式的简化
1、常规情况
(int a) -> { System.out.println(“a是:” + a); };
2、可以省略参数类型
(a) -> { System.out.println(“a是:” + a); };
3、如果参数只有一个可以省略参数和括号
a -> { System.out.println(“a是:” + a); };
4、如果方法体只有一行,可以简写成这样
a -> System.out.println(“a是:” + a);
个人建议,良好的代码可读性也是衡量一个代码质量的标准,一味的彰显代码的精简便捷是不可取的!
3、线程状态(重要)
3.1、线程的五种状态
- 创建状态:线程一旦创建就进入新生状态
- 就绪状态:调用start()方法时,会进入该状态
- 阻塞状态:使用sleep、wait或同步锁时,进入该状态。该状态恢复以后会进入就绪状态
- 运行状态:线程进入运行状态的时候才是运行状态
- 死亡状态:线程中断或者结束,无法再次启动
3.2、线程相关的方法
1、停止线程
不推荐使用JDK的方法用来停止线程。我们可以设置一个标志位,当线程运行到某个状态的时候,修改对应的状态位,然后停止线程
3、线程休眠
Thread.sleep形式
让线程进入阻塞状态,即睡眠指定的时间
4、礼让线程
Thread.yield()形式
让线程进行礼让。重新分配CPU的调度,即抢到资源的线程放弃机会,重新与其他线程一起竞争。所以礼让不一定成功。
5、强制执行线程
new Thread ().join()形式
当程序执行到了我们指定的状态时,强制让某一个线程执行某一种状态
6、观测线程状态
Thread.State state = thread.getState();
System.out.println(state);
使用该方法来接受线程的状态
线程的状态:
- new:尚未启动的线程处于此状态
- runnable:在java虚拟机中执行的线程处于此状态
- blocked:被阻塞等待监视器锁定的线程处于此状态
- waiting:正在等到另一个线程执行特定动作的线程处于此状态
- timed_waiting:正在等到另一个线程执行动作达到指定等待时间的线程处于此状态
- terminated:已推出线程处于此状态
7、线程的优先级
getPriority().setPriority(int xxx)
线程优先级的范围为1~10
可以在线程启动前设置优先级,优先级越高就越先执行(只是优先级高,并不一定真的就是先执行)
8、守护线程
thread.setDaemon(true);
将线程设置为守护线程以后,守护线程只会在用户进程结束以后再结束。比如我们的计算机操作日志,只要我们有一定的操作就会运行,当我们关闭电脑没有任何操作的时候守护线程也就结束。
4、线程同步
在我们的多线程环境下,会出现两个线程抢占同一个资源的情况,继而会导致信息的冲突。为了避免多线程环境下的这种资源冲突,多线程中引入了线程同步这一个概念。是想方式是使用锁机制。
即当第一个线程拿到资源的时候,就带着一把锁,用来向其他的线程表示,该资源已经有人了。当线程处理完该线程以后就释放这把锁,然后其他的线程就能够获取对应的资源了。
这里面有点需要记忆,第一就是使用这样的锁机制能够保证线程安全,第二就是使用了锁机制会带来效率上的降低。
4.1、synchronized(Obj){}
使用synchronized关键字,可以对指定的方法或者指定的代码块进行锁定。
其实在很多代码中都能看到synchronized关键字,比如之前的Vector集合。
直接添加在对应的方法上即可
加了synchronized关键字以后代码的执行过程:
- 第一个线程访问时,锁定同步监视器,执行对应的代码
- 第二个线程访问时,发现同步监视器被锁定了,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问时,发现同步监视器锁消消失,然后自己对同步监视器进行锁定,然后访问
补充
死锁产生的必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:与个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾想接的循环等待资源关系
只要破坏以上任意一个就可以导致死锁
设想一下,你需要对方手里的资源,对方需要你手里的资源,那不就发生冲突了?
4.2、Lock
private final ReentrantLock lock = new ReentrantLock();
lock.lock();
…
lock.unlock();
Lock是显式锁,怎么理解显式锁呢?
我理解的显式定义,再显式的释放
Lock锁的引入使得程序锁机制更加的灵活,其lock锁的功能比synchronized更加强大
使用显式锁的时候,建议是同try…catch…finally语句,将释放锁语句放在finally语句块中,避免没有释放锁引发一系列的冲突。
Lock锁是JDK的一个接口功能,synchronized是Java关键字,是基于JVM层面实现的。简单来说就是,synchronized是java语言最开始设计时候为了解决并发特性所以使用的锁机制,lock锁是后期开发人员想使用更加丰富的锁功功能引入的。
5、线程通信问题
线程与线程之间的通信可以使用两种方法
管程法:
生产者将生产好的数据放入指定的数据缓冲区,然后消费者去缓冲区里面拿数据就行了。生产者生产了产品,放入指定的缓冲区中,消费者对缓冲区进行判定,有就拿取数据,没有就通知生产者生产
信号灯法:
设置一个标志位,用来充当信号灯。生产者生产了商品,点亮信号灯,消费者看到亮着的灯就进行消费。
线程池概念:
- 由于我们经常的创建和销毁特别大的资源,对于性能损耗十分的巨大。所以引入了线程池,可以类比于java的常量池,数据库的数据库连接池
- 当我们创建了多个线程,就将线程放入线程池。当我们需要使用对应线程的时候,就去对应的线程池中获取,减小了系统在频繁创建销毁活成中的性能损耗