JUC系列1-基础知识

线程启动

通常启动有三种方法启动线程:
1、继承Thread类,重写run()方法,启动线程。
2、实现Runnable接口,重写run()方法,将实现作为线程构造传入,启动线程。
3、用FutureTask包装Runnable或者Callable,可以返回线程执行结果。

代码示例

继承Thread类

class MyThread extends Thread {

   public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
 }

public static void main(String[] args){
	Thread t1 = new MyThread("test");
    t1.start();
}

实现Runnable接口

public static void main(String[] args){
	Runnable r = () -> System.out.println(Thread.currentThread().getName());
    Thread aThread = new Thread(r, "a");
    aThread.start();
}

利用FutureTask

1、Future接口用来表示异步执行的结果,其主要方法定义如下:
V get() 获取执行结果,如果执行未结束则会阻塞。
V get(long timeout, TimeUnit unit) 获取执行结果,如果执行未结束则会阻塞指定的时间。
boolean cancel(boolean mayInterruptIfRunning) 取消当前线程任务。参数为true代表取消正在执行任务的线程,false代表不取消正在执行任务的线程;返回值为取消结果,典型场景是任务已经完成调用此方法会返回false。
boolean isDone() 返回true如果线程已经完成,完成的场景包括正常结束、异常或者被取消等。
boolean isCancelled() 返回true如果线程在正常完成之前被取消。

2、Callable接口,定义类似于Runnable,只有一个call()方法,并且有返回值。在JUC中如果需要获取异步执行结果,通常使用该接口实现。

3、RunnableFuture是同时实现RunnableFuture的接口。而FutureTaskRunnableFuture的实现类,其主要方法是:run()执行异步任务,get()获取任务结果。

4、FutureTask 获取任务结果的定义接口来自Future,具体的功能实现来自于Callable接口,其内部持有一个Callable对象,在run()执行异步任务时,实际上执行的是Callable对象的call()方法,并把结果保存在outcome字段中。调用get()方法时,会判断线程状态,如果还未完成则会阻塞挂起,WaitNode变量代表等待的线程队列。在run()方法中任务完成之后,finishCompletion()会唤醒等待获取结果的线程。

public static void main(String[] args){
	Callable<Integer> callable = () -> {
            int sum = 0;
            for (int i = 1; i < 20; i++) {
                sum += i;
            }
            System.out.println(Thread.currentThread().getName());
            return sum;
        };
    FutureTask<Integer> futureTask = new FutureTask<>(callable);
    Thread callThread = new Thread(futureTask, "call");
    callThread.start();
    System.out.println("sum: " + futureTask.get());//future.get()会发生阻塞,等待异步计算完成获取结果
}

线程常用方法

线程通知与等待

主要包括wait()、notify()、notifyAll()方法,属于Object类中方法。

wait方法

当一个线程调用一个共享变量的wait()方法时, 该调用线程会被阻塞挂起, 直到发生下面几件事情之一才返回:
(1) 其他线程调用了该共享对象的 notify()或者notifyAll()方法;
(2) 其他线程调用了该线程的 interrupt()方法, 该线程抛出InterruptedException异常返回。

另外,如果调用 wait()方法的线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程会抛出IllegalMonitorStateException异常。

那么,一个线程如何才能获取一个共享变量的监视器锁呢?
(1) 执行 synchronized同步代码块时,使用该共享变量作为参数。

synchronized(共享变量){
	so something
}

(2) 调用该共享变量的方法,并且该方法使用了synchronized修饰 。

synchronized void add(int a, int b){
	so something
}

notify方法

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

此外,被唤醒的线程不能马上从 wait 方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争, 只有该线程竞争到了共享变量的监视器锁后才可以继续执行。

类似 wait 系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的 notify() 方法,否则会抛出 IllegalMonitorStateException异常。为了避免此类异常,wait与notify系列方法一般在共享代码块中调用。

notifyAll() 方法则会唤醒所有在该共享变量上由于调用 wait 系列方法而被挂起的线程。

生产者与消费者代码示例

public static void main(String[] args){
		ArrayDeque<Integer> deque = new ArrayDeque<>(20);
        Runnable producer = () -> {
            while (true) {
                synchronized (deque) {
               	    // 这里用while判断防止线程虚假唤醒
                    while (deque.size() == 20) {
                        try {
                            System.out.println("producer wait");
                            deque.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Random r = new Random();
                    deque.add(r.nextInt(100));
                    deque.notifyAll(); //通知唤醒消费者线程,有新元素可以消费了
                }
            }
        };

        Runnable consumer = () -> {
            while (true) {
                synchronized (deque) {
                    while (deque.size() == 0) {
                        try {
                            System.out.println("consumer wait");
                            deque.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("deque remove:" + deque.remove());
                    deque.notifyAll();
                }
            }
        };
        new Thread(producer).start();
        new Thread(consumer).start();
        Thread.sleep(5000L);
 }

常用Thread类方法

join方法

join是无参且返回值为void的方法,用于等待某一线程执行完成后再向下执行。

线程 A 调用线程 B 的 join 方法后会被阻塞 , 当其他线程调用了线程 A 的
interrupt()方法中断了线程 A 时,线程 A 会抛出 InterruptedException 异常而返回。

public static void main(String[] args){
		Thread mainTread = Thread.currentThread();
        Runnable r1 = () -> {
            while (true) {
            }
        };
        Runnable r2 = () -> {
            try {
                Thread.sleep(3000L);
                mainTread.interrupt(); //注意这里中断的是主线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);
        thread1.start();
        thread2.start();

        try {
            thread1.join(); //等待线程1结束,主线程阻塞
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("thread is interrupted");
        }
}

interrupt方法

设置线程的中断位状态为true,不代表线程马上中断,而是被中断的线程根据中断状态自行处理 。通常,线程由于调用wait()、join()、sleep()而阻塞时,如果调用此方法会系统会抛出InterruptedException异常。

thread对象.isInterrupted() 是对象方法,返回某线程中断标志位状态。

Thread.interrupted() 是静态方法,返回当前线程中断标志位状态,同时清除标志位(置为false)。

sleep方法

Thread类中有一个静态的 sleep方法,当一个执行中的线程调用了 Thread的 sleep方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与 CPU 的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的

指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与 CPU 的调度,获取到 CPU 资源后就可以继 续运行了。如果在睡眠期间其他线程调用了该线程的 interrupt()方法中断了该线程,则该 线程会在调用 sleep 方法的地方抛出 IntermptedException 异常而返回。

wait方法会挂起线程同时让出监视器资源,需要notify()唤醒,这是和sleep的主要区别。

yield方法

Thread类中有一个静态的yield方法,当一个线程调用 yield方法时,实际就是在暗示线程调度器当前线程请求让出自己的CPU使用,但是线程调度器可以无条件忽略这个暗示。

当一个线程调用 yield 方法时, 当前线程会让出 CPU 使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到 刚刚让出CPU的那个线程来获取CPU执行权。

守护线程与用户线程

Java 中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。 在JVM启动时会调用main函数, main函数所在的钱程就是一个用户线程,其实在JVM内部同时还启动了好多守护线程, 比如垃圾回收线程。那么守护线程和用户线程有什么区别呢? 区别之一是当最后一个非守护线程结束时, JVM会正常退出,而不管当前是否有守护线程 ,也就是说守护线程是否结束并不影响JVM的退出。 言外之意,只要有一个用
户线程还没结束,正常情况下JVM就不会退出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值