JavaSE-多线程

简单描述Java多线程基础

1 什么是多线程

多线程,就是多个任务同时进行,提高程序的运行效率和CPU的利用率,也叫并发。

  • 一个程序可能包含多个可以同时运行的任务。线程是指一个任务从头到尾的执行流程。

2 线程的状态

  • 创建状态: 对象已经生成(new出来了),但没有调用start方法,处于创建状态。
  • 就绪状态: 调用start方法后,向CPU和内存申请到资源了,处于就绪状态。(从sleep与wait等恢复也为就绪状态)。
  • 运行状态:线程调度将当前线程设置为当前线程,这时就是运行状态,开始运行run方法的代码。
  • 阻塞状态:线程在运行的过程中暂停/等待,即阻塞。
  • 死亡状态: run方法调用结束或者被调用interrupt方法,线程死亡,无法调用start方法。

3 线程创建

  1. 继承 Thread类, 重写run方法。
public class Thread_Test extends Thread{
    @Override
    public void run() {
        System.out.println("线程已启动");
    }
}
  1. 实现Runnable接口,重写run方法。
public class Thread_Test implements Runnable{
    @Override
    public void run() {
        System.out.println("这是线程");
    }
}
  1. 实现Callable<返回值> 接口, 重写call方法。

4 线程启动与中断

  1. 线程启动:直接调用start方法,不要调用run方法!!!!
  2. 线程中断:
    强制暂停(suspend)和停止(stop),已经弃用。
    需要停止一条线程时,应使用interrupt()方法请求终止线程,调用该方法时,该线程进入中断状态(Boolean标志,用于检查线程状态),注意此时并不会中断线程的运行,我们需要的线程中断是以下状态:
  • 线程被阻塞:调用Object.wait()、Thread.sleep()或Thread.join();
  • 调用interrupt方法,此时线程被中断,抛出InterruptedExpectation错误。

示例:

    public static void main(String[] args) {
        int cnt = 0;
        Runnable t = () -> {
            try{
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("running");
                    Thread.currentThread().interrupt();
                };
                if (Thread.currentThread().isInterrupted()) {
                    Thread.sleep(1000);
                }

            }catch (InterruptedException e){
                System.out.println("线程已结束");
            }

        };
        Thread thread = new Thread(t);
        thread.start();
       // thread.wait();
    }

5 线程锁

1 乐观锁和悲观锁

  1. 悲观锁:用一切悲观的形态来看待数据,在多个线程对同一数据进行操作的时候,就会进入竞态,竞争到的线程给数据加锁,直到完成数据的修改才释放,这时剩余等待的线程继续进入竞态。
  2. 乐观锁:用乐观的态度去应对数据,认为数据不会受到影响,在对数据完成修改之后才会对数据进行校验,如果不匹配则会发生冲突,不会修改数据。

2 原子操作

  1. 什么是原子操作:一个或者多个操作在CPU执行过程中不会被中断的特性,分为CPU指令级别和高级语言级别。

  2. 原子操作实现方式:锁和自旋CAS。

    • 悲观锁的实现方式:方法锁和类名锁。
    • 关键字:synchronized
  • 类名锁:将类名作为一把锁,当有线程调用的时候将类名上锁,别处调用同样的类名锁时会进入等待状态,直到调用结束释放锁。

例子:

    static int cnt = 0;
    public static void main(String[] args) throws InterruptedException {
        LinkedList <Thread> threads = new LinkedList<>();

        for (int i = 0; i < 100; i++) {
            threads.add(new Thread(() -> {
                for (int j = 0; j < 10; j++)
                    synchronized (Main.class) {
                        cnt++;
                    }
            }));
        }

        for (var t : threads)
            t.start();
        for (var t : threads)
            t.join();

        System.out.println(cnt);
    }
  • 方法锁:将方法作为一把锁,调用该方法时,只允许一条线程调用,等到方法调用结束后才可以进行下一步的调用。

例子:

    static int cnt = 0;
    public static void main(String[] args) throws InterruptedException {
        LinkedList<Thread> threads = new LinkedList<>();

        for (int i = 0; i < 100; i++) {
            threads.add(new Thread(() -> {
                for (int j = 0; j < 10; j++)
                    add();
            }));
        }

        for (var t : threads)
            t.start();
        for (var t : threads)
            t.join();

        System.out.println(cnt);
    }

    private static synchronized void add(){
        cnt++;
    }

3 死锁

  1. 什么是死锁:两个锁被调用,相互阻塞等待,进入僵持状态。
  2. 例子:
    static Object o1 = new Object();
    static Object o2 = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (o1) {
                    System.out.println("拿到锁1了,在等待锁2");
                    synchronized (o2) {
                        System.out.println("拿到锁2了!");
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (o2) {
                    System.out.println("拿到锁2了,在等待锁1");
                    synchronized (o1) {
                        System.out.println("拿到锁1了!");
                    }
                }
            }
        });

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

通过上例不难发现,线程t1会在拿到锁1后继续拿锁2,线程t2会在拿到锁2后继续拿锁1,但都被对方占用了,两个线程一直处于等待状态,形成死锁。

  1. 怎么发现程序中的死锁:

    通过jconsole查看

-Djava.rmi.server.hostname=127.0.0.1 
-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.port=8888 
-Dcom.sun.management.jmxremote.ssl=false 
-Dcom.sun.management.jmxremote.authenticate=false

6 线程等待和唤醒

6.1 Object.wait()方法(线程等待)

  • 使当前线进入等待状态并且释放线程锁
  • 仅在Object作为类锁的时候可用
  • 当线程进入等待状态后,如果该线程调用了interrupt方法,则会抛出InterruptExpectation错误。

6.2 Object.notify()方法(线程唤醒)

  • 随机恢复一条处于正在等待的线程
  • 必须在持有当前锁的代码块下使用
  • 恢复后的线程不会立即继续执行,会等待当前线程执行完成重新把方法锁释放后再继续执行
  • notifyAll()恢复所有线程

7 守护线程

  1. 守护线程指在其他线程结束后,它也会跟着结束,即不会因为自己而影响到整个程序的结束

  2. setDaemon(boolean);

  3. 在守护线程下开出来的线程也是守护线程

LICENSED UNDER CC BY-NC-SA 4.0
Comment