Java多线程常用方法梳理

1 中断

1.1 interrupt

将线程的状态设置为中断,并不是直接停止非阻塞的运行中的线程。当线程进入该中断状态,且被Thread.join和Thread.sleep,Object.wait阻塞时该线程立即会抛出InterruptedException中断异常,从而提早终结阻塞状态。相反如果线程没有被阻塞,调用此方法时将不起作用,直到遇到wait,sleep,join等场景才会抛出InterruptedException中断异常停止运行中的线程。(如同设置一个前哨,如果遇到阻塞马上中断)

// 使用方式
Thread.currentThread().interrupt();

1.2 interrupted

// 源码
public static boolean interrupted() {
    //测试某些线程是否已中断。中断状态是否根据传递的ClearInterrupted值重置。
    return currentThread().isInterrupted(true);
}

测试当前线程是否已中断。此方法将清除线程的中断状态。换言之,如果连续两次调用此方法,则第二次调用将返回false(除非当前线程在第一次调用清除其中断状态之后且在第二次呼叫检查它之前再次中断)。由于线程在中断时不活动而忽略的线程中断将由返回false的方法反映。

1.3 isInterrupted

// 源码
public boolean isInterrupted() {
    // 测试某些线程是否已中断。中断状态是否根据传递的ClearInterrupted值重置。
    return isInterrupted(false);
}

测试此线程是否已中断。线程的中断状态不受此方法的影响。由于线程在中断时不活动而忽略的线程中断将由返回false的方法反映。


1.4 一段代码搞清interrupt、interrupted、isInterrupted的关系

@Test
void interrupted() {
    // 设置中断标志位
    Thread.currentThread().interrupt();
    // 判断当前线程是否处于中断状态 -- true
    System.out.println(Thread.currentThread().isInterrupted());
    // 清理中断标志位(返回是否清理成功)-- true
    System.out.println(Thread.interrupted());
    // 再次清理 -- false
    System.out.println(Thread.interrupted());
    // 判断当前线程是否处于中断状态 -- false
    System.out.println(Thread.currentThread().isInterrupted());
}

2 sleep

阻塞当前正在运行的线程一段时间(该时间可以指定),它不会释放以获取到的锁。当阻塞时间到后会继续执行当前线程的后续逻辑。

在无锁状态下也可以直接调用此方法。实现线程的阻塞。

// 每隔1秒打印一次时间
@Test
void sleep() throws InterruptedException {
    for (int i = 0; i < 10; i++) {
        DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date();
        String format = formatter.format(date);
        System.out.println(format);
        // 阻塞线程1s
        TimeUnit.SECONDS.sleep(1);
    }
}

3 wait

wait会阻塞当前线程,并且不会释放当前持有的锁。它与sleep不同的是,该方法必须通过锁对象来调用,不可无锁调用。wait()与wait(long timeout)的区别在于无参方法必须通过notify()或notifyAll()来唤醒当前等待线程,而有参方法可以等待指定的时间后自动唤醒等待线程去争夺锁。

@Test
public void testWait() {
    // 对象
    final BigDecimal lock = new BigDecimal(10);
    Thread threadA = new Thread(() -> {
        synchronized (lock) {
            System.out.println("我被线程A正在执行");
            try {
                // 该线程会放弃对象锁,该线程会被挂起
                lock.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("如果我被打印,说明我被唤醒了");
        }
    });

    // 启动线程A
    threadA.start();
    
    Thread threadB = new Thread(() -> {
        // 线程B争夺该对象的锁
        synchronized(lock) {
            System.out.println("我被线程B正在执行");
            // 线程B会唤醒该对象的
            lock.notify();
        }
    });

    threadB.start();
}

运行结果


例:模拟闭锁,主线程等待10个子线程执行完毕之后,主线程继续向下执行。且主线程最多等待10秒,若子线程在10秒内未完成将自动向下执行。

/**
 * 模拟主线程执行逻辑
 */
public class MyWait {
    static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        System.out.println("进入主线程。。。。");
        
        // 计数器
        AtomicInteger count = new AtomicInteger(0);

        // 启动10个子线程
        MyWaitSon myWaitSon = new MyWaitSon(lock, count);
        for (int i = 1; i <= 10; i++) {
            Thread sonThread = new Thread(myWaitSon, "son"+i);
            sonThread.start();
        }


        // 主线程挂起,且最多等待10s
        synchronized (lock) {
            lock.wait(10000L);
            System.out.println("主线程继续启动。。。。。");
        }

    }
}
/**
 * 模拟子线程执行逻辑
 */
public class MyWaitSon implements Runnable {

    final Object lock;

    final AtomicInteger count;

    public MyWaitSon(Object lock, AtomicInteger count) {
        this.lock = lock;
        this.count = count;
    }

    @Override
    public void run() {
        System.out.println("子线程启动了");
        synchronized (lock) {
            System.out.println("当前线程为:"+Thread.currentThread().getName()+",count:"+count.incrementAndGet());
            // 通过计数器到达10个后唤醒主线程
            if (count.get() == 10) {
                lock.notify();
                System.out.println("子线程唤醒主线程");
            }
        }

    }
}
进入主线程。。。。
子线程启动了
子线程启动了
当前线程为:son1,count:1
子线程启动了
当前线程为:son2,count:2
子线程启动了
当前线程为:son3,count:3
子线程启动了
当前线程为:son4,count:4
子线程启动了
当前线程为:son5,count:5
当前线程为:son6,count:6
子线程启动了
当前线程为:son7,count:7
子线程启动了
当前线程为:son8,count:8
子线程启动了
当前线程为:son9,count:9
子线程启动了
当前线程为:son10,count:10
子线程唤醒主线程
主线程继续启动。。。。。

4 join

在多线程环境下,需要等待某一个线程执行完毕后才能继续向下执行逻辑,那么这种场景就可以使用join。

为了更加形象的说明,我们看图说话,如上图所示有三个线程,执行完毕的时间分别是5s、3s、10s。

当运行到第3s时,Thread2执行完毕,Thread1、3正在执行。

当运行到第5s时,Thread1执行完毕,Thread2等待了2s,Thread3正在执行。

当运行到第10s时,Thread3执行完毕,Thread1等待了5s,Thread2等待了7s。

当主线程从第10s之后再开始执行后续逻辑。那么这种场景就适合join来实现。

示例代码如下

public static void main(String[] args) throws InterruptedException {

        // 线程1
        Thread thread1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
                System.out.println("线程1执行完毕");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "thread1");


        // 线程2
        Thread thread2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println("线程2执行完毕");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "thread2");

        // 线程3
        Thread thread3 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(10);
                System.out.println("线程3执行完毕");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "thread3");

        // 主线程准备启动三个子线程
        thread1.start();
        thread2.start();
        thread3.start();

        // 主线程等待thread1、2、3执行结束
        thread1.join();
        thread2.join();
        thread3.join();

        // 主线程继续执行
        System.out.println("主线程执行结束");
    }
线程2执行完毕
线程1执行完毕
线程3执行完毕
主线程执行结束

5 yield

yieId在实际生产中使用不多,它的作用是将当前线程CPU的执行权让出给其它线程。

例如当前线程正常循环打印0-10000的耗时要远远小于使用yieId方法打印的耗时

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                System.out.println(i);
                //使用yield方法
//                Thread.yield();
            }
        });

        long startTime = System.currentTimeMillis();
        thread.start();
        thread.join();
        System.out.println("总耗时:" + (System.currentTimeMillis() - startTime));

    }

未使用yield方法耗时70毫米左右,当使用yield方法后会达到100毫秒以上。


收藏吃灰去吧

展开阅读全文

页面更新:2024-04-21

标签:方法   线程   主线   个子   源码   对象   状态   常用   结束   测试   时间

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top