《Java并发编程之美》阅读笔记(一)并发线程基础

目录

 

一、并发编程线程基础

1.1 什么是线程

1.2 线程的创建与运行

1.3 线程通知和等待

   1、wait() 函数

2、wait(long timeout)

3、wait(long timeout,int nanos)

4、notify()函数

5、notifyAll()函数

1.4 等待线程执行终止的join方法

1.5 让线程睡眠的sleep()方法

1.6 暗示CPU让出执行权的yield方法

1.7 线程中断

1.8 理解线程上下文切换

1.9 线程死锁

1.9.1 什么是死锁

1.9.2 如何避免死锁

1.10 守护线程与用户线程

1.11 ThreadLocal

1.11.1 ThreadLocal的使用

1.11.2 ThreadLocal的实现原理

1.11.3 ThreadLocal不支持继承性

1.11.4 InheritableThreadLocal类


一、并发编程线程基础

1.1 什么是线程

    说线程,一定要知道进程。进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,而线程是进程的一个执行路径。线程是不会独立存在的,一个进程中至少有一个线程,多个线程共享进程的资源。
   系统的资源分配给进程,而cpu资源比较特殊,是直接分配给线程的,因为真正要占用cpu的是线程,也就是说线程是cpu资源的基本分配单位。

    进程的方法区的资源被线程共享。程序计数器是每个线程私有的。

  •     程序计数器:前边提到线程是占用cpu的基本单位,而cpu是以时间片轮转的方式轮询被线程占用的,当前线程的获得时间片消耗完后要让出cpu,直到下次再轮到自己。程序计数器就是记录线程的执行地址,从而当下次再获得时间片时可以从上次的执行位置继续执行。注意:当执行的是native方法时,程序计数器记录的是undefined。只有执行java方法时,程序计数器才记录的是地址。
  •     栈:  线程私有,存放该线程的局部变量、基本数据类型的变量,局部变量是线程私有的,其他线程不能访问。还存放该线程的调用栈帧(栈帧:随方法调用而创建,随方法结束而消亡。存放该方法的变量)。
  •     堆:堆是进程中内存最大的一块区域,是在进程创建时系统给分配的,堆内的资源被该进程下的所有线程共享,主要存放new操作创建的实例对象。
  •     方法区:这一区域被线程共享,主要存放JVM加载的类、常量和静态变量等信息。

1.2 线程的创建与运行

    线程创建有3个方法,一是实现Runable接口的run()方法,二是继承Thread类重写run()方法,三是使用FutureTask()方法。

   以下为3种创建方式的代码:

public class CreateThread {
    //线程创建方法一:继承Thread,重写run方法
    public static  class  MyThread1 extends Thread{
        @Override
        public void run() {
            System.out.println("重写run方法");
        }
    }

    //main方法
    public static void main(String[] args) {
        Thread myThread1 = new MyThread1();
        myThread1.start();
    }
}

这是通过继承Thread类创建线程。它的好处是获取当先线程时可以直接用this获取,而不必使用Thread.currentThread()方法。缺点是由于一个类只能继承一个类,再业务中需要继承其他类时就不能使用这种方法了。

注意:创建Thread实例后表示已经获得了除cpu之外的资源,此时线程处于就绪态。在执行start方法后会分配cpu资源, 然后执行run方法,run方法执行结束后,线程进入终止状态。

//线程创建方法二:实现Runable接口
    public static class MyThread2 implements Runnable{
        @Override
        public void run() {
            System.out.println("实现run方法");
        }
    }

    //main方法
    public static void main(String[] args) {
        MyThread2 task = new MyThread2();
        new Thread(task).start();
        new Thread(task).start();
    }

这是通过实现Runnable接口创建线程。但是以上两种方法都有一个问题,他们执行的任务(即run方法)没有返回值。FutureTask可以解决这个问题。

 //创建任务类,类似Runnable方式。返回值类型可自定义
    public static  class CallTask implements Callable<String>{
        @Override
        public String call() throws Exception {
            return "hello";
        }
    }

    //main方法
    public static void main(String[] args) {
        FutureTask<String> futureTask = new FutureTask<>(new CallTask());
        String result = null;
        new Thread(futureTask).start();
        try {
            result = futureTask.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

小结:继承Thread的方式使用简单,但是存在java单继承的问题。实现RUnnable接口避免了这个问题,但是这两种形式中的任务代码都没有返回值。想要任务执行结束后返回数据就要使用实现FutureTask接口的方式了。

1.3 线程通知和等待

   1、wait() 函数

    当一个线程调用一个共享变量的wait()时,该调用线程会被阻塞挂起并释放该共享变量上的锁。直到发生以下事情之一才返回:(1)有其他线程调用了该共享变量的notify()或notifyAll()方法   (2)其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。

    注意,如果调用wait()方法时,没有实现获取该对象的监视器锁,则调用wait()方法时会抛出IllegalMonitorStateException异常。

   获得共享变量的方法有两个:

   (1)使用synchronized()同步代码块,并使用该共享变量作为参数:

synchronized(共享变量){
    //dosomething
    //此时获取了共享变量的监视器锁,可以调用共享变量的wait()方法
}

 (2)一个被synchronized 修饰的方法,去调用共享变量时:

synchronized void add (int a, int b){
    //dosomething
     //在此方法里调用共享变量的wait()方法即可。
}

    另外一个需要注意的是,线程可以从挂起状态变成运行状态(唤醒),即便没有被其他线程调用notify或notifyAll方法,或被中断、等待超时,这就是虚假唤醒。(虚假唤醒 可以理解为:线程在不符合唤醒条件的情况下被唤醒了。)

    这种情况虽然很少发生,但也需要预防。可以使用循环判断是否符合唤醒条件,如果不符合则继续挂起

synchronized(obj){
     while(唤醒条件不满足){
        obj.wait();
    }
}

  ·在调用共享变量的wait()时,只会释放该共享变量上的锁,如果当前线程存在其他的共享变量的锁,则他们不会被释放。

  ·在线程被挂起等待期间调用该线程的interrupt()方法时,会抛出interruptedException异常,返回异常后中断线程。

2、wait(long timeout)

多了一个超时参数,当被挂起的线程一直没有被唤醒且等待时间超过指定的timeout时,该线程会因为超时而返回。如果参数设置为0,则效果与不传参一样,会一直挂起。因为wait()函数的内部就是调用的wait(0)。如果参数传负值,则会抛出IllegalArgumentException异常。

3、wait(long timeout,int nanos)

·当999999>nanos>0时,timeout会自增1 。

·当nanos传入0时,效果与wait(long timeout)一样。

其他情况抛出对应的异常信息。

4、notify()函数

    一个线程调用某共享变量的notify()函数后,会唤醒一个因调用该共享变量的wait系列方法而被挂起的线程。如果有多个线程因同一个共享变量的wait系列函数而挂起,那么调用notify()后被唤醒的线程是随机的。

    另外,notify()函数与wait系列函数一样,必须在已经获取到该共享变量的监视器锁才能调用,否则抛出IllegalMonitorStateException异常。

    还要注意,被唤醒的线程不能马上从挂起状态进入执行状态,他必须获取到该共享变量的监视器锁才能进入执行状态。而即便他被notify()唤醒,仍然可能竞争不到该共享变量的监视器锁。

5、notifyAll()函数

   不同于notify()函数只能唤醒某一个线程,当一个线程调用某共享变量的notifyAll()函数时,会唤醒所有因调用该共享变量的wait系列方法而被挂起的线程。

下边是一个代码示例:

package com.learnThread.demo.part1;


/**
 * @Author: tongys
 * @Date: 2019/12/23
 */
public class NotifyThread {
    private static volatile Object resourceA = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread_A = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA){
                    System.out.println("thread_A get resource's lock .");
                    try {
                        System.out.println("thread_A begin wait...");
                        resourceA.wait();
                        System.out.println("thread_A end wait...");
                        Thread.sleep(2000); //证明A、B两线程在同时被唤醒时,会竞争同一个监视器锁,从而同时只会有一个线程进入执行状态。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread thread_B = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA){
                    System.out.println("thread_B g
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值