Java.Thread02

静态代理

静态代理:真实对象和代理对象实现同一个接口
代理对象用于代理真实对象(真实对象作为参数传入代理对象中),代理对象实现真实对象以外的方法。

静态代理的优点:

  1. 代理对象可以做真实对象方法以外的事
  2. 真实对象可以专注于实现自己的方法

静态代理实现

  1. 定义接口
interface Marry{
    void marry();
}
  1. 真实对象实现接口
class Man implements Marry{
    @Override
    public void marry() {
        System.out.println("happy");
    }
}
  1. 代理对象实现接口,代理对象以真实对象作为参数,并实现与其相关的更多方法
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("收尾");
    }
}
  1. 实例化对象
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
  • 同步监视器的执行
  1. 第一个线程访问,锁定同步监视器,执行内部代码
  2. 第二个线程访问,同步监视器被锁,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器未锁定,锁定并访问

同步监视器

同步监视器,就是临界资源

  • 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不会释放已持有的资源。

tag(s):
show comments · back · home