java多线程

发布时间 2023-07-30 14:05:53作者: 吾执青剑向天涯

1、什么是JUC

  1. 官方文档+源码

    ​ 面试高频问

    java.util

    java.util.concurrent

    java.util.concurrent.atomic

    java.util.concurrent.locks

​ java,util 工具包、包、分类

业务:普通的线程代码 Thread

Runnable 没有返回值

2、线程和进程

线程和进程 如果不能用一句话说出来的技术就是不扎实

进程:就是一个程序 一个程序的集合

​ 一个进程往往可以有多个线程至少包含一个

java 默认高就有两个线程 main 、GC:垃圾回收

线程:开了一个java 有main线程 GC线程

对应java而言:: Thread、Runnable、Callable

java真的可以开线程吗?? 不可以

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
	// 本地方法,底层的C++,  java无法操作硬件
    private native void start0();

并发、并行

并发编程:并发 、并行

并发 (多线程操作同一个资源) 充分利用cpu的资源

  • CPU 一核 模拟出来多条线程,天下武功为快不破 快速交替模拟并行(宏观上并行微观上窜行)

并行 (多个人一起走)

  • CPU多核 多个线程可以同时进行;线程池

    package com.lmq;
    
    /**
     * @author 羡鱼
     * @version 1.0
     * @date 2023/7/29 17:05
     */
    public class JucTest {
        public static void main(String[] args) {
            // 获取cpu的核数
            System.out.println(Runtime.getRuntime().availableProcessors());    }
    }
    
    

所有公司都看中

线程有几个状态

public enum State {

         // 新生
        NEW,
         // 运行
        RUNNABLE,
		// 阻塞
        BLOCKED,
		// 等待  一直等
        WAITING,
		// 超时等待
        TIMED_WAITING,

    	//	终止
        TERMINATED;
    }

wait/sleep 区别

1、来自不同的类

wait =>Object

sleep => Thread

2、关于锁的释放

wait 会释放锁 sleep睡觉了,抱着锁睡觉了,不会释放!

3、使用范围是不同的

wait: 必须在同步代码块中使用

sleep 可以任何地方睡觉

4、是否要捕获异常

wait 要捕获异常

sleep 必须捕获异常

3、Lock锁(重点)

传统Synchronized 和 Lock 区别

1、 Synchronized 内置的JAVA关键字 Lock 是一个java类

2、 Synchronized 无法判断获取锁的状态Lock可以

3、 Synchronized 会自动释放锁Lock 必须手动释放锁 ,不释放锁就死锁

4、 Synchronized 线程1(获得锁,阻塞了) 线程2(一直等待);Lock 锁就不一定等下去

5、 Synchronized 可重入锁不可中断,非公平;Lock 可以重锁,可以判断 锁, 非公平(可以自己设置);

6、 Synchronized 适合锁少量的同步代码, Lock 适合锁大量的同步代码

// synchronized 本质 : 队列  锁

Synchronized wait

公平锁 先来后到

非公平锁 可以插队(默认)

锁是什么 如何判断锁的是谁

4、生产者和消费者的问题

面试常问

单例模式 排序算法 生产者消费者 死锁

生产者消费者

package com.lmq.pc;

/**
 * @author 羡鱼
 * @version 1.0
 * @date 2023/7/29 18:12
 * <p>
 * 线程之间的通讯  生产者 消费者  等待幻想  通知唤醒
 * 线程交替执行  A B 操作同一个变量  num = 0
 * A num=1
 * B num-1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "B").start();
    }
}


// 判断  等待   业务   通知
class Data {
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        if (number != 0) {
            this.wait();
        }
        number++;
        // 通知其他线程
        this.notify();
        System.out.println(Thread.currentThread().getName() + "=>" + number);
    }

    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {
            this.wait();
        }
        number--;
        this.notify();
        System.out.println(Thread.currentThread().getName() + "=>" + number);
    }
}

使用if判断会出现虚假唤醒 使用while

package com.lmq.pc;

/**
 * @author 羡鱼
 * @version 1.0
 * @date 2023/7/29 18:12
 * <p>
 * 线程之间的通讯  生产者 消费者  等待幻想  通知唤醒
 * 线程交替执行  A B 操作同一个变量  num = 0
 * A num=1
 * B num-1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "C").start();
    }
}


// 判断  等待   业务   通知
class Data {
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        while (number != 0) {
            this.wait();
        }
        number++;
        // 通知其他线程
        this.notify();
        System.out.println(Thread.currentThread().getName() + "=>" + number);
    }

    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            this.wait();
        }
        number--;
        this.notify();
        System.out.println(Thread.currentThread().getName() + "=>" + number);
    }
}

原来的 synchronized wait notify

juc的 (Lock)Lock ()await signal

juc 版本生产者消费者

Lock 找到 Condi

上锁 等待 通知 解锁

package com.lmq.pc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 羡鱼
 * @version 1.0
 * @date 2023/7/29 18:48
 */
public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "A").start();


        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "C").start();


        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }, "D").start();
    }
}

class Data2 {
    private int number = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public  void increment() throws InterruptedException {

        lock.lock();
        try {
            while (number != 0) {
                condition.await();
            }

            number++;
            // 通知其他线程
            condition.signalAll();
            System.out.println(Thread.currentThread().getName() + "=>" + number);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public  void decrement()   {
        lock.lock();
        try {
            while (number == 0) {
                condition.await();
            }
            number--;
            condition.signalAll();
            System.out.println(Thread.currentThread().getName() + "=>" + number);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

Lock 可以精准唤醒进程

可以设置多个监视器

5、8锁现象

如何判断锁的是谁!

  • s锁就是关于锁的8个问题
    1. 标准情况下 连个线程先打印 发短信还是打电话 1、发短信 2、打电话 都是phone 调用所以先发短信 后 打电话
    1. 发短信方法延迟四秒两个线程先打印? 发短信还是先
    1. 加入普通方法后先发短信还是普通方法? 执行普通方法
    1. 两个对象 两个同步方法 先发短信还是先打电话? 先打电话 对象不同 phone2 和phone1 各有一把锁
    1. 增加两个静态方法,只有一个对象,先打印 发短信 还是打电话? 发短信static 锁的是类 全局唯一
    1. 两个对象!增加两个静态的同步方法 先打印发短信还是打电话? 发短信 static 锁的是class class只有一个
    1. 一个是静态同步方法一个是普通同步方法, 一个一下先发育发短信还是打电话? 打电话
    1. 一个是静态同步方法一个是普通同步方法
package com.lmq.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author 羡鱼
 * @version 1.0
 * @date 2023/7/29 20:28
 *
 *
 *
 * s锁就是关于锁的8个问题
 * 1. 标准情况下 连个线程先打印 发短信还是打电话  1、发短信 2、打电话  都是phone 调用所以先发短信 后 打电话
 * 2. 发短信方法延迟四秒两个线程先打印? 发短信还是先
 * 3. 加入普通方法后先发短信还是普通方法?     执行普通方法
 * 4. 两个对象 两个同步方法  先发短信还是先打电话?   先打电话 对象不同 phone2 和phone1 各有一把锁
 * 5. 增加两个静态方法,只有一个对象,先打印 发短信 还是打电话?  发短信static 锁的是类 全局唯一
 * 6. 两个对象!增加两个静态的同步方法 先打印发短信还是打电话?    发短信 static 锁的是class class只有一个
 * 7. 一个是静态同步方法一个是普通同步方法, 一个一下先发育发短信还是打电话?  打电话
 * 8. 一个是静态同步方法一个是普通同步方法
 *
 */
public class Text1 {

    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(phone1::sendSms,"A").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(phone2::call,"B").start();

    }
}


class Phone{

    // synchronized  锁的对象是方法的调用者(在上面可以看到锁的phone phone调用的方法)!  两个方法用的同一个锁 谁先拿到谁先执行
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("sendSms");
    }
    public synchronized void call(){
        System.out.println("call");
    }

    public void hello() {
        System.out.println("hello");
    }
}

小结

new this 具体的一个手机

static Class 唯一的一个模版

6、集合类不安全

并发下 集合就不安全

package com.lmq.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author 羡鱼
 * @version 1.0
 * @date 2023/7/29 21:13
 */

// java.util.ConcurrentModificationException
public class ListTest1 {
    public  static void main(String[] args) {

        // 并发下 ArrayList 不安全
        // 解决方案
        // 1.  在ArrayList 前面就已经有 Vector()  在java 1.0 就有了
        // 2.  Collections.synchronizedList(new ArrayList<>())
        // 3.  new CopyOnWriteArrayList<>();  JUC方案
        //   CopyOnWrite 写入时复制  COW 计算机程序设计领域的一种优化策略
        //   多线程调用的时候 List 读取的时候 固定的写入(覆盖)
        //  在写入的时候避免覆盖  造成数据问题
        //  在写入的时候避免覆盖 造成数据问题
        // 读写分离


        // CopyOnWriteArrayList<>() 比 Vector()  强在哪里  只要用synchronized效率就比lock锁底

//        ArrayList<String> list = new ArrayList<>();
//        List<String> list = Collections.synchronizedList(new ArrayList<>()) ;
        List<String> list = new CopyOnWriteArrayList<>();

        for (int i=0;i<=10;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

学习方法推荐 1.先学会用 2.货比三家寻找解决方案 3.分析源码

Set 方法不安全

package com.lmq.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @author 羡鱼
 * @version 1.0
 * @date 2023/7/30 10:14
 */
public class SetTest {
    public static void main(String[] args) {
//        HashSet<String> set = new HashSet<>();
        //  java.util.ConcurrentModificationException
//       方案一 Set<Object> set = Collections.synchronizedSet(new HashSet<>());
//      方案二  Set<String> set = new CopyOnWriteArraySet<>();
        Set<String> set = new CopyOnWriteArraySet<>();


        for (int i =1;i<=10;i++){
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

hashSet 底层是什么?

就是HashMap

7、Map 不安全

package com.lmq.unsafe;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @author 羡鱼
 * @version 1.0
 * @date 2023/7/30 10:50
 */
public class MapTest {
    public static void main(String[] args) {
        // 什么是map  是怎么用的  等价于什么
        // 不用  工作中不用HashMap
        // 默认等价于什么  new HashMap<>(16,0.75)
//        Map<String, String> map = new HashMap<>();
//        Map map = Collections.synchronizedMap(new HashMap<String,String>());
        Map<String, String> map = new ConcurrentHashMap<>();
        // 加载因子  初始化容量

        for (int i = 0; i <= 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }

    }
}

7、 Callable (简单)

Thread 只能接受 Runable

  • new thread(new Runnable()).start();
  • new Thread(new FutureTask()).start();
  • 两个对等 FutureTask是Runnable的实现类
  • new Thread(new FutureTask()).start();
package com.lmq.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author 羡鱼
 * @version 1.0
 * @date 2023/7/30 11:38
 */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // new thread(new Runnable()).start();
        // new Thread(new FutureTask<V>()).start();
        // 两个对等 FutureTask是Runnable的实现类
//        new Thread(new FutureTask<V>()).start();


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

        FutureTask futureTask = new FutureTask(thread);

        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start(); // 细节 结果有缓存 只打印一个hello   结果可能需要等待 会阻塞
        Object o = futureTask.get(); // 获取Callable 返回值
        System.out.println(o);
    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("hello");
        return 1024;
    }
}