多线程

多线程

一、多线程的基本概念

1.1、进程和线程区别

  • 进程
    是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  • 线程
    进程内部的一个独立执行单元;一个进程可以同时并发的运行多个线程,可以理解为一个进程便相当于一个单 CPU 操作系统,而线程便是这个系统中运行的多个任务。
  • 总结
    进程是资源分配最小单位,线程是程序执行的最小单位。

1.2、为什么需要使用到多线程

采用多线程的形式执行代码,目的就是为了提高程序的效率。
比如:现在一个项目只有一个程序员开发,需要开发功能模块用户模块、支付模块、课程模块,多人协同同时开发。

1.3、并行/串行区别

串行也就是单线程执行 代码执行效率非常低,代码从上向下执行。
并行就是多个线程并行一起执行,效率比较高。

1.4、使用多线程一定提高效率吗?

不一定,需要了解cpu调度的算法,多线程执行需要同时执行,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务

  • 上下文切换
    从该线程执行切换到另外的线程 该线程—运行切换为就绪状态。
  • 线程池数量
    取决于服务器cpu的核数

1.5、同步与异步的区别

同步概念:就是代码从上向下执行。
异步的概念:单独分支执行 相互之间没有任何影响。

1.6、并发和并行

  • 并发
    同时发生事情开始处理 看起来同时执行
  • 并行
    操作系统同时执行多个逻辑 真正的同时

二、多线程创建方式

  • 1、继承Thread类创建线程
  • 2、实现Runnable接口创建线程
  • 3、使用匿名内部类的形式创建线程
  • 4、使用Lambda表达式创建线程
  • 5、使用Callable和Future创建线程
  • 6、使用线程池例如用Executor框架
  • 7、Spring @Async异步注解 结合线程池

2.1、继承Thread类创建线程

public class Thread01 extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我是子线程: " + Thread.currentThread().getName());
        if ("Thread-2".equals(Thread.currentThread().getName())) {
            System.out.println("我是在子线程Thread-2里执行的");
            System.out.println(1/0);
        }
    }

    public static void main(String[] args) {
        new Thread01().start();
        new Thread01().start();
        new Thread01().start();
        new Thread01().start();
        new Thread01().start();
        System.out.println("我是主线程: " + Thread.currentThread().getName());
    }
}

2.2、实现Runnable接口创建线程

public class ThreadRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我是子线程: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new Thread(new ThreadRunnable()).start();
        new Thread(new ThreadRunnable()).start();
        new Thread(new ThreadRunnable()).start();
        new Thread(new ThreadRunnable()).start();
        new Thread(new ThreadRunnable()).start();
        System.out.println("我是主线程: " + Thread.currentThread().getName());

        //匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是匿名内部类子线程: " + Thread.currentThread().getName());
            }
        }).start();

        //lambda
        new Thread(() -> System.out.println("我是lambda子线程: " + Thread.currentThread().getName())).start();
        new Thread(() -> System.out.println("我是lambda子线程: " + Thread.currentThread().getName())).start();
        new Thread(() -> System.out.println("我是lambda子线程: " + Thread.currentThread().getName())).start();
    }
}

2.3、使用匿名内部类的形式创建线程

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "我是子线程");
    }
}).start();

2.4、使用Lambda表达式创建线程

在这里插入图片描述

new Thread(() -> System.out.println(Thread.currentThread().getName() + "我是子线程")).start();

2.5、使用Callable和Future创建线程

public class ThreadCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "执行计算操作");
        return 1 + 2;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadCallable threadCallable = new ThreadCallable();
        FutureTask<Integer> integerFutureTask = new FutureTask<>(threadCallable);
        new Thread(integerFutureTask).start();
        Integer result = integerFutureTask.get();
        System.out.println("result = " + result);
    }
}

2.6、使用线程池例如用Executor框架

public class ThreadExecutors {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "我是子线程");
            }
        });
    }
}

2.7、Spring @Async异步注解 结合线程池

@Component
@Slf4j
public class LogManage {
    public static void asyncLog() {
        try {
            Thread.sleep(3000L);
            log.info("2");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
@Slf4j
@RestController
public class ThreadAsync {
    @RequestMapping("/addLog")
    public String addLog() {
        log.info("1");
        LogManage.asyncLog();
        log.info("3");
        return "3";
    }
}

测试地址:http://localhost:8080/addLog
在这里插入图片描述
在这里插入图片描述

三、主线程, 子线程, 用户线程, 守护线程

  • 主线程
    main方法, java内置的, 线程运行, 需要首先运行主线程
  • 子线程,用户线程
    我们自定义的线程也就是new thread或者实现runnable接口创建的线程.
    特点
    主线程执行结束, 不影响子线程或者叫做用户线程的运行
  • 守护线程
    当用户线程或者子线程调用thread.setDaemon(true);方法的时候, 这个用户线程变为守护线程
    特点:
    主线程执行结束, 守护线程也随主线程一起结束.

四、线程安全问题

4.1、什么是线程安全问题

多个线程一起操作一个这些个线程共同使用的变量, 会造成这个变量中的数据的混乱, 这就是线程安全问题

当多个线程共享同一个全局变量,做写的操作时,可能会受到其他的线程干扰,发生线程
安全问题。

public class ThreadCount implements Runnable {
    private static Integer count = 10;

    @Override
    public void run() {
        while (count > 1) {
            cal();
        }
    }

    private void cal() {
        try {
            Thread.sleep(20);
        } catch (Exception e) {

        }
        count--;
        System.out.println(Thread.currentThread().getName() + "," + count);
    }

    public static void main(String[] args) {
        ThreadCount threadCount = new ThreadCount();
        Thread thread1 = new Thread(threadCount);
        Thread thread2 = new Thread(threadCount);
        thread1.start();
        thread2.start();
    }
}

在这里插入图片描述

4.2、如何解决线程安全的问题

核心思想:上锁 分布式锁

在同一个jvm中,多个线程需要竞争锁的资源,最终只能够有一个线程
能够获取到锁,多个线程同时抢同一把锁,谁(线程)能够获取到锁,
谁就可以执行到该代码,如果没有获取锁成功 中间需要经历锁的升级过程
如果一致没有获取到锁则会一直阻塞等待。
如果线程A获取锁成功 但是线程A一直不释放锁
线程B一直获取不到锁,则会一直阻塞等待。

代码从那一块需要上锁?-----可能会发生线程安全性问题的代码需要上锁。
Juc并发编程 锁 重入锁 悲观锁 乐观锁 公平锁 非公平锁
线程0 线程1 同时获取 this锁,假设线程0 获取到this ,意味着线程1没有获取到锁
则会阻塞等待。等我们线程0 执行完count-- 释放锁之后 就会唤醒 线程1从新进入
到获取锁的资源。

获取锁与释放锁 全部都是有底层虚拟机实现好了。

对一块代码加锁缺点:可能会影响到程序的执行效率。

如果是同一把锁 在多线程的情况下 最终只能够给一个线程使用。如果有线程持有了该锁 意味着其他的线程 不能够在继续获取锁。

核心思想:当多个线程共享同一个全局变量时,将可能会发生线程安全的代码上锁,保证只有拿到锁的线程才可以执行,没有拿到锁的线程不可以执行,需要阻塞等待。

1、使用synchronized锁,JDK1.6开始 锁的升级过程 juc 18-25
2、使用Lock锁 ,需要自己实现锁的升级过程。底层是基于aqs实现
3、使用Threadlocal,需要注意内存泄漏的问题。
4、原子类 CAS 非阻塞式
请添加图片描述

4.3、synchronized锁的基本用法

在多线程的情况下 需要是同一个对象锁
Synchronized(对象锁) {
需要保证线程安全的代码
}

Object lock = new Object();
//加锁, 加锁和解锁可以自动控制, 重量级锁
synchronized (lock) {
    ........
}

1、修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码快前要获得 给定对象 的锁。
2、修饰实例方法,作用于当前实例加锁,进入同步代码前要获得 当前实例 的锁
3、修饰静态方法,作用于当前类对象(当前类.class)加锁,进入同步代码前要获得 当前类对象 的锁

4.3.1、修饰代码块

修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得 给定对象 的锁。

public class ThreadCount implements Runnable {
    private static Integer count = 10;

    @Override
    public void run() {
        while (count > 1) {
            call();
        }
    }

    private void call() {
        synchronized (this) {
            try {
                Thread.sleep(20);
            } catch (Exception e) {

            }
            count--;
            System.out.println(Thread.currentThread().getName() + "," + count);
        }
    }

    public static void main(String[] args) {
        ThreadCount threadCount = new ThreadCount();
        Thread thread1 = new Thread(threadCount);
        Thread thread2 = new Thread(threadCount);
        thread1.start();
        thread2.start();
    }
}

4.3.2、修饰实例方法

修饰实例方法,作用于当前实例加锁,进入同步代码前要获得 当前实例的锁
在实例方法上默认加上synchronized 默认使用this锁。

public class ThreadCount implements Runnable {
    private static Integer count = 10;

    @Override
    public void run() {
        while (count > 1) {
            call();
        }
    }

    private synchronized void call() {
        try {
            Thread.sleep(20);
        } catch (Exception e) {

        }
        count--;
        System.out.println(Thread.currentThread().getName() + "," + count);
    }

    public static void main(String[] args) {
        ThreadCount threadCount = new ThreadCount();
        Thread thread1 = new Thread(threadCount);
        Thread thread2 = new Thread(threadCount);
        thread1.start();
        thread2.start();
    }
}

4.3.3、修饰静态方法

修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得 当前类对象的锁
默认使用当前类的类名.class 锁

public class ThreadCount implements Runnable {
    private static Integer count = 10;

    @Override
    public void run() {
        while (count > 1) {
            call();
        }
    }

    private static void call() {
        synchronized (ThreadCount.class) {
            try {
                Thread.sleep(20);
            } catch (Exception e) {

            }
            count--;
            System.out.println(Thread.currentThread().getName() + "," + count);
        }
    }

    public static void main(String[] args) {
        ThreadCount threadCount = new ThreadCount();
        Thread thread1 = new Thread(threadCount);
        Thread thread2 = new Thread(threadCount);
        thread1.start();
        thread2.start();
    }
}

4.3.4、synchronized死锁问题

我们如果在使用synchronized 需要注意 synchronized锁嵌套的问题 避免死锁的问题发生。

所以解决死锁就是锁不能够嵌套使用

案例:

public class DeadlockThread implements Runnable {
    private int count = 1;
    private String lock = "lock";

    @Override
    public void run() {
        while (true) {
            count++;
            if (count % 2 == 0) {
                // 线程1需要获取 lock 在获取 a方法this锁
                // 线程2需要获取this 锁在 获取B方法lock锁
                synchronized (lock) {
                    a();
                }
            } else {
                synchronized (this) {
                    b();
                }
            }
        }
    }

    public synchronized void a() {
        System.out.println(Thread.currentThread().getName() + ",a方法...");
    }

    public void b() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + ",b方法...");
        }
    }

    public static void main(String[] args) {
        DeadlockThread deadlockThread = new DeadlockThread();
        Thread thread1 = new Thread(deadlockThread);
        Thread thread2 = new Thread(deadlockThread);
        thread1.start();
        thread2.start();
    }
}
  • 线程1 先获取到自定义对象的lock锁,进入到a方法需要获取this锁
  • 线程2 先获取this锁,进入到b方法需要自定义对象的lock锁

线程1 线程2 是在同时执行

线程1线程2
先获取到自定义对象的lock锁先获取this锁
需要线程2已经持有的this锁线程1已经持有自定义对象的lock锁

4.3.5、springmvc 接口中使用

需要注意:
Spring MVC Controller默认是单例的 需要注意线程安全问题
单例的原因有二:
1、为了性能。
2、不需要多例。
@Scope(value = “prototype”) 设置为多例子。

@RestController
@Slf4j
public class CountController {
    private int count = 0;

    @RequestMapping("/count")
    public synchronized String count() {
        try {
            log.info(">count<" + count++);
            try {
                Thread.sleep(3000);
            } catch (Exception e) {

            }
        } catch (Exception e) {

        }
        return "count";
    }
}

4.3.6、临界区

  • 当多个线程读共享资源 读的过程中,没有任何问题,
  • 在多个线程对共享资源读写操作时发生指令交错,就会发生线程安全问题
  • 在多线程中如果存在对共享资源读写操作,该代码称作为临界区。
public class Thread05 implements Runnable {
    private Integer count = 0;

    @Override
    public void run() {
        // 该代码就是为临界区
        count++;
    }
}

4.3.7、竞争条件

多个线程在临界区内执行,由于代码的执行序列不同(指令)而导致结果无法预测,称之为发生了竞态条件
解决办法:
synchronized,Lock、原子类

4.4、字节码角度分析线程安全问题

4.4.1、线程安全问题:

  • 字节码
  • 上下文切换
  • Jmm java内存模型

Java源代码 →编译成class文件

4.4.2、线程安全代码

public class Thread07 extends Thread {
    private static int sum = 0;
    @Override
    public void run() {
       sum();
    }

    private void sum() {
        for (int i = 0; i < 10000; i++) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {

五、多线程线程之间通讯

5.1、等待/通知机制

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法如下:

  • notify() :通知一个在对象上等待的线程,使其从main()方法返回,而返回的前提是该线程获取到了对象的锁
  • notifyAll():通知所有等待在该对象的线程
  • wait():调用该方法的线程进入WAITING状态,只有等待其他线程的通知或者被中断,才会返回。需要注意调用wait()方法后,会释放对象的锁 。

5.2、wait/notify/notifyAll在Object类中

因为我们在使用synchronized锁 对象锁可以是任意对象,所以wait/notify/notifyAll需要放在Object类中。

5.3、wait/notify/简单的用法

public class Thread08 extends Thread {
    @Override
    public void run() {
       synchronized (this){
           try {
               System.out.println(Thread.currentThread().getName() +  "当前线程阻塞,同时释放锁");
               this.wait();
               System.out.println("run");
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }

    public static void main(String[] args) {
        Thread08 thread = new Thread08();
        thread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (thread){
            // 唤醒正在阻塞的线程
            thread.notify();
        }
    }
}

5.4、多线程通讯实现生产者与消费者

public class Thread09 {
    class Res {
        private String userName;
        private char sex;
        private boolean flag = false;
    }

    class InputThread extends Thread {
        private Res res;

        public InputThread(Res res) {
            this.res = res;
        }

        @Override
        public void run() {
            int count = 0;
            while (true) {
                synchronized (res) {
                    //flag = false  写入输入 flag = true 则不能写入数据 只能读取数据
                    try {
                        if (res.flag) {
                            res.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (count == 0) {
                        this.res.userName = "徐先生";
                        this.res.sex = '男';
                    } else {
                        this.res.userName = "柳岩";
                        this.res.sex = '女';
                    }
                    res.flag = true;
                    res.notify();
                }
                count = (count + 1) % 2;
            }
        }
    }

    class OutThread extends Thread {
        private Res res;

        public OutThread(Res res) {
            this.res = res;
        }

        @Override
        public void run() {
            while (true) {
                synchronized (res) {
                    try {
                        if (!res.flag) {
                            res.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(res.userName + "--" + res.sex);
                    res.flag = false;
                    res.notify();

                }
            }
        }
    }

    public static void main(String[] args) {
        new Thread09().print();
    }

    private void print() {
        Res res = new Res();
        InputThread inputThread = new InputThread(res);
        OutThread outThread = new OutThread(res);
        inputThread.start();
        outThread.start();
    }
}

flag 默认值==false
flag false 输入线程 输入值 输出线程 先拿到锁 释放锁
flag true 输出线程 输出值

public boolean flag = false;

5.5、Join/Wait与sleep之间的区别

sleep(long)方法在睡眠时不释放对象锁
join(long)方法先执行另外的一个线程,在等待的过程中释放对象锁 底层是基于wait封装的,
Wait(long)方法在等待的过程中释放对象锁

5.6、三个线程 T1,T2,T3,怎么确保它们按顺序执行?

public class Thread10 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (Exception e) {

            }
            System.out.println(Thread.currentThread().getName() + ",线程执行");

        }, "t1");

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (Exception e) {

            }
            System.out.println(Thread.currentThread().getName() + ",线程执行");

        }, "t2");

        Thread t3 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (Exception e) {

            }
            System.out.println(Thread.currentThread().getName() + ",线程执行");

        }, "t3");

        t1.start();
        t2.start();
        t3.start();
    }
}

每次不同
在这里插入图片描述

public class Thread10 {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ", 我是子线程");
            }
        });
        Thread thread2 = new Thread(() -> {
            try {
                thread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ", 我是子线程");
        });
        Thread thread3 = new Thread(() -> {
            try {
                thread2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ", 我是子线程");
        });
        Thread thread4 = new Thread(() -> {
            try {
                thread3.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ", 我是子线程");
        });
       thread1.start();
       thread2.start();
       thread3.start();
       thread4.start();
    }
}

在这里插入图片描述

5.7、Join的底层原理如何实现

public class Thread11 {
    private Object object = new Object();

    public static void main(String[] args) {
        Thread11 thread11 = new Thread11();
        Thread thread = thread11.print();
        thread.start();
        try {
            Thread.sleep(3000);
            //中断线程
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private Thread print() {
        Thread thread = new Thread(() -> {
            synchronized (object) {
                System.out.println("1");
                try {
                    object.wait(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("2");
            }
        });
        return thread;
    }
}

join底层原理是基于wait封装的,唤醒的代码在jvm Hotspot 源码中,当jvm在关闭线程之前会检测线阻塞在t1线程对象上的线程,然后执行notfyAll(),这样t2就被唤醒了。

六、多线程知识高级部分

6.1、多线程七种执行的状态

  • 初始化状态
  • 就绪状态
  • 运行状态
  • 死亡状态
  • 阻塞状态
  • 超时等待
  • 等待状态
start()		//调用start()方法会使得该线程开始执行,正确启动线程的方式。
wait() 		//调用wait()方法,进入等待状态,释放资源,让出CPU。需要在同步快中调用。
sleep()		//调用sleep()方法,进入超时等待,不释放资源,让出CPU
stop()		//调用sleep()方法,线程停止,线程不安全,不释放锁导致死锁,过时。
join()		//调用sleep()方法,线程是同步,它可以使得线程之间的并行执行变为串行执行。
yield()		//暂停当前正在执行的线程对象,并执行其他线程,让出CPU资源可能立刻获得资源执行。
yield()		//目的是让相同优先级的线程之间能适当的轮转执行
notify()	//在锁池随机唤醒一个线程。需要在同步快中调用。
nnotifyAll()//唤醒锁池里所有的线程。需要在同步快中调用。

sleep 主动释放cpu执行权 休眠一段时间
运行状态→限时等待状态
限时等待状态→就绪状态→运行状态

synchronized 没有获取到锁 当前线程变为阻塞状态
如果有线程释放了锁,唤醒正在阻塞没有获取到锁的线程
从新进入到获取锁的状态

wait() 运行—等待状态

notify() 等待状态–阻塞状态(没有获取到锁的线程 队列)—就绪状态→运行状态
在这里插入图片描述

6.2、守护线程与用户线程

java中线程分为两种类型:用户线程和守护线程。通过Thread.setDaemon(false)设置为用户线程;通过Thread.setDaemon(true)设置为守护线程。如果不设置次属性,默认为用户线程。

  • 守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。
  • 用户线程是独立存在的,不会因为其他用户线程退出而退出。
public class Thread01 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "我是子线程");
        });
        /**
         * 1.setDaemon 设置为true 守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程
         * 2.setDaemon 设置为false 用户线程是独立存在的,不会因为其他用户线程退出而退出。
         */
        //thread.setDaemon(true);
        thread.setDaemon(false);
        thread.start();
        System.out.println("我是主线程,代码执行完毕");
    }
}

6.3、多线程yield

主动释放cpu执行权

  • 多线程yield 会让线程从运行状态进入到就绪状态,让后调度执行其他线程。
  • 具体的实现依赖于底层操作系统的任务调度器
public class Thread02 extends Thread{
    public Thread02(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            if (i == 30){
                System.out.println(Thread.currentThread().getName() + ", 释放cpu执行权.");
                this.yield();
            }
            System.out.println(Thread.currentThread().getName() + "," + i);
        }
    }

    public static void main(String[] args) {
        new Thread02("t1").start();
        new Thread02("t2").start();
    }
}

6.4、多线程优先级

  • 在java语言中,每个线程都有一个优先级,当线程调控器有机会选择新的线程时,线程的优先级越高越有可能先被选择执行,线程的优先级可以设置1-10,数字越大代表优先级越高
    注意:Oracle为Linux提供的java虚拟机中,线程的优先级将被忽略,即所有线程具有相同的优先级。
    所以,不要过度依赖优先级。
  • 线程的优先级用数字来表示,默认范围是1到10,即Thread.MIN_PRIORITY到Thread.MAX_PRIORTY.一个线程的默认优先级是5,即Thread.NORM_PRIORTY
  • 如果cpu非常繁忙时,优先级越高的线程获得更多的时间片,但是cpu空闲时,设置优先级几乎没有任何作用。
public class Thread03 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            int count = 0;
            while (true) {
                System.out.println(Thread.currentThread().getName() + "," + count++);
            }
        }, "t1线程");

        Thread t2 = new Thread(() -> {
            int count = 0;
            while (true) {
                System.out.println(Thread.currentThread().getName() + "," + count++);
            }
        }, "t2线程");

        t1.setPriority(Thread.MAX_PRIORITY);
        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
    }
}

6.5、sleep防止CPU占用100%

sleep(long millis) 线程睡眠 millis 毫秒
sleep(long millis, int nanos) 线程睡眠 millis 毫秒 + nanos 纳秒
使用sleep方法避免cpu空转 防止cpu占用100%

public class Thread04 {
    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

6.6、如何安全的停止一个线程

6.6.1、调用stop方法

stop:中止线程,并且清除监控器锁的信息,但是可能导致 线程安全问题,JDK不建议用(不推荐使用Thread.stop, 这种终止线程运行的方法已经被废弃,使用它们是极端不安全的!)

6.6.2、使用interrupt()方法中断线程。

Interrupt 打断正在运行或者正在阻塞的线程。
1.如果目标线程在调用Object class的wait()、wait(long)或wait(long, int)方法、join()、join(long, int)或sleep(long, int)方法时被阻塞,那么Interrupt会生效,该线程的中断状态将被清除,抛出InterruptedException异常。
2.如果目标线程是被I/O或者NIO中的Channel所阻塞,同样,I/O操作会被中断或者返回特殊异常值。达到终止线程的目的。
如果以上条件都不满足,则会设置此线程的中断状态。
打断正在阻塞的线程

public class Thread05 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1");
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打断子线程 ");
        //调用interrupt 打断正在阻塞的线程
        t1.interrupt();
        System.out.println("获取打断标记:" + t1.isInterrupted());
    }
}

打断正在运行的线程

public class Thread06 extends Thread {
    @Override
    public void run() {
        while (true) {
            // 如果终止了线程,则停止当前线程
            if (this.isInterrupted()) {
                break;
            }
        }
    }

    public static void main(String[] args) {
        Thread06 thread06 = new Thread06();
        thread06.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打断子线程 ");
        //调用interrupt 打断正在阻塞的线程
        thread06.interrupt();
    }
}

6.6.3、设置退出标志,使线程正常退出。

在上方代码逻辑中,增加一个判断,用来控制线程执行的中止。

public class Thread07 extends Thread {
    private volatile boolean isFlag = true;
    @Override
    public void run() {
        while (isFlag){
        }
    }

    public static void main(String[] args) {
        Thread07 thread07 = new Thread07();
        thread07.start();
        thread07.isFlag= false;
    }
}

6.7、Lock锁的基本使用

在jdk1.5后新增的ReentrantLock类同样可达到此效果,且在使用上比synchronized更加灵活
相关API:
使用ReentrantLock实现同步
lock()方法:上锁
unlock()方法:释放锁
使用Condition实现等待/通知 类似于 wait()和notify()及notifyAll()
Lock锁底层基于AQS实现,需要自己封装实现自旋锁。

Synchronized —属于JDK 关键字 底层属于 C++虚拟机底层实现
Lock锁底层基于AQS实现-- 变为重量级
Synchronized 底层原理—锁的升级过程
Lock 过程中 注意 获取锁 释放锁

6.7.1、ReentrantLock用法

public class Thread08 implements Runnable {
    private int count = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                // 释放锁
                lock.lock();
                if (count > 1) {
                    count--;
                    System.out.println(Thread.currentThread().getName() + "," + count);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        Thread08 thread08 = new Thread08();
        Thread t1 = new Thread(thread08);
        Thread t2 = new Thread(thread08);
        t1.start();
        t2.start();
    }
}

6.7.2、Condition用法

  • wait()是属于synchronized
  • await()是属于lock
public class Thread09 {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread09 thread09 = new Thread09();
        try {
            thread09.print();
            Thread.sleep(3000);
            thread09.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private void print() {
        new Thread(() -> {
            try {
                // 释放锁 同时当前线程阻塞
                lock.lock();
                System.out.println(Thread.currentThread().getName() + ",1");
                condition.await();
                System.out.println(Thread.currentThread().getName() + ",2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();
    }

    private void signal() {
        try {
            lock.lock();
            condition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

七、多线程编程三大特性

7.1、原子性

即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行

问题产生原因
如果多线程操作, 转账, A线程负责从一个账户中扣款, B线程负责向另一个账户加款.

这两个线程必须连续运行, 运行中如果被突然打断, 会造成账户钱数错误.

7.2、可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值

问题产生原因
java无法操作主内存(物理内存)中的数据, 都是由JMM控制. 如果多线程中使用共享变量, 那么按照JMM原理, 就是主内存中的数据会被JMM拷贝出副本分别放到线程A和线程B的本地内存中, 供这两个线程使用, 如果线程A改了本地内存中的共享变量数据, 那么这个数据会被JMM修改到主内存中, 但是线程B不知道主内存中的数据已经修改, 使用的还是本地内存中共享变量中的老数据. 会造成内存可见性问题.

7.3、有序性

程序执行的顺序按照代码的先后顺序执行

问题产生原因
我们写得代码是.java的文件, 叫做源代码, jvm执行的是由java编译器将.java的文件编译后产生的.class文件也就是字节码, 字节码文件中是jvm可执行的指令集. jvm对指令集会自动进行优化, 调整指令的顺序, 为了执行更快速.

有的代码顺序的调整不会对结果产生影响无所谓, 有些代码那么指令集被jvm自动优化后, 执行先后顺序调整后, 会造成执行结果的错误. 也就是jvm中的指令重排机制会导致代码有序性问题, 最终导执行结果错误.

7.4、解决多线程三大特性问题方法

  • synchronized(不太推荐使用, 重量级)

    重量级锁, 可以解决多线程编程中的原子性, 可见性, 有序性问题.

    使用synchronized之后, JVM不会对编译后的执行进行执行重排优化, 保证了字节码指令的顺序

    使用synchronized之后, 可见性问题得以解决

    使用synchronized之后, 原子性问题得到解决.

  • volatile关键字(推荐使用, 轻量级)

    ​ volatile关键字修饰多线程共同使用的共享变量, 可以解决可见性和有序性问题.

    ​ 但是volatile关键字无法解决原子性问题.

  • ReentrantLock可重入锁(推荐使用, 轻量级)

    ReentrantLock可重入锁可以解决, 可见性, 有序性, 原子性问题

  • 使用JUC中的Atomic类型(推荐使用, 轻量级)

    可以解决原子性, 有序性, 可见性问题

    CAS原理(重点)

7.5、cas原理和作用(重点)

CAS,Compare And Swap,即比较并交换。是java中多线程编程的基石, 是jdk中的JUC包的原理, JUC包中基本上所有工具类都使用了CAS+volatile关键字开发完成的.

CAS是无锁的, 所以速度比较快, 利用内存的比较并且交换的原理, 保证数据的原子性

加上volatile关键字也就相当于解决了在无锁状态下, 使多线程共享使用的的变量,具有原子性, 可见性, 有序性. 速度非常快.

原理: 内存值V、旧的预期值A、即将更新的值B

if(V == A) {
    V = B
    return true;
} else {
    return false;
}

7.6、ABA问题(重点)

问题描述:

多线程共享使用的变量z的值是A, 如果线程M将z的值改成B, 再改成A, 那么线程N无法发现线程M更改过变量z的值. 因为z的初始值是A, 后来又被从新改成值A, 所以其他线程无法发现. 这是CAS的ABA问题.

八、面试可能问到的多线程问题(重要)

问题描述:

是否知道JUC包及作用?

解答:

了解一些, JUC包是jdk中为我们多线程编程准备的一些工具类和工具方法

JUC包的底层原理就是volatile关键字和CAS原理开发而成的

volatile是jdk底层提供的关键字, 这个关键字的作用就是解决多线程, 一起使用一个共享的变量数据造成的可见性和有序性问题的关键字, 使用volatile关键字修饰的变量, 在多线程一起使用这个变量的时候, 不会发生呢可见性和有序性问题.

​ 多线程使用一个共享变量, 有可能会造成这个共享变量中的数据错误, 原因是多个线程一起修改这个变量的值会造成错乱, 造成原子性问题. 解决方式是, 最早使用jdk提供的synchronized关键字解决, 虽然能够解决问题, 但是效率低. JUC包中有atomic开头的原子操作类供我们使用, 使用这些原子操作类既能够保证共享数据的原子性, 速度又快. 底层使用CAS研发而成, CAS就是内存的比较交换技术. 因为JMM(java内存模型)规定, java中操作数据操作的不是主内存中的数据, 而是本地内存中的数据. 一个线程更改了本地内存中的数据和主内存中的数据, 另一个线程不知道. CAS是在无锁状态下, 利用本地内存和主内存中的数据进行对比的原理, 解决多线程共享使用数据的原子性问题. 速度快, 效率高.

JUC中提供了线程池, 原子操作类, 锁等工具类和方法供我们使用.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值