06-并发

一、线程创建

Java中常见创建线程有三种,常见的如下两类

1.1 继承Threa类

Java 复制代码
public class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("MyThread is running");
    }
}

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

1.2 实现Runnable接口

由于继承Thread类后无法继承其它类,因此实现Runnable接口是常见的线程创建方式。Runnable接口只有1个抽象方法run,因此可以直接使用lambda表达式。

Java 复制代码
public static void main(String[] args) {
    Thread t1 = new Thread(()->{
    System.out.println("Thread 1 is running");
   });
   t1.start();
}

二、常用方法

2.1 sleep

sleep是让当前执行的线程让出cpu占用权n毫秒

Java 复制代码
public static void main(String[] args) throws InterruptedException {
   Thread t1 = new Thread(()->{
        System.out.println("Thread 1 is running");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread 1 is ending");
   });

   t1.start();
   try {
    Thread.sleep(3000);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   System.out.println("main thread is running");
}
  1. main线程先执行,走到了t1线程,t1和main同时抢占cpu,然后main被休眠了3s。
  2. t1线程执行thread1 running,此时t1自己线程也被休眠1s,此时main线程和t1都在休眠。
  3. t1后,抢占cpu,输出Thread 1 is ending
  4. main最后才醒来,抢到cpu,输出main thread is running

2.2 join

join让当前线程暂停直到目标线程结束后。如下代码,如果没有t1.join方法,最后的输出肯定是Thread 1 is ending。但t1.join让main线程暂停,必须等t1线程执行结束才能继续,所以输出是

  1. Thread 1 is running
  2. Thread 1 is ending
  3. main thread is running
Java 复制代码
public static void main(String[] args) throws InterruptedException {
   Thread t1 = new Thread(()->{
        System.out.println("Thread 1 is running");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread 1 is ending");
   });
   t1.start();
   t1.join();
   System.out.println("main thread is running");
}

2.3 interrupt

interrupt给目标线程贴请停标签;如果对方阻塞会立刻醒,其它情况需要isInterrupted方法判断是否有停止标签。

休眠被唤醒

Java 复制代码
public static void main(String[] args) throws InterruptedException {
   Thread t1 = new Thread(()->{
        System.out.println("Thread 1 is running");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            System.out.println("Thread 1 is interrupted");
        }
        System.out.println("Thread 1 is ending");
   });

   t1.start();
   t1.interrupt();
   System.out.println("main thread is running");
}
  1. t1线程在执行时,interrupt告诉t1你需要暂停下,此时t1如果刚好在sleep休眠时,立马被唤醒走到catch,输出Thread 1 is interrupted。
  2. t1与main线程继续互相抢占cpu。也就是Thread 1 is ending与main thread is running输出顺序随机。

其它情况

线程被interrupt告知暂停时,如果线程刚好休眠会被唤醒进入catch。但是如果线程是其它状态,必须使用isInterrupted获取状态来处理

Java 复制代码
public static void main(String[] args) throws InterruptedException {
   Thread t1 = new Thread(()->{
        System.out.println("Thread 1 is running");
        for(int i=0;i<10000;i++){
            if(Thread.currentThread().isInterrupted()){
                break;
            }
        }
        System.out.println("Thread 1 is ending");
   });
   t1.start();
   t1.interrupt();
   Thread.sleep(2000);
   System.out.println("main thread is running");
}
  1. t1和main线程在互相抢占cpu执行,t1被标记暂停了,main线程睡眠了。
  2. 此时大概率t1线程在跑任务,输出Thread 1 is running。
  3. t1继续在执行for循环时,判断状态发现自己被标记暂停了,立马退出,执行Thread 1 is ending
  4. 然后main线程醒来后,执行main thread is running。

2.4 volatile

volatile保证可见性:我写完后,别人能读取到新值。但是它还是无法解决并发写的问题。

Java 复制代码
public class VolatileStop {
    static volatile boolean running = true;
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            long n = 0;
            while (running) {
                n++;
            }
            System.out.println("worker 看到 false,退出 n=" + n);
        });
        worker.start();
        Thread.sleep(100);
        running = false;
        worker.join();
        System.out.println("main 结束");
    }
}

三、同步与锁

同步与锁是并发编程非常重要环节,首先需要掌握3个核心api。

3.1 synchronized

3.1 概念

锁可以理解成钥匙,synchronized (x)就是多个线程去抢占这个x锁,每次只能有1个线程能抢到,方法执行结束后后释放锁,没有抢到的线程无法进入方法内。

javascript 复制代码
synchronized(x){
    //do
}

3.2 条件

成功的锁必须满足两个条件

  1. 多线程抢的必须是同一个对象(同一把钥匙)
  2. 保护的是同一份共享数据

锁代码块

线程t1和t2共享变量count,而且锁的作用对象都是lock。

scss 复制代码
private static int count = 0;
private static Object lock = new Object();
public static void increment(){
    synchronized(lock){
        for(int i=0;i<10000;i++){
            count++;
        }
    }
}
public static void main(String[] args) throws InterruptedException {
   Thread t1 = new Thread(()->{
    increment();     
   });
   Thread t2 = new Thread(()->{ 
    increment();
   });
   t1.start();
   t2.start();
   t1.join();
   t2.join();
   System.out.println(count);
}

2. 锁函数

这里t1和t2线程的锁的对象是static的Class类上,二者还是同一个对象,所以依旧正常。

Java 复制代码
private static int count = 0;
private static Object lock = new Object();
public static synchronized void increment(){
    for(int i=0;i<10000;i++){
      count++;
    }
}
public static void main(String[] args) throws InterruptedException {
   Thread t1 = new Thread(()->{
    increment();     
   });
   Thread t2 = new Thread(()->{ 
    increment();
   });
   t1.start();
   t2.start();
   t1.join();
   t2.join();
   System.out.println(count);
}

3. 错误锁

多线程时,锁的对象明显地址是不同的,无效锁。

Java 复制代码
synchronized (new Object()) {
    count++;
}

3.2 wait

wait和sleep有本质差异,sleep是释放cpu抢占资源,而wait是释放锁的意思。线程wait释放锁后,就一直被阻塞,苏醒后继续抢到锁,然后执行未完成代码。

wait苏醒后从被wait地方继续执行代码,而不是从函数或者synchronized开始执行。

如下代码模拟生产者与消费者,由于生产者的方法入口添加sleep,所以消费者一定先抢到了锁,输出 begin decrement,来到锁里面输出begin decrement1,但是由于队列空,只能wait,把锁给释放了。此时生产者拿到锁,输出了begin increment和begin increment1,给队列生产1个数据,通知消费者消费。注意消费者苏醒后直接从lock.wait()这行代码后继续执行,继续while循环判断。

arduino 复制代码
begin decrement
begin decrement1
begin increment
begin increment1
Java 复制代码
public class Main {
    private static int max = 1;
    private static Object lock = new Object();
    private static List<Integer> list = new ArrayList<>();
    public static void increment() {
        try{
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("begin increment");
        synchronized (lock) {
            System.out.println("begin increment1");
            while (list.size() >= max) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
            list.add(1);
            lock.notifyAll();
        }
    }

    public static void decrement() {
        System.out.println("begin decrement");
        synchronized (lock) {
            System.out.println("begin decrement1");
            while (list.size() == 0) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
            list.remove(list.size() - 1);
            lock.notifyAll();
        }
    }
    public static void main(String[] args) throws InterruptedException {
       Thread t1 = new Thread(()->{
        increment();
       });
       Thread t2 = new Thread(()->{ 
        decrement();
       });
       t1.start();
       t2.start();
    }
}

为什么边界条件都是while,而不是if?

if会造成条件失效,模拟下如果3个生产者,1个消费者,队列最大是1。

markdown 复制代码
1. 生产者t1,生产1个,通知其它线程抢锁
2. 生产者t2,发现队列满了,把自己wait,
3. 生产者t3,发现队列还是满了,把自己wait,
4. 消费者进来,消费1个,通知其它线程抢锁
5. t2苏醒,直接走if后面的代码,队列生产1个
6. t3苏醒,直接走if后面的代码,队列生产2个,报错。

3.3 notify

notify是wait对立面,它手上有锁,告诉被wait的线程,你可以去外面排队了,我马上就释放锁了,注意此时还没有释放锁,只有synchronized代码块执行结束才释放锁。而被wait的线程收到notify通知后,不睡觉了,在门口等待抢锁动作。

notify() notifyAll()
叫醒几个 一个(哪个不确定) 这把锁上 所有 wait 的
风险 可能叫错人:叫醒了另一个生产者,消费者还在睡 大家醒了再用 while 判断,不会干错
入门 少用 优先用这个

四、死锁

死锁必须满足

  1. 互斥: 锁同时只能一人拿
  2. 持有并等待: t1拿着 lock1 不放,再等 lock2
  3. 不可剥夺: 不能把别人的锁抢走,只能等他自己放
  4. 循环等待: t1 等 t2,t2 等 t1,围成环
Java 复制代码
public class Main {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();
    public static void main(String[] args) throws InterruptedException {
       Thread t1 = new Thread(() -> {
        synchronized(lock1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(lock2){
                System.out.println("Thread 1 locked lock2");
            }
        }
       });

       Thread t2 = new Thread(() -> {
        synchronized(lock2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(lock1){
                System.out.println("Thread 2 locked lock1");
            }
        }
       });

       t1.start();
       t2.start();
    }
}
相关推荐
程序员-珍1 小时前
Android studio 突然打不开
android·java
重生之小比特2 小时前
【Java SE】字符串 - String类
java·开发语言·intellij-idea
mldong7 小时前
给若依加审批流,不用 Flowable
java·spring boot·架构
Wang's Blog9 小时前
Java框架快速入门: Spring Security+OAuth2之数据库和实体类的RBAC改造
java·数据库·spring
讳疾忌医丶9 小时前
深度拆解 RocksDB 内核:基于 C++17 的 FIFO 调度状态机与温度阶梯自愈设计
java·c++·算法·架构
郑州光合科技余经理10 小时前
国际版外卖系统:税率字段怎么和订单主流程解耦
android·java·开发语言·前端·后端·php·ai编程
毅炼10 小时前
Rover-Suite 开源发布:轻量服务注册中心与网关一体化方案
java·后端·系统架构·gateway
梦想平凡10 小时前
百游棋牌源代码开发搭建教程(十):隔离部署、备份恢复与双端验收
java·前端·javascript·数据库·源代码管理
sunshine22 girl11 小时前
Idea中如何搜索
java·ide·intellij-idea