Java-09 并发编程

线程的创建

创建并开启一个新的线程

第一种方法

    // 创建线程
    Thread thread = new Thread(new Runnable(){
      @Override
      public void run() { 
        System.out.println("新线程任务-----------");
      }
    });
    thread.setName("lwy");
    // 开启线程
    thread.start();

Thread调用start方法之后,内部会调用run方法。

注意: 直接调用run方法并不能开启新线程,只是在当前线程执行run里面的任务而已。 调用start方法才能开启新线程(start内部有一个native的start0方法,会向内核申请开启新线程)

第二种方法: 创建一个线程类(继承自Thread)

public class MyThread extends Thread {
  @Override
  public void run() {
    System.out.println("自己的线程类");
  }
}

    Thread thread = new MyThread();
    thread.start();

多线程的内存布局

PC寄存器:每个线程都有自己的pc寄存器

java虚拟机栈:每个线程都有自己的java虚拟机栈

堆(Heap):多个线程共享

方法区: 多个线程共享方法区

本地方法栈: 每个线程都有自己的本地方法栈

线程的状态

java中线程一共有6种状态。可以通过getState方法获取

    public enum State {
        // 新建(还未启动)
        NEW,
        // 可运行状态(正在JVM中运行。或者正在等待操作系统的其他资源(比如处理器))
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
  1. NEW: 新建(还未启动

  2. RUNNABLE : 可运行状态(正在JVM中运行。或者正在等待操作系统的其他资源(比如处理器))

  3. BLOCKED: 阻塞状态,正在等待监视器锁(内部锁)

  4. WAITING: 等待状态,在等待另一个线程

  5. TIMED_WAITING:定时等待状态,

  6. TERMINATED: 终止状态,已经执行完毕

BLOCK跟WATING的区别:

一个线程如果正在执行任务,会消耗CPU时间片。BLOCK状态是等待锁,
类似于一直执行while(锁没有被释放); ,会消耗时间片。而WAITING状态就是等待其他线程,处于休眠,CPU不会分配时间片,也不会执行其他代码

线程的方法

sleep、interrupt

可以通过Thread.sleep方法来暂停线程,进入WATING状态。
在暂停期间,若调用线程对象的interrupt方法中断线程,会抛出java.lang.InterruptedExpection异常

    Thread thread = new Thread(() -> {
      System.out.println(1);
      try {
        Thread.sleep(3000);
      } catch (Exception e) {
        e.printStackTrace();
      }
      System.out.println(2);
    });
    
    thread.start();
    
    try {
      Thread.sleep(1000);
    } catch (Exception e) {

    }
    System.out.println(3);

    thread.interrupt();

打印:

1
3
java.lang.InterruptedException: sleep interrupted
        at java.base/java.lang.Thread.sleep(Native Method)
        at Thread.Main.lambda$0(Main.java:12)
        at java.base/java.lang.Thread.run(Thread.java:835)
2

join、isAlive

A.join: 等线程A执行完毕之后,当前线程再继续执行任务。可以传参制定最长等待时间

    Thread thread = new Thread(()-> {
      System.out.println(1);
      try {
        Thread.sleep(3000);
      } catch (Exception e) {
        e.printStackTrace();
      }
      System.out.println(2);
    });
    thread.start();

    System.out.println(3);

打印

3
1
2

加入等待之后:

    Thread thread = new Thread(()-> {
      System.out.println(1);
      try {
        Thread.sleep(3000);
      } catch (Exception e) {
        e.printStackTrace();
      }
      System.out.println(2);
    });
    thread.start();
    try {
    // 等待线程thread执行完毕之后再往下执行
      thread.join();
    } catch (Exception e) {
    }
    System.out.println(3);

打印

1
2
3

A.isAlive: 查看线程A是否活着

线程安全问题

多个线程可能会共享(访问)同一个资源,比如访问同一个对象,同一个变量,同一个文件。当多个线程访问同一块资源是,很容易引发数据错乱和数据安全问题,成为线程安全问题

什么时候下会出现线程安全问题?

  • 多个线程共享同一个资源,且至少一个线程正在进行写操作

线程同步

  • 可以使用线程同步技术来解决线程安全问题
    • 同步语句(Synchronized Statement)
    • 同步方法(Synchronized Method)

线程同步 - 同步语句

  public boolean saleTicket() {
    synchronized (this) {
      if (tickets < 0)
        return false;
      tickets--;
      String name = Thread.currentThread().getName();
      System.out.println(name + "买了一张票, 还剩" + tickets + "张");
      return tickets > 0;
    }
  }
  • synchronized(obj)的原理

    • 每个对象都有一个与他相关的内部锁(intrinsic lock),或者叫做监视器锁(monitor lock)
    • 第一个执行到同步语句的线程会获得obj的内部锁,在执行同步语句结束后会释放该锁
    • 只要一个线程持有了内部锁,那么其他线程在同一时刻无法再获得该锁
    • 当他们试图获取此锁时,会进入BLOCKED状态

线程同步 - 同步方法

public synchronized boolean saleTicket() {
    if (tickets < 0)
      return false;
    tickets--;
    String name = Thread.currentThread().getName();
    System.out.println(name + "买了一张票, 还剩" + tickets + "张");
    return tickets > 0;
  }
  • synchronized 不能修饰构造方法

  • 修饰实例方法时跟同步语句是等价的

  • 静态方法的话等价于synchronized (Class对象)

public synchronized static void test() {

  }
  public static void test1() {
    // Station.class: 类对象 每一个类都只有一个类对象
    synchronized (Station.class) {

    }
  }

死锁(Deadlock)

什么是死锁?

两个或多个线程永远阻塞,相互等待对方的锁


    new Thread(() -> {
      synchronized ("1") {
        System.out.println("1");
        try {
          Thread.sleep(100);
        } catch (Exception e) {
          e.printStackTrace();
        }
        synchronized ("2") {
          System.out.println("1 - 2");
        }
      }
    });

    new Thread(() -> {
      synchronized ("2") {
        System.out.println("2");
        try {
          Thread.sleep(100);
        } catch (Exception e) {
          e.printStackTrace();
        }
        synchronized ("1") {
          System.out.println("2 - 1");
        }
      }
    });

注:同步语句的obj这里传的是字面量,由于SCP的存在,同一字面量就是同一个对象

线程间通信

可以使用Object.waitObject.notify,Object.notifyAll方法实现线程间通信

若想在线程A中陈工调用obj.wait,obj,notify,obj.notifyAll方法,线程A必须要持有obj的内部锁

obj.wait:释放obj的内部锁,当前线程进入WATINGTIMED_WAITING状态

obj.notifyAll:唤醒所有因为obj.wait进入WATINGTIMED_WAITING状态的线程

obj.notify:随机唤醒一个因为obj.wait进入WATINGTIMED_WAITING状态的线程

注意:

  1. 调用waitnotify,notifyAll的obj是同一个obj
  2. 调用waitnotify,notifyAll的线程必须持有obj的内部锁

可重入锁(ReentrantLock)

可重入锁具有跟同步语句,同步方法一样的一些基本功能,但功能更加强大

什么是可重入?

同一个线程可以重复获取同一个锁。

其他地方叫做递归锁

 private ReentrantLock lock = new ReentrantLock();

  public boolean saleTicket() {
    try {
    // lock():必须获得此锁,如果锁被其他线程获取 将一直等待直到获得此锁
      lock.lock();
      if (tickets < 0)
        return false;
      tickets--;
      String name = Thread.currentThread().getName();
      System.out.println(name + "买了一张票, 还剩" + tickets + "张");
      return tickets > 0;
    } finally {
      lock.unlock();
    }
  }
  • ReentrantLock.lock:获得此锁,

    • 如果此锁没有被另一个线程持有,则将锁的持有计数设为1.并且此方法立即返回。
    • 如果当前线程已经持有此锁,则将此锁的持有计数加一,并且此方法立即返回。
    • 如果此锁被另一个线程持有,那么在获得此锁之前,此线程将一直处于休眠状态,直到获得此锁。此时锁的持有计数被设为1(虽然被设为1,但是此线程并没有持有该锁,而是在等待获取该锁)。
    • 所以,调用了几次lock方法,对应的就要有几次的unlock方法
  • ReentrantLock.tryLock: 仅在锁没有被其他线程持有的情况下,才会获得此锁

    • 如果此锁没有被其他线程持有,则将锁的持有计数设为1,并且立即返回
    • 如果当前线程已经持有此锁,则将锁的持有计数加一,并且立即返回
    • 如果锁被另一个线程持有,此方法立即返回false
  public boolean saleTicket() {
    boolean flag = false;
    try {
      flag = lock.tryLock();
      if (tickets < 0)
        return false;
      tickets--;
      String name = Thread.currentThread().getName();
      System.out.println(name + "买了一张票, 还剩" + tickets + "张");
      return tickets > 0;
    } finally {
      if (flag) {
        lock.unlock();
      }
    }
  }
  • ReentrantLock.unlock:尝试释放此锁

    • 如果当前线程持有此锁,则将持有计数减一
    • 如果持有计数为0,释放此锁
    • 如果当前线程没有持有此锁,则抛出异常
  • ReentrantLock.isLocked:查看此锁是否被任意线程持有

其实synchronized也是可重入的

  public static void main(String[] args) {
    synchronized("1") {
      synchronized("1") {
        System.out.println("123456");
      }
    }
  }
  
  // 打印:123456

假设synchronized不是可重入锁,那么在第一个synchronized中,获得了"1"的内部锁,在第二个synchronized中会发现“1”的内部锁已经被持有,然后会等待,但是“1”本身就是被当前的线程持有,所以打印语句永远不会被执行。
但是现在打印了,说明synchronized是可重入锁,可以多次获得该锁

synchronized在不同语言实现不同,有可能在其他语言就不是可重入锁(递归锁),再像上面那样写的话可能就会出错

线程池

线程对象占用大量的内存,在大型项目中,频繁的创建和销毁线程对象将产生大量的内存管理开销。

使用线程池可以最大程度的减少线程创建、销毁带来的开销

线程池由工作线程(Worker Thread)组成

  • 普通线程:执行完一个工作之后,生命周期就结束了

  • 工作线程:可以执行多个任务(没有工作时就在等待,有任务了就开始干活)

    • 先将任务添加到队列(Queue)中,再从队列中取出任务提交到池中
  • 常用的线程池类型是固定线程池(Fixed Thread Pool)
    • 具有固定适量的正在运行的线程
    // 创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(5);
    pool.execute(() -> {
      System.out.println(11 + "_" + Thread.currentThread().getName());
    });
    pool.execute(() -> {
      System.out.println(22 + "_" + Thread.currentThread().getName());
    });
    pool.execute(() -> {
      System.out.println(33 + "_" + Thread.currentThread().getName());
    });
    pool.execute(() -> {
      System.out.println(44 + "_" + Thread.currentThread().getName());
    });

    // 关闭线程池
    // pool.shutdown();
    
    /*
     * 11_pool-1-thread-1 
     * 22_pool-1-thread-2 
     * 33_pool-1-thread-3 
     * 44_pool-1-thread-4
     */
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272