静态代理
静态代理:真实对象和代理对象实现同一个接口
代理对象用于代理真实对象(真实对象作为参数传入代理对象中),代理对象实现真实对象以外的方法。
静态代理的优点:
- 代理对象可以做真实对象方法以外的事
- 真实对象可以专注于实现自己的方法
静态代理实现
- 定义接口
interface Marry{
void marry();
}
- 真实对象实现接口
class Man implements Marry{
@Override
public void marry() {
System.out.println("happy");
}
}
- 代理对象实现接口,代理对象以真实对象作为参数,并实现与其相关的更多方法
class WeddingCompany implements Marry{
private Marry man;
public WeddingCompany(Marry man) {
this.man = man;
}
@Override
public void marry() {
before();
this.man.marry(); // 真实对象的方法
after();
}
private void before() {
System.out.println("布置");
}
private void after() {
System.out.println("收尾");
}
}
- 实例化对象
Man man = new Man(); // 真实对象
WeddingCompany weddingCompany = new WeddingCompany(man); // 代理对象
weddingCompany.marry();
静态代理实现多线程
new Thread(() -> System.out.println("Runnable接口")).start();
线程状态
线程有五大状态
不同线程状态对应的代码
线程相关的方法
方法 | 说明 |
---|---|
setPrioiority(int newPriority) | 更改程序的优先级 |
static void sleep(long millis) | 在指定的毫秒数内,让当前执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他的线程 |
void interrupt() | 中断线程,不建议使用 |
boolean isAlive() | 测试线程是否处于活动状态 |
停止线程 stop
不推荐的停止线程方法:
- 使用JDK提供的停止方法:
stop()
、destroy()
等。
推荐的停止线程方法:
- 让线程自己停止,设置循环次数,不建议写死循环
- 建议使用标志位作为停止变量,当flag==false时,终止线程。
public class Thread02 implements Runnable{
// 1. 线程中定义停止变量
private Boolean flag = true;
@Override
public void run() {
// 2. 线程体中使用flag标志位
while (flag){
System.out.println("Running...");
}
}
// 3. 对外提供方法改变标志位
public void stop() {
this.flag = false;
}
}
线程休眠 sleep
static void sleep(long millis)
方法:在指定的毫秒(1000ms = 1s)数内,让当前执行的线程休眠
- sleep方法存在异常InterruptedException异常
- sleep时间到达后,线程进入就绪态
- sleep可以模拟网络延迟,倒计时等
- 每个对象都有一个锁,sleep不会释放锁
线程休眠demo:
模拟倒计时
public static void tenDown() {
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("发射!!!");
}
打印当前时间
public static void printTime() {
Date time;
for (int i = 0; i < 10; i++) {
time = new Date(System.currentTimeMillis()); // 获取系统当前时间
System.out.println(new SimpleDateFormat("HH:mm:ss").format(time)); // 简易格式化输出时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程礼让 yield
Thread.yield()
方法,礼让线程:让当前线程暂停执行,但不阻塞
- 让线程从运行态转为就绪态
- 让CPU重新调度,但仍有可能调度当前线程
线程强制执行 join
join合并线程,其他线程被阻塞,待程序执行完后,才会执行其他线程。
不建议使用,会阻塞其他线程。
public static void main(String[] args) throws InterruptedException {
Runnable t = ()->{
for (int i = 0; i < 300; i++) {
System.out.println("thread " + i);
}
};
Thread thread = new Thread(t);
thread.start();
for (int i = 0; i < 80; i++) {
if (i == 60){
thread.join(); // join:线程插队,当前main方法阻塞
}
System.out.println("main " + i);
}
}
线程状态监测
Thread.getState()
方法可以获取线程状态,线程有以下状态:
状态 | 说明 |
---|---|
NEW | 尚未启动的线程状态 |
RUNNABLE | 在Java虚拟机中正在执行的线程状态 |
BLOCKED | 被阻塞等待监视器锁定的线程状态 |
WAITING | 等待另一线程执行特定动作的线程状态 |
TIMED_WAITING | 等待另一个线程到达指定等待时间的线程状态 |
TERMINATED | 已退出的线程状态,退出后的线程不能再启动 |
这些状态是虚拟机状态,不反映任何OS状态
线程优先级 priority
Java提供一个线程调度器来监控程序中启动后进入就绪态的所有线程,线程调度器根据线程优先级来调度线程。
- 线程优先级用数字表示,范围从1~10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
- 通过
getPriority()
方法获取优先级 - 通过
setPriority(int X)
方法改变优先级
优先级低只是被调度的概率降低,仍有可能被调度。
守护线程 deamon
线程分为用户线程(如main线程)和守护线程(如gc线程)
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不需要等待守护线程执行完毕,如:后台操作日志,监控内存,垃圾回收等待...
可以通过thread.setDeamon(Boolean on)
方法,将线程设置为守护线程,布尔值默认为false。
线程同步
并发:同一个对象被多个线程同时操作
处理多线程问题,多个线程访问同一对象,且某些线程还会修改对象,就需要线程同步。
其本质是一种等待机制,多个需要同时访问此对象的线程进入该对象的等待池,形成队列,等待前面的进程使用完毕,下一个线程再使用。
队列 和 锁
线程同步的必要条件是要有队列和锁
由于同一进程的多个线程共享同一块存储空间,所以会有访问冲突,为了保证数据在被访问时的正确性,加入锁机制synchronized。
当一个线程获得对象的排它锁,独占资源,其余线程必须等待,使用后释放锁即可。
锁机制存在以下问题:
- 一个线程持有锁会导致其余线程挂起
- 在多线程中,加锁,释放锁会导致较多的上下文切换和调度延时,引起性能问题
- 若一个高优先级的线程等待低优先级的线程释放锁,会导致优先级倒置,引起性能问题
同步方法
- 通过private关键字保证数据对象只能通过方法访问,然后针对方法提出锁机制,通过synchronized关键字实现两种用法:synchronized方法和synchronized块
synchronized方法
synchronized是隐式的定义同步锁。
同步方法:public synchronized void method(int args){}
- synchronized方法控制对对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得该锁才能执行
- 方法一旦执行就独占该锁,知道方法返回,释放锁,后面的线程才能获得锁,继续执行
- 缺点:讲一个大方法设为synchronized会影响效率
synchronized块
同步块:synchronized(Obj) {}
Obj称为同步监视器
- Obj可以是任何对象,推荐用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,同步方法中this就是同步监视器,即对象本身或class
- 同步监视器的执行
- 第一个线程访问,锁定同步监视器,执行内部代码
- 第二个线程访问,同步监视器被锁,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器未锁定,锁定并访问
同步监视器
同步监视器,就是临界资源
- synchronized方法的同步监视器是方法所在的this对象,即对象本身或class。
- synchronized块的同步监视器由自己决定。
哪个类的属性需要同步,就锁哪个类的对象!
锁 lock
JDK5.0之后,Java提供了更好的线程同步机制:通过显式定义同步锁对象,来实现同步,使用lock对象作为同步锁。
JUC中的locks.Lock接口是控制多个线程对共享资源进行访问的工具,实现了互斥访问,线程访问共享资源前要先获得Lock对象。
ReentrantLock类(可重入锁)实现了Lock接口,实现类与synchronized相同的并发性和内存语义,而且可以显示地加锁、释放锁,所以更加常用。
ReentrantLock的使用:
// 定义Lock锁,一般用private修饰保证安全,用final修饰保证常量
private final ReentrantLock lock = new ReentrantLock();
lock.lock(); // 上锁
lock.unlock(); // 解锁
ReentrantLock使用规范:
lock.lock();
try {
// 可能引发异常的部分代码
} finally {
lock.unlock();
}
lock方法没有指定抛出异常,不应包含到try-catch块中。
unlock放到finally中保证业务无论异常与否,最终都要解除锁,释放资源避免死锁。
synchronized与Lock对比
- Lock是显示锁(手动关闭和开启锁);synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁时,JVM调度线程的花费较小,性能更好;且具有更好的拓展性(提供多种子类,ReentrantLock只是其中之一)
- 推荐使用顺序:Lock > synchronized块(已经进入方法,分配了相应资源) > synchronized方法(在方法体之外)
死锁
多个线程各自占有一些共享资源,且互相等待其他线程占用的资源才能继续运行,双方都停止运行。
某一同步块,同时有两个以上对象的锁时,可能发生死锁;即同步块内部嵌套同步块(表示持有某一资源的同时,需求另一资源),可能导致死锁。
死锁产生的条件:
- 互斥:一个资源一次只能被一个线程访问
- 请求并保持:一个线程因请求资源而阻塞,同时不释放已经持有的资源
- 不剥夺:对于已获得的资源,在使用完成之前,不强行剥夺
- 循环等待:若干进程间形成一种头尾相接的循环等待资源的关系。
解决死锁只需要破坏四个条件中的任意一个或多个即可。
线程sleep不会释放已持有的资源。