天天看点

java并发编程之美1-1线程基础线程通知和等待线程中断

线程创建与运行

  • 线程基础
    • 继承Thread类
    • 实现Runnable接口
    • FutureTask
  • 线程通知和等待
    • wait()
    • wait(long timeout)函数
    • notify()函数
    • notifyAll()
    • join()
    • sleep()
  • 线程中断
      • void interrupt()方法
      • boolean isInterrupted()
      • boolean interrupted()

线程基础

继承Thread类

public static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println("i am a student");
        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
           

注意,当创建完thread对象之后线程并没有启动,直到调用了start才是真正启动了线程。

其实调用start之后线程也没有立即执行,而是处于就绪状态,等待获取CPU资源之后才会开始。一旦run方法执行完毕,线程终止。

但是使用这种方法有弊端,一是java不支持多继承,继承了Thread类就不能再继承别的类;二是任务和代码没有分离,如果多个线程需要执行相同的任务,就需要同样的代码写好几份。

实现Runnable接口

public static class RunnableTask implements Runnable{
        @Override
        public void run() {
            System.out.println("i am a student");
        }
    }

    public static void main(String[] args) {
        
        RunnableTask task = new RunnableTask();
        new Thread(task).start();
        new Thread(task).start();
    }
           

如上所示,两个线程共用一个task代码逻辑,另外,RunnableTask还可以继承别的类。

FutureTask

以上两种方法都没有返回值,如果需要返回值,需要用到以下这种方法

public static class CallerTask implements Callable<String>{
        @Override
        public String call() throws Exception {
            return "student";
        }
    }

    public static void main(String[] args) {
        FutureTask<String> futureTask = new FutureTask(new CallerTask());
        new Thread(futureTask).start();
        try {
            String res = futureTask.get();
            System.out.println(res);
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
        }
    }
           

以上代码中,的CallerTask类实现了Callable接口的call()方法。首先在main函数中创建FutureTask对象,然后使用创建的FutureTask对象作为任务创建一个线程并启动它,最后可以通过futureTask.get()获取返回值。

线程通知和等待

wait()

当一个线程调用一个共享变量的wait()方法时,该调用线程就会被阻塞直到① 其他线程调用了该共享对象的notify()、notifyAll()函数 ②其他线程调用该线程的interrupt()函数,该线程会排除InterruptedException返回。

如果要调用wait()方法,必须先获取该对象的监视器锁。获取监视器锁有两种方法。

1.执行synchronized同步代码块
synchronized(共享变量){}
2.调用该共享变量的synchronized修饰的方法
synchronized void add(int a,int b){}
           

虚假唤醒:一个线程没有被其他线程notify唤醒或者被中断但变成了运行状态。为了防止虚假唤醒,可以不停的去测试线程被唤醒的条件是否满足

synchronized(obj){

while(条件不满足){

obj.wait();

}

}

例子

public static class RunnableTask implements Runnable{
        @Override
        public void run() {
            try {
                synchronized (A){
                    System.out.println("A get A lock");
                    synchronized (B){
                        System.out.println("A got B lock");
                        System.out.println("A release A lock");
                        A.wait();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static class RunnableTask2 implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                synchronized (A){
                    System.out.println("B get A lock");
                    System.out.println("B try to get B lock");
                    synchronized (B){
                        System.out.println("B got B lock");
                        System.out.println("B release B lock");
                        A.wait();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static class CallerTask implements Callable<String>{
        @Override
        public String call() throws Exception {
            return "student";
        }
    }
    private static volatile Object A = new Object();
    private static volatile Object B = new Object();
    public static void main(String[] args) {
         RunnableTask runnableTask = new RunnableTask();
         RunnableTask2 runnableTask2 = new RunnableTask2();
         new Thread(runnableTask).start();
         new Thread(runnableTask2).start();
    }
           

在main函数中启动了线程A和B,为了让A先获取到锁,让B睡了1s。A先获取到A和B锁,然后调用wait()方法阻塞自己,然后释放A锁。

B在结束休眠后先尝试获取A锁,如A还没有释放则B会阻塞。获取之后获取B锁,因为A一直没有释放,所以B获取不到。

这个例子可以证明当线程调用共享对象的wait()方法时,当前线程只会释放当前共享对象的锁,当前线程持有的其他共享对象的锁不会释放。

wait(long timeout)函数

设置超时参数,如果在指定时间内没有该共享变量没有被notify,那该函数还是会因为超时而返回。

notify()函数

一个线程调用共享对象notify()方法之后,会唤醒一个在该共享变量上调用wait系列方法而被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个线程是随机的。

但是,被唤醒的线程也不是立刻从wait方法返回并执行,而是需要先获取对共享对象的监视器锁。因为还会有其他线程和这个刚被唤醒的线程一起争夺监视器锁

public static class RunnableTask implements Runnable{
        @Override
        public void run() {
            synchronized (A){
                System.out.println("A get A lock");
                try {
                    System.out.println("A begin wait");
                    A.wait();
                    System.out.println("A end wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
    public static class RunnableTask2 implements Runnable{
        @Override
        public void run() {
            synchronized (A){
                System.out.println("B get A lock");
                try {
                    System.out.println("B begin wait");
                    A.wait();
                    System.out.println("B end wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
    public static class RunnableTask3 implements Runnable{
        @Override
        public void run() {
            synchronized (A){
                System.out.println("C get A lock");
                System.out.println("C begin notify");
                A.notify();
                System.out.println("C end notify");

            }
        }
    }
    public static class CallerTask implements Callable<String>{
        @Override
        public String call() throws Exception {
            return "student";
        }
    }
    private static volatile Object A = new Object();
    private static volatile Object B = new Object();
    public static void main(String[] args) throws InterruptedException {
         RunnableTask runnableTask = new RunnableTask();
         RunnableTask2 runnableTask2 = new RunnableTask2();
         RunnableTask3 runnableTask3 = new RunnableTask3();
         new Thread(runnableTask).start();
         new Thread(runnableTask2).start();
         Thread.sleep(1000);
         new Thread(runnableTask3).start();

    }
   
B get A lock
B begin wait
A get A lock
A begin wait
C get A lock
C begin notify
C end notify
B end wait
           

可以看到,线程c在notify之后唤醒了线程B,而A没有被唤醒

notifyAll()

如果把c线程中的notify改成notifyAll,那么AB线程都被唤醒,AB会争抢锁,先后执行。

join()

join这个方法的主要作用就是当前线程等待子线程运行结束。

sleep()

当一个线程调用了sleep方法后,调用线程会暂时让出指定时间的执行权,也就是这期间不参与CPU调度,但该线程所持有的资源,如监视器锁不会让出。指定时间到了之后该函数会返回,线程处于就绪状态参与CPU调度。

线程中断

java的线程中断是一种线程间的写作模式,通过设置线程的中断标志并不能直接终止现成的运行,被中断的线程会根据中断状态自行处理。

就好比老师要求你写assignment,但是最后要不要写还是你自己决定。

void interrupt()方法

中断线程,当线程A运行时,线程B可以调用线程A的interrupt()方法设置A的中断标志为true并立即返回。但是设置标志位仅仅是设置了标志,线程A并没有被中断,他还会继续往下执行。如果线程A调用了wait系列的函数,join方法或sleep方法而被挂起,这时候若线程B调用线程A的interrupt方法,则会抛出InterruptedException而返回。

此函数是唯一一个可以更改标志位的函数。

boolean isInterrupted()

用来检测当前线程是否被中断,如果是就返回true,否则为false

boolean interrupted()

和isInterrupted方法不同的是,如果该方法发现线程被中断,不仅会返回true,还会清除中断标志。

下面是一个使用中断标志位判断线程是否终止的例子。

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

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(!Thread.currentThread().isInterrupted()){
                    System.out.println(Thread.currentThread() + "hello");
                }
            }
        });
        thread.start();
        Thread.sleep(100);
        System.out.println("main thread interrupt thread");
        thread.interrupt();
        thread.join();
        System.out.println("main over");
    }
           

以知使用interrupt函数会让在sleep、wait等挂起状态的线程抛出异常并强制返回,节省时间,以下是例子

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

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("begin sleep");
                    Thread.sleep(20000);
                    System.out.println("awake");
                } catch (InterruptedException e) {
                    System.out.println("interrupted");
                    return;
                }
                System.out.println("normally");
            }
        });
        thread.start();
        Thread.sleep(100);
        thread.interrupt();
        thread.join();
        System.out.println("main over");
    }