Thread->sleep、wait、join使用

Thread sleep、wait、join使用

这里先介绍join,然后把两个有关联的sleep和wait一起介绍.

join()

这个方法比较好理解,当前线程等待指定线程终止后在执行,将两个交替执行的线程合并为顺序执行的线程.比如在B线程中调用A线程的join()方法,直到A线程执行完毕,B线程才会继续执行.

api有两个

  • void join()

    当前线程等待调用这个方法的线程终止后再执行.

  • void join(long millis)

    当前线程等待millis毫秒后,不管调用这个方法的线程是否终止,当前线程将变成可运行状态

这里我们不妨看一下join的源码,join()中调用的join(long millis)所以这里我只贴出join(long millis)方法

可以发现他先调用isAlive()方法判断该线程是否存活,然后通过Object.wait(0)方法使当前线程阻塞(客官别急wait后面会讲到).

通过注释可以发现线程存活指的是线程存活并且调用start()了.接下来我们通过一个例子体会下join的使用

public class MyClass {

    public static void main(String[] args) {

        System.out.println("main start");

        Thread t = new Thread(new MyRunnable());
        t.start();

        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main end");

    }

    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            System.out.println("other start");

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("other end");
        }
    }

}

运行结果.

先打印了main start,然后由于我们调用了t.join()于是主线程等待other线程执行完毕后在继续执行,所以打印出other start,other end,最后打印出main end.

接下来我们将join()方法替换成join(1000)

运行结果.

这里应该没啥问题就不多说了.

sleep()

Thread静态方法,强制当前线程休眠.当休眠时间到期后恢复到可运行状态.不一定会立即执行,具体取决于线程调度器.

下面一个很简单的栗子

public static void main(String[] args) {

    System.out.println("main start");

    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("main end");

}

结果就是先打印main start,然后休眠3秒后打印,main end.

我主要想说的是下面这个问题,sleep()在Synchronized块中调用,线程虽然休眠了,但是对象的锁并未释放,其他线程无法访问这个对象.下面一个例子展示这个过程.

public static void main(String[] args) {

        MyRunnable r = new MyRunnable();
        Thread t = new Thread(r);
        t.start();
        r.method1();
    }

    private static class MyRunnable implements Runnable {

        @Override
        public synchronized void run() {
            System.out.println("other start");

            System.out.println("other end");
        }

        public synchronized void  method1(){
            System.out.println("main start");

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("main end");
        }
    }

结果如下

可以发现即使method1()中调用了Thread.sleep(),但对象锁并未释放,所以休眠3秒后依次执行main end->other start->other end.这里可以留意下后面在wait()方法的时候我们会做个对比.

wait()

wait()一般都是配合notify()和notifyAll()一起使用,并且他们都是java.lang.Object的方法.

具体细节如下

  • wait()方法是使当前线程,进入到一个和对象相关的等待池中,同时失去对象的锁,其他线程可以访问.

  • wait()方法通过notify(),notifyAll(),或者指定超时时间来唤醒当前等待池中线程.

  • wait()必须用在synchronized代码块中,否则会抛出异常.

下面通过一个栗子检验下

public static void main(String[] args) {

    MyRunnable r = new MyRunnable();
    Thread t = new Thread(r);
    t.start();
    r.method1();
}

private static class MyRunnable implements Runnable {

    @Override
    public synchronized void run() {
        System.out.println("other start");

        System.out.println("other end");

        notifyAll();
    }

    public synchronized void method1() {
        System.out.println("main start");

        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main end");
    }
}

运行结果如下

由于线程创建需要时间,所以先执行了method1()方法打印出main start,然后调用wait()方法后该线程进入等待状态,并且释放该对象锁,于是run()运行打印出other start和other end,然后调用notifyAll()后wait的线程被唤醒打印出main end.

这里细说下notify()与notifyAll(),

  • notify()

    举个例子如果线程A1,A2,A3都obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)

  • notifyAll()

    obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行.

sleep()与wait()区别

  • sleep()

    1. sleep是Thread静态方法,因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁).
  • wait()

    1. wait()是Object类里的方法,当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去了对象的锁,其他线程可以访问.

    2. wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程

    3. wait()必须放在synchronized block中,否则会在运行时抛出”java.lang.IllegalMonitorStateException”异常.

一句话总结最大区别:sleep()睡眠时,保持对象锁,仍然占有该锁;而wait()睡眠时,释放对象锁.

总结

  1. join将并行运行的线程改为串行.

  2. sleep使当前线程休眠,目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会.

  3. 在某种条件下该线程wait(),当条件成熟后通过notify()唤醒该线程继续运行.

#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <windows.h> typedef struct QueueNode { int id; struct QueueNode* next; }QueueNode; typedef struct TaskQueue { QueueNode* front; QueueNode* rear; }TaskQueue; int InitQueue(TaskQueue* Qp) { Qp->rear = Qp->front = (QueueNode*)malloc(sizeof(QueueNode)); Qp->front->id = 2018; Qp->front->next = NULL; return 1; } int EnQueue(TaskQueue* Qp, int e) { QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode)); if (newnode == NULL) return 0; newnode->id = e; newnode->next = NULL; Qp->rear->next = newnode; Qp->rear = newnode; return 1; } int DeQueue(TaskQueue* Qp, int* ep, int threadID) { QueueNode* deletenode; if (Qp->rear == Qp->front) return 0; deletenode = Qp->front->next; if (deletenode == NULL) { return 0; } *ep = deletenode->id; Qp->front->next = deletenode->next; free(deletenode); return 1; } int GetNextTask(); int thread_count, finished = 0; pthread_mutex_t mutex, mutex2; pthread_cond_t cond; void* task(void* rank); TaskQueue Q; int main() { int n; InitQueue(&Q); pthread_t* thread_handles; thread_count = 8; thread_handles = malloc(thread_count * sizeof(pthread_t)); pthread_mutex_init(&mutex, NULL); pthread_mutex_init(&mutex2, NULL); pthread_cond_init(&cond, NULL); printf("Task Number:"); scanf_s("%d", &n); for (int i = 0; i < thread_count; i++) pthread_create(&thread_handles[i], NULL, task, (void*)i); for (int i = 0; i < n; i++) { pthread_mutex_lock(&mutex2); EnQueue(&Q, i); Sleep(1); pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex2); } finished = 1; pthread_cond_broadcast(&cond); for (int i = 0; i < thread_count; i++) pthread_join(thread_handles[i], NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); free(thread_handles); return 0; } void* task(void* rank) { int my_rank = (long)rank; int my_task; QueueNode** p = &(Q.front->next); while (1) { pthread_mutex_lock(&mutex2); if (finished) { if (*p == NULL) { pthread_mutex_unlock(&mutex2); break; } DeQueue(&Q, &my_task, my_rank); pthread_mutex_unlock(&mutex2); printf("From thread %ld: Task no.%-3d result->%5d\n", my_rank, my_task, my_task * 10); } else { while(pthread_cond_wait(&cond, &mutex2)!=0); //pthread_mutex_lock(&mutex2); DeQueue(&Q, &my_task, my_rank); pthread_mutex_unlock(&mutex2); Sleep(2); printf("From thread %ld: Task no.%-3d result->%5d\n", my_rank, my_task, my_task * 10); } } } 该代码在运行中可能遇到什么问题
最新发布
07-14
在这段代码中,可能会遇到以下问题: 1. 并发访问问题:由于多个线程同时访问任务队列,可能会导致竞争条件和数据不一致的问题。例如,当多个线程同时执行EnQueue或DeQueue操作时,可能会导致内存泄漏或无法正确删除节点。 2. 线程同步问题:在条件变量的使用中,需要确保正确的等待和唤醒顺序。在task函数中,使用了条件变量和互斥锁来同步线程的执行,但是可能存在死锁或唤醒丢失的情况。 3. 内存泄漏问题:在EnQueue函数中,每次插入新节点时都会分配内存,但没有释放已插入节点的内存。这可能导致内存泄漏。 4. 跨平台问题:代码中使用了Windows特定的Sleep函数,因此在Linux环境下可能无法直接编译和运行。可能需要替换为适用于Linux环境的等效函数。 为了解决这些问题,建议进行以下改进: 1. 使用互斥锁来保护共享资源的访问,例如对任务队列的操作应该在加锁的情况下进行。 2. 确保条件变量的正确使用,包括等待和唤醒的顺序以及互斥锁的正确解锁和加锁。 3. 在EnQueue函数中,释放已插入节点的内存,避免内存泄漏。 4. 针对跨平台问题,可以使用与平台无关的替代函数,例如使用pthread库提供的等效函数来替代Windows特定的Sleep函数。 5. 进行错误处理和边界情况的检查,例如在DeQueue操作中,需要检查队列是否为空,以避免空指针错误。 通过以上改进,可以提高代码的健壮性和可移植性,减少潜在的错误和异常情况。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值