精华内容
下载资源
问答
  • 多线程():创建线程和线程的常用方法

    万次阅读 多人点赞 2018-09-01 19:14:23
    了解并发编程:实际工作中很少写多线程的代码,这部分代码一般都被人封装起来了,在业务中使用多线程的机会也不是很多(看具体项目),但是作为一个高级程序员如果不会多线程是说不过去的。 二:进程与线程 ...
    分享一个朋友的人工智能教程(请以“右键”->"在新标签页中打开连接”的方式访问)。比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看。

    一:为什么要学多线程

    1. 应付面试 :多线程几乎是面试中必问的题,所以掌握一定的基础知识是必须的。
    2. 了解并发编程:实际工作中很少写多线程的代码,这部分代码一般都被人封装起来了,在业务中使用多线程的机会也不是很多(看具体项目),虽然代码中很少会自己去创建线程,但是实际环境中每行代码却都是并行执行的,同一时刻大量请求同一个接口,并发可能会产生一些问题,所以也需要掌握一定的并发知识

    二:进程与线程

    1. 进程

    进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

    2. 线程

    线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

    一个正在运行的软件(如迅雷)就是一个进程,一个进程可以同时运行多个任务( 迅雷软件可以同时下载多个文件,每个下载任务就是一个线程), 可以简单的认为进程是线程的集合。

    线程是一条可以执行的路径。

    • 对于单核CPU而言:多线程就是一个CPU在来回的切换,在交替执行。
    • 对于多核CPU而言:多线程就是同时有多条执行路径在同时(并行)执行,每个核执行一个线程,多个核就有可能是一块同时执行的。

    3. 进程与线程的关系

    一个程序就是一个进程,而一个程序中的多个任务则被称为线程。进程是表示资源分配的基本单位,又是调度运行的基本单位。,亦即执行处理机调度的基本单位。 进程和线程的关系:

    • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。

    • 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量。

    • 处理机分给线程,即真正在处理机上运行的是线程。

    • 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

    如果把上课的过程比作进程,把老师比作CPU,那么可以把每个学生比作每个线程,所有学生共享这个教室(也就是所有线程共享进程的资源),上课时学生A向老师提出问题,老师对A进行解答,此时可能会有学生B对老师的解答不懂会提出B的疑问(注意:此时可能老师还没有对A同学的问题解答完毕),此时老师又向学生B解惑,解释完之后又继续回答学生A的问题,同一时刻老师只能向一个学生回答问题(即:当多个线程在运行时,同一个CPU在某一个时刻只能服务于一个线程,可能一个线程分配一点时间,时间到了就轮到其它线程执行了,这样多个线程在来回的切换)

    4. 为什么要使用多线程

    多线程可以提高程序的效率。

    实际生活案例:村长要求喜洋洋在一个小时内打100桶水,可以喜洋洋一个小时只能打25桶水,如果这样就需要4个小时才能完成任务,为了在一个小时能够完成,喜洋洋就请美洋洋、懒洋洋、沸洋洋,来帮忙,这样4只羊同时干活,在一小时内完成了任务。原本用4个小时完成的任务现在只需要1个小时就完成了,如果把每只羊看做一个线程,多只羊即多线程可以提高程序的效率。

    5. 多线程应用场景

    • 一般线程之间比较独立,互不影响
    • 一个线程发生问题,一般不影响其它线程

    三:多线程的实现方式

    1. 顺序编程

    顺序编程:程序从上往下的同步执行,即如果第一行代码执行没有结束,第二行代码就只能等待第一行执行结束后才能结束。

    public class Main {
        // 顺序编程 吃喝示例:当吃饭吃不完的时候,是不能喝酒的,只能吃完晚才能喝酒
        public static void main(String[] args) throws Exception {
    		// 先吃饭再喝酒
            eat();
            drink();
        }
    
        private static void eat() throws Exception {
            System.out.println("开始吃饭?...\t" + new Date());
            Thread.sleep(5000);
            System.out.println("结束吃饭?...\t" + new Date());
        }
    
        private static void drink() throws Exception {
            System.out.println("开始喝酒?️...\t" + new Date());
            Thread.sleep(5000);
            System.out.println("结束喝酒?...\t" + new Date());
        }
    }
    

    这里写图片描述

    2. 并发编程

    并发编程:多个任务可以同时做,常用与任务之间比较独立,互不影响。
    线程上下文切换:

    同一个时刻一个CPU只能做一件事情,即同一时刻只能一个线程中的部分代码,假如有两个线程,Thread-0和Thread-1,刚开始CPU说Thread-0你先执行,给你3毫秒时间,Thread-0执行了3毫秒时间,但是没有执行完,此时CPU会暂停Thread-0执行并记录Thread-0执行到哪行代码了,当时的变量的值是多少,然后CPU说Thread-1你可以执行了,给你2毫秒的时间,Thread-1执行了2毫秒也没执行完,此时CPU会暂停Thread-1执行并记录Thread-1执行到哪行代码了,当时的变量的值是多少,此时CPU又说Thread-0又该你,这次我给你5毫秒时间,去执行吧,此时CPU就找出上次Thread-0线程执行到哪行代码了,当时的变量值是多少,然后接着上次继续执行,结果用了2毫秒就Thread-0就执行完了,就终止了,然后CPU说Thread-1又轮到你,这次给你4毫秒,同样CPU也会先找出上次Thread-1线程执行到哪行代码了,当时的变量值是多少,然后接着上次继续开始执行,结果Thread-1在4毫秒内也执行结束了,Thread-1也结束了终止了。CPU在来回改变线程的执行机会称之为线程上下文切换。

    public class Main {
        public static void main(String[] args) {
    	    // 一边吃饭一边喝酒
            new EatThread().start();
            new DrinkThread().start();
        }
    }
    
    class EatThread extends Thread{
        @Override
        public void run() {
            System.out.println("开始吃饭?...\t" + new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束吃饭?...\t" + new Date());
        }
    }
    
    class DrinkThread extends Thread {
        @Override
        public void run() {
            System.out.println("开始喝酒?️...\t" + new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束喝酒?...\t" + new Date());
        }
    }
    

    并发编程,一边吃饭一边喝酒总共用时5秒,比顺序编程更快,因为并发编程可以同时运行,而不必等前面的代码运行完之后才允许后面的代码

    这里写图片描述

    本示例主要启动3个线程,一个主线程main thread、一个吃饭线程(Thread-0)和一个喝酒线程(Thread-1),共三个线程, 三个线程并发切换着执行。main线程很快执行完,吃饭线程和喝酒线程会继续执行,直到所有线程(非守护线程)执行完毕,整个程序才会结束,main线程结束并不意味着整个程序结束。
    这里写图片描述

    • 顺序:代码从上而下按照固定的顺序执行,只有上一件事情执行完毕,才能执行下一件事。就像物理电路中的串行,假如有十件事情,一个人来完成,这个人必须先做第一件事情,然后再做第二件事情,最后做第十件事情,按照顺序做。

    • 并行:多个操作同时处理,他们之间是并行的。假如十件事情,两个人来完成,每个人在某个时间点各自做各自的事情,互不影响

    • 并发:将一个操作分割成多个部分执行并且允许无序处理,假如有十件事情,如果有一个人在做,这个人可能做一会这个不想做了,再去做别的,做着做着可能也不想做了,又去干其它事情了,看他心情想干哪个就干哪个,最终把十件事情都做完。如果有两个人在做,他们俩先分一下,比如张三做4件,李四做6件,他们各做自己的,在做自己的事情过程中可以随意的切换到别的事情,不一定要把某件事情干完再去干其它事情,有可能一件事做了N次才做完。

    通常一台电脑只有一个cpu,多个线程属于并发执行,如果有多个cpu,多线程并发执行有可能变成并行执行。
    这里写图片描述

    3. 多线程创建方式

    • 继承 Thread
    • 实现 Runable
    • 实现 Callable
    ①:继成java.lang.Thread, 重写run()方法
    public class Main {
        public static void main(String[] args) {
            new MyThread().start();
        }
    }
    
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
        }
    }
    

    Thread 类

    package java.lang;
    public class Thread implements Runnable {
    	// 构造方法
    	public Thread(Runnable target);
    	public Thread(Runnable target, String name);
    	
    	public synchronized void start();
    }
    

    Runnable 接口

    package java.lang;
    
    @FunctionalInterface
    public interface Runnable {
        pubic abstract void run();
    }
    

    ②:实现java.lang.Runnable接口,重写run()方法,然后使用Thread类来包装

    public class Main {
        public static void main(String[] args) {
        	 // 将Runnable实现类作为Thread的构造参数传递到Thread类中,然后启动Thread类
            MyRunnable runnable = new MyRunnable();
            new Thread(runnable).start();
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
        }
    }
    

    可以看到两种方式都是围绕着Thread和Runnable,继承Thread类把run()写到类中,实现Runnable接口是把run()方法写到接口中然后再用Thread类来包装, 两种方式最终都是调用Thread类的start()方法来启动线程的。
    两种方式在本质上没有明显的区别,在外观上有很大的区别,第一种方式是继承Thread类,因Java是单继承,如果一个类继承了Thread类,那么就没办法继承其它的类了,在继承上有一点受制,有一点不灵活,第二种方式就是为了解决第一种方式的单继承不灵活的问题,所以平常使用就使用第二种方式

    其它变体写法:

    public class Main {
        public static void main(String[] args) {
            // 匿名内部类
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
                }
            }).start();
    
            // 尾部代码块, 是对匿名内部类形式的语法糖
            new Thread() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
                }
            }.start();
    
            // Runnable是函数式接口,所以可以使用Lamda表达式形式
            Runnable runnable = () -> {System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());};
            new Thread(runnable).start();
        }
    }
    

    ③:实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread

    Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕

    public class Main {
        public static void main(String[] args) throws Exception {
        	 // 将Callable包装成FutureTask,FutureTask也是一种Runnable
            MyCallable callable = new MyCallable();
            FutureTask<Integer> futureTask = new FutureTask<>(callable);
            new Thread(futureTask).start();
    
            // get方法会阻塞调用的线程
            Integer sum = futureTask.get();
            System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
        }
    }
    
    
    class MyCallable implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
    
            int sum = 0;
            for (int i = 0; i <= 100000; i++) {
                sum += i;
            }
            Thread.sleep(5000);
    
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
            return sum;
        }
    }
    

    Callable 也是一种函数式接口

    @FunctionalInterface
    public interface Callable<V> {
        V call() throws Exception;
    }
    

    FutureTask

    public class FutureTask<V> implements RunnableFuture<V> {
    	// 构造函数
    	public FutureTask(Callable<V> callable);
    	
    	// 取消线程
    	public boolean cancel(boolean mayInterruptIfRunning);
    	// 判断线程
    	public boolean isDone();
    	// 获取线程执行结果
    	public V get() throws InterruptedException, ExecutionException;
    }
    

    RunnableFuture

    public interface RunnableFuture<V> extends Runnable, Future<V> {
        void run();
    }
    

    三种方式比较:

    • Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
    • Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
    • Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
    • 当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
    • Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现

    四:线程的状态

    1. 创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
    2. 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
    3. 运行(running)状态: 执行run()方法
    4. 阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
    5. 死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)

    这里写图片描述

    这里写图片描述

    这里写图片描述

    五:Thread常用方法

    Thread

    public class Thread implements Runnable {
        // 线程名字
        private volatile String name;
        // 线程优先级(1~10)
        private int priority;
        // 守护线程
        private boolean daemon = false;
        // 线程id
        private long tid;
        // 线程组
        private ThreadGroup group;
        
        // 预定义3个优先级
        public final static int MIN_PRIORITY = 1;
        public final static int NORM_PRIORITY = 5;
        public final static int MAX_PRIORITY = 10;
        
        
        // 构造函数
        public Thread();
        public Thread(String name);
        public Thread(Runnable target);
        public Thread(Runnable target, String name);
        // 线程组
        public Thread(ThreadGroup group, Runnable target);
        
        
        // 返回当前正在执行线程对象的引用
        public static native Thread currentThread();
        
        // 启动一个新线程
        public synchronized void start();
        // 线程的方法体,和启动线程没毛关系
        public void run();
        
        // 让线程睡眠一会,由活跃状态改为挂起状态
        public static native void sleep(long millis) throws InterruptedException;
        public static void sleep(long millis, int nanos) throws InterruptedException;
        
        // 打断线程 中断线程 用于停止线程
        // 调用该方法时并不需要获取Thread实例的锁。无论何时,任何线程都可以调用其它线程的interruptf方法
        public void interrupt();
        public boolean isInterrupted()
        
        // 线程是否处于活动状态
        public final native boolean isAlive();
        
        // 交出CPU的使用权,从运行状态改为挂起状态
        public static native void yield();
        
        public final void join() throws InterruptedException
        public final synchronized void join(long millis)
        public final synchronized void join(long millis, int nanos) throws InterruptedException
        
        
        // 设置线程优先级
        public final void setPriority(int newPriority);
        // 设置是否守护线程
        public final void setDaemon(boolean on);
        // 线程id
        public long getId() { return this.tid; }
        
        
        // 线程状态
        public enum State {
            // new 创建
            NEW,
    
            // runnable 就绪
            RUNNABLE,
    
            // blocked 阻塞
            BLOCKED,
    
            // waiting 等待
            WAITING,
    
            // timed_waiting
            TIMED_WAITING,
    
            // terminated 结束
            TERMINATED;
        }
    }
    
    public static void main(String[] args) {
        // main方法就是一个主线程
    
        // 获取当前正在运行的线程
        Thread thread = Thread.currentThread();
        // 线程名字
        String name = thread.getName();
        // 线程id
        long id = thread.getId();
        // 线程优先级
        int priority = thread.getPriority();
        // 是否存活
        boolean alive = thread.isAlive();
        // 是否守护线程
        boolean daemon = thread.isDaemon();
    
        // Thread[name=main, id=1 ,priority=5 ,alive=true ,daemon=false]
        System.out.println("Thread[name=" + name + ", id=" + id + " ,priority=" + priority + " ,alive=" + alive + " ,daemon=" + daemon + "]");
    }
    
    0. Thread.currentThread()
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        // 线程名称
        String name = thread.getName();
        // 线程id
        long id = thread.getId();
        // 线程已经启动且尚未终止
        // 线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的
        boolean alive = thread.isAlive();
        // 线程优先级
        int priority = thread.getPriority();
        // 是否守护线程
        boolean daemon = thread.isDaemon();
        
        // Thread[name=main,id=1,alive=true,priority=5,daemon=false]
        System.out.println("Thread[name=" + name + ",id=" + id + ",alive=" + alive + ",priority=" + priority + ",daemon=" + daemon + "]");
    }
    
    1. start() 与 run()
    public static void main(String[] args) throws Exception {
       new Thread(()-> {
           for (int i = 0; i < 5; i++) {
               System.out.println(Thread.currentThread().getName() + " " + i);
               try { Thread.sleep(200); } catch (InterruptedException e) { }
           }
       }, "Thread-A").start();
    
       new Thread(()-> {
           for (int j = 0; j < 5; j++) {
               System.out.println(Thread.currentThread().getName() + " " + j);
               try { Thread.sleep(200); } catch (InterruptedException e) { }
           }
       }, "Thread-B").start();
    }
    

    start(): 启动一个线程,线程之间是没有顺序的,是按CPU分配的时间片来回切换的。
    这里写图片描述

    public static void main(String[] args) throws Exception {
        new Thread(()-> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                try { Thread.sleep(200); } catch (InterruptedException e) { }
            }
        }, "Thread-A").run();
    
        new Thread(()-> {
            for (int j = 0; j < 5; j++) {
                System.out.println(Thread.currentThread().getName() + " " + j);
                try { Thread.sleep(200); } catch (InterruptedException e) { }
            }
        }, "Thread-B").run();
    }
    

    注意:执行结果都是main主线程
    这里写图片描述

    run(): 调用线程的run方法,就是普通的方法调用,虽然将代码封装到两个线程体中,可以看到线程中打印的线程名字都是main主线程,run()方法用于封装线程的代码,具体要启动一个线程来运行线程体中的代码(run()方法)还是通过start()方法来实现,调用run()方法就是一种顺序编程不是并发编程。

    有些面试官经常问一些启动一个线程是用start()方法还是run()方法,为了面试而面试。

    2. sleep() 与 interrupt()
    public static native void sleep(long millis) throws InterruptedException;
    public void interrupt();
    

    sleep(long millis): 睡眠指定时间,程序暂停运行,睡眠期间会让出CPU的执行权,去执行其它线程,同时CPU也会监视睡眠的时间,一旦睡眠时间到就会立刻执行(因为睡眠过程中仍然保留着锁,有锁只要睡眠时间到就能立刻执行)。

    • sleep(): 睡眠指定时间,即让程序暂停指定时间运行,时间到了会继续执行代码,如果时间未到就要醒需要使用interrupt()来随时唤醒
    • interrupt(): 唤醒正在睡眠的程序,调用interrupt()方法,会使得sleep()方法抛出InterruptedException异常,当sleep()方法抛出异常就中断了sleep的方法,从而让程序继续运行下去
    public static void main(String[] args) throws Exception {
        Thread thread0 = new Thread(()-> {
            try {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t太困了,让我睡10秒,中间有事叫我,zZZ。。。");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t被叫醒了,又要继续干活了");
            }
        });
        thread0.start();
    
        // 这里睡眠只是为了保证先让上面的那个线程先执行
        Thread.sleep(2000);
    
        new Thread(()-> {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t醒醒,醒醒,别睡了,起来干活了!!!");
            // 无需获取锁就可以调用interrupt
            thread0.interrupt();
        }).start();
    }
    

    这里写图片描述

    3. wait() 与 notify()

    wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,因此在程序中可以通过this或者super来调用this.wait(), super.wait()

    • wait(): 导致线程进入等待阻塞状态,会一直等待直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。wait(long timeout): 时间到了自动执行,类似于sleep(long millis)
    • notify(): 该方法只能在同步方法或同步块内部调用, 随机选择一个(注意:只会通知一个)在该对象上调用wait方法的线程,解除其阻塞状态
    • notifyAll(): 唤醒所有的wait对象

    注意:

    • Object.wait()和Object.notify()和Object.notifyall()必须写在synchronized方法内部或者synchronized块内部
    • 让哪个对象等待wait就去通知notify哪个对象,不要让A对象等待,结果却去通知B对象,要操作同一个对象

    Object

    public class Object {
    	public final void wait() throws InterruptedException;
    	public final native void wait(long timeout) throws InterruptedException;
    	public final void wait(long timeout, int nanos) throws InterruptedException;
    	
    	
    	public final native void notify();
    	public final native void notifyAll();
    }
    

    WaitNotifyTest

    public class WaitNotifyTest {
        public static void main(String[] args) throws Exception {
            WaitNotifyTest waitNotifyTest = new WaitNotifyTest();
            new Thread(() -> {
                try {
                    waitNotifyTest.printFile();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(() -> {
                try {
                    waitNotifyTest.printFile();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(() -> {
                try {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t睡觉1秒中,目的是让上面的线程先执行,即先执行wait()");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                waitNotifyTest.notifyPrint();
            }).start();
        }
    
        private synchronized void printFile() throws InterruptedException {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
            this.wait();
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印结束。。。");
        }
    
        private synchronized void notifyPrint() {
            this.notify();
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t通知完成...");
        }
    }
    
    

    这里写图片描述

    wait():让程序暂停执行,相当于让当前,线程进入当前实例的等待队列,这个队列属于该实例对象,所以调用notify也必须使用该对象来调用,不能使用别的对象来调用。调用wait和notify必须使用同一个对象来调用。
    这里写图片描述

    this.notifyAll();
    这里写图片描述

    4. sleep() 与 wait()
    ① Thread.sleep(long millis): 睡眠时不会释放锁
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
    
        new Thread(() -> {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                    try { Thread.sleep(1000); } catch (InterruptedException e) { }
                }
            }
        }).start();
    
        Thread.sleep(1000);
    
        new Thread(() -> {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                }
            }
        }).start();
    }
    

    因main方法中Thread.sleep(1000)所以上面的线程Thread-0先被执行,当循环第一次时就会Thread.sleep(1000)睡眠,因为sleep并不会释放锁,所以Thread-1得不到执行的机会,所以直到Thread-0执行完毕释放锁对象lock,Thread-1才能拿到锁,然后执行Thread-1;
    这里写图片描述

    5. wait() 与 interrupt()

    wait(): 方法的作用是释放锁,加入到等待队列,当调用interrupt()方法后,线程必须先获取到锁后,然后才抛出异常InterruptedException 。注意: 在获取锁之前是不会抛出异常的,只有在获取锁之后才会抛异常

    所有能抛出InterruptedException的方法都可以通过interrupt()来取消的

    public static native void sleep(long millis) throws InterruptedException;
    public final void wait() throws InterruptedException;
    public final void join() throws InterruptedException;
    public void interrupt()

    notify()和interrupt()
    从让正在wait的线程重新运行这一点来说,notify方法和intterrupt方法的作用有些类似,但仍有以下不同之处:

    • notify/notifyAll是java.lang.Object类的方法,唤醒的是该实例的等待队列中的线程,而不能直接指定某个具体的线程。notify/notifyAll唤醒的线程会继续执行wait的下一条语句,另外执行notify/notifyAll时线程必须要获取实例的锁

    • interrupte方法是java.lang.Thread类的方法,可以直接指定线程并唤醒,当被interrupt的线程处于sleep或者wait中时会抛出InterruptedException异常。执行interrupt()并不需要获取取消线程的锁。

    • 总之notify/notifyAll和interrupt的区别在于是否能直接让某个指定的线程唤醒、执行唤醒是否需要锁、方法属于的类不同

    6. interrupt()

    有人也许认为“当调用interrupt方法时,调用对象的线程就会InterruptedException异常”, 其实这是一种误解,实际上interrupt方法只是改变了线程的“中断状态”而已,所谓中断状态是一个boolean值,表示线程是否被中断的状态。

    public class Thread implements Runnable {
    	public void interrupt() {
    		中断状态 = true;
    	}
    	
    	// 检查中断状态
    	public boolean isInterrupted();
    	
    	// 检查中断状态并清除当前线程的中断状态
    	public static boolean interrupted() {
    		// 伪代码
    		boolean isInterrupted = isInterrupted();
    		中断状态 = false;
    	}
    }	
    

    假设Thread-0执行了sleep、wait、join中的一个方法而停止运行,在Thread-1中调用了interrupt方法,此时线程Thread-0的确会抛出InterruptedException异常,但这其实是sleep、wait、join中的方法内部会对线程的“中断状态”进行检查,如果中断状态为true,就会抛出InterruptedException异常。假如某个线程的中断状态为true,但线程体中却没有调用或者没有判断线程中断状态的值,那么线程则不会抛出InterruptedException异常。

    isInterrupted() 检查中断状态
    若指定线程处于中断状态则返回true,若指定线程为非中断状态,则反回false, isInterrupted() 只是获取中断状态的值,并不会改变中断状态的值。

    interrupted()
    检查中断状态并清除当前线程的中断状态。如当前线程处于中断状态返回true,若当前线程处于非中断状态则返回false, 并清除中断状态(将中断状态设置为false), 只有这个方法才可以清除中断状态,Thread.interrupted的操作对象是当前线程,所以该方法并不能用于清除其它线程的中断状态。

    interrupt()与interrupted()

    • interrupt():打断线程,将中断状态修改为true
    • interrupted(): 不打断线程,获取线程的中断状态,并将中断状态设置为false

    这里写图片描述

    public class InterrupptTest {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();
            boolean interrupted = thread.isInterrupted();
            // interrupted=false
            System.out.println("interrupted=" + interrupted);
    
            thread.interrupt();
    
            boolean interrupted2 = thread.isInterrupted();
            // interrupted2=true
            System.out.println("interrupted2=" + interrupted2);
    
            boolean interrupted3 = Thread.interrupted();
            // interrupted3=false
            System.out.println("interrupted3=" + interrupted3);
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            synchronized (this) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    // InterruptedException	false
                    System.out.println("InterruptedException\t" + Thread.currentThread().isInterrupted());
                }
            }
        }
    }
    
    

    这里写图片描述

    ② object.wait(long timeout): 会释放锁
    public class SleepWaitTest {
        public static void main(String[] args) throws InterruptedException {
            SleepWaitTest object = new SleepWaitTest();
    
            new Thread(() -> {
                synchronized (object) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
                    try {
                        object.wait(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印结束。。。");
                }
            }).start();
    
    		 // 先上面的线程先执行
            Thread.sleep(1000);
    
            new Thread(() -> {
                synchronized (object) {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                    }
                }
            }).start();
        }
    }
    

    因main方法中有Thread.sleep(1000)所以上面的线程Thread-0肯定会被先执行,当Thread-0被执行时就拿到了object对象锁,然后进入wait(5000)5秒钟等待,此时wait释放了锁,然后Thread-1就拿到了锁就执行线程体,Thread-1执行完后就释放了锁,当等待5秒后Thread-0就能再次获取object锁,这样就继续执行后面的代码。wait方法是释放锁的,如果wait方法不释放锁那么Thread-1是拿不到锁也就没有执行的机会的,事实是Thread-1得到了执行,所以说wait方法会释放锁

    这里写图片描述

    ③ sleep与wait的区别
    • sleep在Thread类中,wait在Object类中
    • sleep不会释放锁,wait会释放锁
    • sleep使用interrupt()来唤醒,wait需要notify或者notifyAll来通知
    5.join()

    让当前线程加入父线程,加入后父线程会一直wait,直到子线程执行完毕后父线程才能执行。当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。

    将某个线程加入到当前线程中来,一般某个线程和当前线程依赖关系比较强,必须先等待某个线程执行完毕才能执行当前线程。一般在run()方法内使用

    join() 方法:

    public final void join() throws InterruptedException {
            join(0);
    }
    
    
    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (millis == 0) {
        	 // 循环检查线程的状态是否还活着,如果死了就结束了,如果活着继续等到死
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    
    
    public final synchronized void join(long millis, int nanos) throws InterruptedException {
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
    
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
    
        join(millis);
    }
    
    

    JoinTest

    public class JoinTest {
        public static void main(String[] args) {
            new Thread(new ParentRunnable()).start();
        }
    }
    
    class ParentRunnable implements Runnable {
        @Override
        public void run() {
            // 线程处于new状态
            Thread childThread = new Thread(new ChildRunable());
            // 线程处于runnable就绪状态
            childThread.start();
            try {
                // 当调用join时,parent会等待child执行完毕后再继续运行
                // 将某个线程加入到当前线程
                childThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            for (int i = 0; i < 5; i++) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "父线程 running");
            }
        }
    }
    
    class ChildRunable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try { Thread.sleep(1000); } catch (InterruptedException e) {}
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "子线程 running");
            }
        }
    }
    
    

    程序进入主线程,运行Parent对应的线程,Parent的线程代码分两段,一段是启动一个子线程,一段是Parent线程的线程体代码,首先会将Child线程加入到Parent线程,join()方法会调用join(0)方法(join()方法是普通方法并没有加锁,join(0)会加锁),join(0)会执行while(isAlive()) { wait(0);} 循环判断线程是否处于活动状态,如果是继续wait(0)知道isAlive=false结束掉join(0), 从而结束掉join(), 最后回到Parent线程体中继续执行其它代码。

    在Parent调用child.join()后,child子线程正常运行,Parent父线程会等待child子线程结束后再继续运行。
    这里写图片描述

    • join() 和 join(long millis, int nanos) 最后都调用了 join(long millis)。

    • join(long millis, int nanos)和join(long millis)方法 都是synchronized。

    • join() 调用了join(0),从源码可以看到join(0)不断检查当前线程是否处于Active状态。

    • join() 和 sleep() 一样,都可以被中断(被中断时,会抛出 InterrupptedException 异常);不同的是,join() 内部调用了wait(),会出让锁,而 sleep() 会一直保持锁。

    6. yield()

    交出CPU的执行时间,不会释放锁,让线程进入就绪状态,等待重新获取CPU执行时间,yield就像一个好人似的,当CPU轮到它了,它却说我先不急,先给其他线程执行吧, 此方法很少被使用到,

    /**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();
    

    这里写图片描述

    public static void main(String[] args) {
        new Thread(new Runnable() {
            int sum = 0;
            @Override
            public void run() {
                long beginTime=System.currentTimeMillis();
                for (int i = 0; i < 99999; i++) {
                    sum += 1;
                    // 去掉该行执行用2毫秒,加上271毫秒
                    Thread.yield();
                }
                long endTime=System.currentTimeMillis();
                System.out.println("用时:"+ (endTime - beginTime) + " 毫秒!");
            }
        }).start();
    }
    

    sleep(long millis) 与 yeid()

    • sleep(long millis): 需要指定具体睡眠的时间,不会释放锁,睡眠期间CPU会执行其它线程,睡眠时间到会立刻执行
    • yeid(): 交出CPU的执行权,不会释放锁,和sleep不同的时当再次获取到CPU的执行,不能确定是什么时候,而sleep是能确定什么时候再次执行。两者的区别就是sleep后再次执行的时间能确定,而yeid是不能确定的
    • yield会把CPU的执行权交出去,所以可以用yield来控制线程的执行速度,当一个线程执行的比较快,此时想让它执行的稍微慢一些可以使用该方法,想让线程变慢可以使用sleep和wait,但是这两个方法都需要指定具体时间,而yield不需要指定具体时间,让CPU决定什么时候能再次被执行,当放弃到下次再次被执行的中间时间就是间歇等待的时间
    7. setDaemon(boolean on)

    线程分两种:

    • 用户线程:如果主线程main停止掉,不会影响用户线程,用户线程可以继续运行。
    • 守护线程:如果主线程死亡,守护线程如果没有执行完毕也要跟着一块死(就像皇上死了,带刀侍卫也要一块死),GC垃圾回收线程就是守护线程
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                IntStream.range(0, 5).forEach(i -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\ti=" + i);
                });
            }
        };
        thread.start();
    
    
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName() + "\ti=" + i);
        }
        System.out.println("主线程执行结束,子线程仍然继续执行,主线程和用户线程的生命周期各自独立。");
    }
    

    这里写图片描述

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                IntStream.range(0, 5).forEach(i -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\ti=" + i);
                });
            }
        };
        thread.setDaemon(true);
        thread.start();
    
    
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName() + "\ti=" + i);
        }
        System.out.println("主线程死亡,子线程也要陪着一块死!");
    }
    

    这里写图片描述

    六 线程组

    可以对线程分组,分组后可以统一管理某个组下的所有线程,例如统一中断所有线程

    public class ThreadGroup implements Thread.UncaughtExceptionHandler {
        private final ThreadGroup parent;
        String name;
        int maxPriority;
        
        Thread threads[];
        
        private ThreadGroup() {
            this.name = "system";
            this.maxPriority = Thread.MAX_PRIORITY;
            this.parent = null;
        }
        
        public ThreadGroup(String name) {
            this(Thread.currentThread().getThreadGroup(), name);
        }
        
        public ThreadGroup(ThreadGroup parent, String name) {
            this(checkParentAccess(parent), parent, name);
        }
        
        // 返回此线程组中活动线程的估计数。 
        public int activeGroupCount();
        
        // 中断此线程组中的所有线程。
        public final void interrupt();
    }
    
    public static void main(String[] args) {
        String mainThreadGroupName = Thread.currentThread().getThreadGroup().getName();
        System.out.println(mainThreadGroupName);
        // 如果一个线程没有指定线程组,默认为当前线程所在的线程组
        new Thread(() -> { }, "my thread1").start();
    
        ThreadGroup myGroup = new ThreadGroup("MyGroup");
        myGroup.setMaxPriority(5);
    
        Runnable runnable = () -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
            String groupName = threadGroup.getName();
            ThreadGroup parentGroup = threadGroup.getParent();
            String parentGroupName = parentGroup.getName();
            ThreadGroup grandpaThreadGroup = parentGroup.getParent();
            String grandpaThreadGroupName = grandpaThreadGroup.getName();
            int maxPriority = threadGroup.getMaxPriority();
            int activeCount = myGroup.activeCount();
    
            // system <- main <- MyGroup(1) <- my thread2
            System.out.println(MessageFormat.format("{0} <- {1} <- {2}({3}) <- {4}",
                    grandpaThreadGroupName,
                    parentGroupName,
                    groupName,
                    activeCount,
                    Thread.currentThread().getName()));
        };
    
        new Thread(myGroup, runnable, "my thread2").start();
    }
    

    线程组与线程组之间是有父子关系的,自定义线程组的父线程组是main线程组,main线程组的父线程组是system线程组。
    这里写图片描述

    展开全文
  • Java使用循环创建个线程

    千次阅读 2019-05-19 23:31:13
    使用start()方法启动线程,则立即开始创建下一个线程。 测试代码及结果如下: 情景:循环创建一类线程,这类线程的run()方法不能立即结束,如包含循环等。 问题:创建线程后,启动线程时,使用run(...

    目录

    情景:循环创建一类线程,这类线程的run()方法不能立即结束,如包含循环等。

    问题:创建线程后,启动线程时,使用run()方法则需要等待线程的run()方法先结束,否则阻塞;使用start()方法启动线程,则立即开始创建下一个线程。

    测试代码及结果如下:


     

     情景:循环创建一类线程,这类线程的run()方法不能立即结束,如包含循环等。

    问题:创建线程后,启动线程时,使用run()方法则需要等待线程的run()方法先结束,否则阻塞;使用start()方法启动线程,则立即开始创建下一个线程。

    测试代码及结果如下:

    package thread_test;
    
    class Thread_test extends Thread{//线程类
    	@Override
    	public void run() {
    		while(true);//线程的功能函数中含有死循环
    	}
    }
    public class For_Thread {
    	public static void main(String[] args) {
    		for(int i=0;i<4;i++) {//使用循环创建线程
    			Thread_test t = new Thread_test();
    			t.start();//使用start()方法,不会阻塞
    			System.out.println(i);
    		}
    	}
    }
    

     

    package thread_test;
    
    class Thread_test extends Thread{//线程类
    	@Override
    	public void run() {
    		while(true);//线程功能函数中存在不能立即结束的循环
    	}
    }
    public class For_Thread {
    	public static void main(String[] args) {
    		for(int i=0;i<4;i++) {//循环创建线程
    			Thread_test t = new Thread_test();
    			t.run();//使用run()方法会阻塞
    			System.out.println(i);
    		}
    	}
    }

    当线程的run()函数可以立即结束时,启动线程时,使用run()方法可以立即结束

    展开全文
  • Java线程

    万次阅读 多人点赞 2021-06-11 16:28:49
    Java多线程Java多线程线程的创建线程常见方法线程的状态线程的优先级守护线程线程组Java线程池线程池的创建线程池的参数线程池的使用线程不安全问题Java中的锁synchronized同步方法synchronized同步语句块...

    Java多线程

    线程的创建

    1.继承Thread
    2.实现Runnable
    3.实现Callable
    使用继承Thread类来开发多线程的应用程序在设计上是有局限性的,因为Java是单继承。
    继承Thread类

    public class ThreadDemo1 {
        // 继承Thread类 写法1
        static class MyThread extends Thread{
            @Override
            public void run() {
                //要实现的业务代码
            }
        }
    
        // 写法2
        Thread thread = new Thread(){
            @Override
            public void run() {
                //要实现的业务代码
            }
        };
    
    
    }
    

    实现Runnable接口

    //实现Runnable接口 写法1
    
    class MyRunnable implements Runnable{
        @Override
        public void run() {
            //要实现的业务代码
        }
    }
    
    //实现Runnable接口 写法2 匿名内部类
    class MyRunnable2 {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    //要实现的业务代码
                }
            });
        }
    }
    

    实现Callable接口(Callable + FutureTask 创建带有返回值的线程)

    package ThreadDeom;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 17:34;
     */
    //创建有返回值的线程 Callable + Future
    public class ThreadDemo2 {
        static class MyCallable implements Callable<Integer>{
            @Override
            public Integer call() throws Exception {
                return 0;
            }
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //创建Callable子对象
            MyCallable myCallable = new MyCallable();
            //使用FutureTask 接受 Callable
            FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
            //创建线程并设置任务
            Thread thread = new Thread(futureTask);
            //启动线程
            thread.start();
            //得到线程的执行结果
            int num = futureTask.get();
        }
    }
    
    

    也可以使用lambda表达式

    class ThreadDemo21{
        //lambda表达式
        Thread thread = new Thread(()-> {
            //要实现的业务代码
        });
    }
    
    

    Thread的构造方法
    在这里插入图片描述

    线程常用方法

    获取当前线程的引用、线程的休眠

    class Main{
        public static void main(String[] args) throws InterruptedException {
            Thread.sleep(1000);
            //休眠1000毫秒之后打印
            System.out.println(Thread.currentThread());
            System.out.println(Thread.currentThread().getName());
        }
    }
    

    在这里插入图片描述

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 18:38;
     */
    public class ThreadDemo6 {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
    
                    System.out.println("线程的ID:" + Thread.currentThread().getId());
                    System.out.println("线程的名称:" + Thread.currentThread().getName());
                    System.out.println("线程的状态:" + Thread.currentThread().getState());
    
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"线程一");
    
            thread.start();
            Thread.sleep(100);
            //打印线程的状态
            System.out.println("线程的状态:"+thread.getState());
            System.out.println("线程的优先级:"+thread.getPriority());
            System.out.println("线程是否存活:"+thread.isAlive());
            System.out.println("线程是否是守护线程:"+thread.isDaemon());
            System.out.println("线程是否被打断:"+thread.isInterrupted());
        }
    }
    
    

    在这里插入图片描述

    线程的等待
    假设有一个坑位,thread1 和 thread2 都要上厕所。一次只能一个人上,thread2只能等待thread1使用完才能使用厕所。就可以使用join()方法,等待线程1执行完,thread2在去执行。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 10:48;
     */
    public class ThreadDemo13 {
        public static void main(String[] args) throws InterruptedException {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"🚾");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(Thread.currentThread().getName()+"出来了");
                }
            };
    
            Thread t1 = new Thread(runnable,"thread1");
            t1.start();
    
            //t1.join();
            Thread t2 = new Thread(runnable,"thread2");
            t2.start();
        }
    }
    
    

    在这里插入图片描述

    没有join()显然是不行的。加上join()之后:
    在这里插入图片描述

    线程的终止

    1.自定义实现线程的终止

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:59;
     */
    public class ThreadDemo11 {
        private static boolean flag = false;
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!flag){
                        System.out.println("我是 : " + Thread.currentThread().getName() + ",我还没有被interrupted呢");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("我是 "+Thread.currentThread().getName()+",我被interrupted了");
                }
    
            },"thread");
            thread.start();
    
            Thread.sleep(300);
            flag = true;
    
        }
    }
    
    

    在这里插入图片描述

    2.使用Thread的interrupted来中断

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:59;
     */
    public class ThreadDemo11 {
    //    private static boolean flag = false;
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!Thread.interrupted()){
                        System.out.println("我是 : " + Thread.currentThread().getName() + ",我还没有被interrupted呢");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
    //                        e.printStackTrace();
                            break;
                        }
                    }
                    System.out.println("我是 "+Thread.currentThread().getName()+",我被interrupted了");
                }
    
            },"thread");
            thread.start();
    
            Thread.sleep(300);
            thread.interrupt();
    //        flag = true;
    
        }
    }
    
    

    在这里插入图片描述
    3.Thraed.interrupted()方法和Threaed.currentThread().interrupt()的区别
    Thread.interrupted()方法第一次接收到终止的状态后,之后会将状态复位,Thread.interrupted()是静态的,是全局的。Threaed.currentThread().interrupt()只是普通的方法。
    Thraed.interrupted()方法

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 10:32;
     */
    public class ThreadDemo12 {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() ->{
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.interrupted());
                }
            });
    
            thread.start();
            thread.interrupt();
    
        }
    }
    
    

    在这里插入图片描述

    Threaed.currentThread().interrupt()

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 10:32;
     */
    public class ThreadDemo12 {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() ->{
                for (int i = 0; i < 10; i++) {
    //                System.out.println(Thread.interrupted());
                    System.out.println(Thread.currentThread().isInterrupted());
                }
    
            });
    
            thread.start();
            thread.interrupt();
    
        }
    }
    
    

    在这里插入图片描述

    yield()方法
    让出CPU的执行权

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 11:47;
     */
    public class ThreadDemo15 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    Thread.yield();
                    System.out.println("thread1");
                }
            });
    
            thread1.start();
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    System.out.println("thread2");
                }
            });
    
            thread2.start();
        }
    }
    
    

    在这里插入图片描述

    线程的状态

    在这里插入图片描述

    打印出线程的所有的状态,所有的线程的状态都在枚举中。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 11:06;
     */
    public class ThreadDemo14 {
        public static void main(String[] args) {
            for (Thread.State state: Thread.State.values()) {
                System.out.println(state);
            }
        }
    }
    
    

    在这里插入图片描述

    NEW 创建了线程但是还没有开始工作
    RUNNABLE 正在Java虚拟机中执行的线程
    BLOCKED 受到阻塞并且正在等待某个监视器的锁的时候所处的状态
    WAITTING 无限期的等待另一个线程执行某个特定操作的线程处于这个状态
    TIME_WAITTING 有具体等待时间的等待
    TERMINATED 已经退出的线程处于这种状态

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 11:06;
     */
    
    class TestThreadDemo{
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            System.out.println(thread.getState());
            thread.start();
            System.out.println(thread.getState());
    
            Thread.sleep(100);
    
            System.out.println(thread.getState());
    
            thread.join();
    
            System.out.println(thread.getState());
        }
    }
    

    在这里插入图片描述

    线程的优先级

    在Java中线程 的优先级分为1 ~ 10 一共十个等级

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 21:22;
     */
    public class ThreadDemo9 {
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                Thread t1 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("t1");
                    }
                });
                //最大优先级
                t1.setPriority(10);
                t1.start();
                Thread t2 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("t2");
                    }
                });
                //最小优先级
                t2.setPriority(1);
                t2.start();
                Thread t3 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("t3");
                    }
                });
                t3.setPriority(1);
                t3.start();
            }
        }
    }
    
    

    在这里插入图片描述
    线程的优先级不是绝对的,只是给程序的建议。
    线程之间的优先级具有继承的特性,如果A线程启动了B线程,那么B的线程的优先级与A是一样的。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 20:46;
     */
    class ThreadA extends Thread{
        @Override
        public void run() {
            System.out.println("ThreadA优先级是:" + this.getPriority());
            ThreadB threadB = new ThreadB();
            threadB.start();
        }
    }
    
    class ThreadB extends ThreadA{
        @Override
        public void run() {
            System.out.println("ThreadB的优先级是:" + this.getPriority());
        }
    }
    public class ThreadDemo7 {
        public static void main(String[] args) {
    
            System.out.println("main线程开始的优先级是:" + Thread.currentThread().getPriority());
            
            System.out.println("main线程结束的优先级是:" + Thread.currentThread().getPriority());
    
            ThreadA threadA = new ThreadA();
            threadA.start();
        }
    }
    
    

    在这里插入图片描述

    再看👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 20:46;
     */
    class ThreadA extends Thread{
        @Override
        public void run() {
            System.out.println("ThreadA优先级是:" + this.getPriority());
            ThreadB threadB = new ThreadB();
            threadB.start();
        }
    }
    
    class ThreadB extends ThreadA{
        @Override
        public void run() {
            System.out.println("ThreadB的优先级是:" + this.getPriority());
        }
    }
    public class ThreadDemo7 {
        public static void main(String[] args) {
    
            System.out.println("main线程开始的优先级是:" + Thread.currentThread().getPriority());
            Thread.currentThread().setPriority(9);
            System.out.println("main线程结束的优先级是:" + Thread.currentThread().getPriority());
    
            ThreadA threadA = new ThreadA();
            threadA.start();
        }
    }
    
    

    结果为👇
    在这里插入图片描述

    守护线程

    Java中有两种线程:一种是用户线程,一种就是守护线程。
    什么是守护线程?守护线程是一种特殊的线程,当进程中不存在用户线程的时候,守护线程就会自动销毁。典型的守护线程就是垃圾回收线程,当进程中没有了非守护线程,则垃圾回收线程也就没有存在的必要了。
    Daemon线程的作用就是为其他线程的运行提供便利的。👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 21:06;
     */
    
    public class ThreadDemo8 {
        static private int i = 0;
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        i++;
                        System.out.println(i);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            //设置守护线程
            thread.setDaemon(true);
            thread.start();
            Thread.sleep(5000);
            System.out.println("我是守护线程thread 当用户线程执行完成后 我也就销毁了😭哭了");
        }
    }
    
    

    在这里插入图片描述
    注意:守护线程的设置必须放在start()之前,否则就会报错。
    在这里插入图片描述
    在守护线程中创建的线程默认也是守护线程。

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:35;
     */
    public class ThreadDemo10 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(()->{
                Thread thread2 = new Thread(() -> {
                },"thread2");
                System.out.println("thread2是守护线程吗?:" + thread2.isDaemon());
    
            },"thread1");
    
            System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
            //thread1.setDaemon(true);
            thread1.start();
           // System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
        }
    }
    
    

    在这里插入图片描述
    再看👇

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 9:35;
     */
    public class ThreadDemo10 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(()->{
                Thread thread2 = new Thread(() -> {
                },"thread2");
                System.out.println("thread2是守护线程吗?:" + thread2.isDaemon());
    
            },"thread1");
    
            System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
            thread1.setDaemon(true);
            thread1.start();
            System.out.println("thread1是守护线程吗?:" + thread1.isDaemon());
    
        }
    }
    

    在这里插入图片描述

    线程组

    为了便于对某些具有相同功能的线程进行管理,可以把这些线程归属到同一个线程组中,线程组中既可以有线程对象,也可以有线程组,组中也可以有线程。
    使用线程模拟赛跑

    public class ThreadDemo5 {
        //线程模拟赛跑(未使用线程分组)
        public static void main(String[] args) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
                }
            }, "选手一");
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
                }
            }, "选手二");
    
            t1.start();
            t2.start();
    
    
            System.out.println("所有选手到达了终点");
        }
    }
    
    

    运行结果:
    在这里插入图片描述
    不符合预期效果,就可以使用线程组来实现

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-11;
     * time: 18:24;
     */
    class ThreadGroup1 {
        //线程分组模拟赛跑
        public static void main(String[] args) {
            ThreadGroup threadGroup = new ThreadGroup("Group");
            Thread t1 = new Thread(threadGroup, new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("选手一到达了终点");
                }
            });
    
            Thread t2 = new Thread(threadGroup, new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("选手二到达了终点");
                }
            });
            t2.start();
            t1.start();
    
            while (threadGroup.activeCount() != 0) {
    
            }
            System.out.println("所有选手到达了终点");
        }
    }
    
    

    在这里插入图片描述
    线程组常用的方法
    在这里插入图片描述

    线程安全问题

    来看单线程情况下让count分别自增和自减10000次

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 12:03;
     */
    class Counter {
        private static int count = 0;
        public void increase(){
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease(){
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount(){
            return count;
        }
    }
    public class ThreadDemo16 {
        public static void main(String[] args) {
            //单线程
            Counter counter = new Counter();
            counter.increase();
            counter.decrease();
            System.out.println(counter.getCount());
        }
    }
    
    

    结果符合预期
    在这里插入图片描述

    如果想使程序的执行速度快,就可以使用多线程的方式来执行。在来看多线程情况下的问题

    public class ThreadDemo16 {
        public static void main(String[] args) throws InterruptedException {
    
            //多线程情况下
            Counter counter = new Counter();
            Thread thread1 = new Thread(()->{
                counter.decrease();
            });
    
            Thread thread2 = new Thread(()->{
                counter.increase();
            });
    
            thread1.start();
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(counter.getCount());
    
                    /*
            //单线程
            Counter counter = new Counter();
            counter.increase();
            counter.decrease();
            System.out.println(counter.getCount());
             */
        }
    }
    
    

    执行结果:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    每次的执行结果是不一样的。这就是多线程的不安全问题
    在这里插入图片描述
    预期的结果是0,但结果却不是。
    线程不安全问题的原因:
    1.CPU的抢占式执行
    2.多个线程共同操作一个变量
    3.内存可见性
    4.原子性问题
    5.编译器优化(指令重排)

    多个线程操作同一个变量
    如果多个线程操作的不是一个变量,就不会发生线程的不安全问题,可以将上面的代码修改如下:👇

    public class ThreadDemo16 {
        static int res1 = 0;
        static int res2 = 0;
    
        public static void main(String[] args) throws InterruptedException {
    
            Counter counter = new Counter();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    res1 = counter.getCount();
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    res2 = counter.getCount();
                }
            });
            System.out.println(res1 + res2);
    /*
            //多线程情况下
            Counter counter = new Counter();
            Thread thread1 = new Thread(()->{
                counter.decrease();
            });
    
            Thread thread2 = new Thread(()->{
                counter.increase();
            });
    
            thread1.start();
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(counter.getCount());
            */
    
    
                    /*
            //单线程
            Counter counter = new Counter();
            counter.increase();
            counter.decrease();
            System.out.println(counter.getCount());
             */
        }
    }
    
    

    这样就可以了:
    在这里插入图片描述

    内存不可见问题:看下面的代码,是不是到thread2执行的时候,就会改变num的值,从而终止了thread1呢?

    package ThreadDeom;
    
    import java.util.Scanner;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 13:03;
     */
    public class ThreadDemo17 {
    
        private static int num = 0;
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (num == 0){}
                }
            });
    
            thread1.start();
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    Scanner scanner = new Scanner(System.in);
                    System.out.println("输入一个数字来终止线程thread1");
                    num = scanner.nextInt();
                }
            });
    
            thread2.start();
        }
    
    }
    
    

    结果是不能的:
    在这里插入图片描述
    输入一个数字后回车,并没有让thread1的循环结束。这就是内存不可见的问题。
    原子性的问题
    上面的++和–操作其实是分三步来执行的
    在这里插入图片描述
    假设在第二部的时候,有另外一个线程也来修改值,那么就会出现脏数据的问题了。
    所以就会发生线程的不安全问题

    编译器优化
    编译器的优化会打乱原本程序的执行顺序,就有可能导致线程的不安全问题发生。
    在单线程不会发生线程的不安全问题,在多线程就可能会不安全。

    volatile关键字

    可以使用volatile关键字,这个关键字可以解决指令重排和内存不可见的问题。
    在这里插入图片描述
    加上volatile关键字之后的运行结果
    在这里插入图片描述
    但是volatile关键字不能解决原子性的问题👇:

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:02;
     */
    
    class Counter1 {
        private static volatile int count = 0;
    
        public void increase() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease() {
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount() {
            return count;
        }
    }
    
    
    public class ThreadDemo18 {
        public static void main(String[] args) throws InterruptedException {
            Counter1 counter1 = new Counter1();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    counter1.decrease();
                }
            });
    
            Thread thread2 = new Thread(() -> {
                counter1.increase();
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter1.getCount());
        }
    }
    
    

    在这里插入图片描述
    在这里插入图片描述
    那么Java中如何解决原子性的问题呢👇

    Java中的锁

    Java中的加锁操作有两种:
    1.synchronized锁(jvm层的解决方案,也叫监视器锁)
    在操作系统的层面使用的是互斥锁(mutex lock)
    在Java中放在了对象头中。
    2.手动锁Lock
    操作锁的流程
    1.尝试获取锁
    2.使用锁
    3.释放锁

    synchronized锁

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:12;
     */
    class Counter2 {
        private static volatile int count = 0;
    
        public void increase() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease() {
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount() {
            return count;
        }
    }
    
    
    public class ThreadDemo19 {
        public static void main(String[] args) throws InterruptedException {
            //声明锁对象,任何的对象都可以作为锁
            Object lock = new Object();
            Counter2 counter2 = new Counter2();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //使用锁
                    synchronized (lock) {
                        counter2.decrease();
    
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    counter2.increase();
                }
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter2.getCount());
        }
    }
    
    

    结果是:
    在这里插入图片描述

    synchronized使用场景

    1.使用synchronized来修饰代码块(可以给任意的对象进行加锁操作)

    public class ThreadDemo19 {
        public static void main(String[] args) throws InterruptedException {
            //声明锁对象,任何的对象都可以作为锁
            Object lock = new Object();
            Counter2 counter2 = new Counter2();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //使用锁
                    synchronized (lock) {
                        counter2.decrease();
    
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    counter2.increase();
                }
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter2.getCount());
        }
    }
    
    

    在这里插入图片描述

    2.使用synchronized来修饰静态方法(对当前的类进行加锁的操作)

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:02;
     */
    
    class Counter1 {
        private static volatile int count = 0;
    
        public void increase() {
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    
        public void decrease() {
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    
        public int getCount() {
            return count;
        }
    }
    
    
    public class ThreadDemo18 {
        public static void main(String[] args) throws InterruptedException {
            Counter1 counter1 = new Counter1();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    counter1.decrease();
                }
            });
    
            Thread thread2 = new Thread(() -> {
                counter1.increase();
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(counter1.getCount());
        }
    }
    
    

    在这里插入图片描述

    3.使用synchronized来修饰普通的方法(对当前类的实例来进行加锁)

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 14:12;
     */
    public class ThreadDemo20 {
        private static int num = 0;
        private static final int maxSize = 100000;
    
        public static void main(String[] args) throws InterruptedException {
    
            ThreadDemo20 threadDemo20 = new ThreadDemo20();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    threadDemo20.increase();
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                   threadDemo20. decrease();
                }
            });
    
            thread1.start();
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(num);
        }
    
        //给静态的方法进行加锁,被加的锁是当前的对象。
    //    public synchronized static void increase(){
    
        //给普通的方法进行加锁的操作
        public synchronized void increase() {
    
            for (int i = 0; i < maxSize; i++) {
                num++;
            }
        }
    
        //    public synchronized static void decrease(){
        public synchronized void decrease() {
    
            for (int i = 0; i < maxSize; i++) {
                num--;
            }
        }
    }
    

    在这里插入图片描述

    synchronized注意事项

    1.加锁的时候一定要使用同一把锁对象

    Lock类的使用

    也叫手动锁

    package ThreadDeom;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 18:32;
     */
    public class ThreadDemo22 {
        private static int number = 0;
        private static final int maxSize = 100000;
    
        public static void main(String[] args) {
            //创建lock锁对象,lock是接口,不能实列化
            Lock lock = new ReentrantLock();
    
    
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try {
                        number++;
    
                    } finally {
                        lock.unlock();
                    }
                }
            });
    
    
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try {
                        number--;
    
                    } finally {
                        lock.unlock();
                    }
    
                }
            });
    
            System.out.println(number);
        }
    }
    
    

    在这里插入图片描述

    Lock锁使用的注意事项

    1.lock()操作一定要放在try外面
    如果放在try的里面:
    1.try中抛出了异常,还没有加锁就释放了finally中的锁的操作了
    2.如果放在了try,没加锁就释放了锁,就会抛出异常,就会将业务代码中的异常吞噬掉👇
    如果一定要放的话,将lock()放在try的第一行。

    package ThreadDeom;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 18:49;
     */
    public class ThreadDemo23 {
        public static void main(String[] args) {
            Lock lock = new ReentrantLock();
    
            try{
    
                System.out.println(1/0);
                lock.lock();
    
            } finally {
                lock.unlock();
            }
        }
    }
    
    

    在这里插入图片描述

    公平锁、非公平锁

    公平锁的调度:
    一个线程释放锁。
    主动唤醒“需要得到锁”的队列来得到锁。
    非公平锁
    当一个线程释放锁之后,另一个线程刚好执行到获取锁的代码就可以直接获取锁。
    Java中的所有锁默认都是非公平锁。
    非公平锁的性能更高。
    ReentrantLock可以设置非公平锁。
    公平锁

    package ThreadDeom;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 19:22;
     */
    public class ThreadDemo24 {
        public static void main(String[] args) throws InterruptedException {
            ReentrantLock reentrantLock = new ReentrantLock();
    
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    reentrantLock.lock();
                    try {
                        System.out.println("thread1");
    
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    reentrantLock.lock();
                    try {
                        System.out.println("thread2");
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            });
    
            Thread.sleep(100);
            thread1.start();
            thread2.start();
        }
    }
    
    

    打印的结果是无序的
    在这里插入图片描述
    如果设置为公平锁:👇
    在这里插入图片描述

    在这里插入图片描述
    thread1和thread2 交替输出

    synchronzied 和 Lock 的区别

    1.synchronzied可以自动的进行加锁和释放锁,而Lock需要手动的加锁、释放锁。
    2.Lock是Java层面的锁实现,而synchronzied 是JVM层面锁的实现
    3.synchronzed 即可以修饰代码块,又可以修饰普通方法和静态的方法,而Lock 只能修饰代码块
    4. synchronized 实现的是 非公平的锁,而Lock 可以实现公平锁。

    5.lock的灵活性更高

    死锁

    在两个或两个以上的线程运行中,因为资源的抢占而造成线程一直等待的问题。
    看👇:

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 19:48;
     */
    public class ThreadDemo25 {
        public static void main(String[] args) throws InterruptedException {
            Object lockA = new Object();
            Object lockB = new Object();
    
    
            Thread thread1 = new Thread(() -> {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockA");
                    //让线程2获取lockB
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (lockB) {
    
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                    }
                }
    
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //线程2获取资源B
                    synchronized (lockB) {
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                        //让线程1先获取到锁lockA
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (lockA) {
                            System.out.println(Thread.currentThread().getName() + "获取到lockA");
    
                        }
    
                    }
    
                }
            });
            
            thread1.start();
    
            thread2.start();
        }
    }
    
    

    这就造成了死锁
    在这里插入图片描述

    造成死锁的四个条件

    1.互斥条件:
    当资源被一个线程拥有之后,就不能被其它的线程拥有了
    2.拥有请求条件:
    当一个线程拥有了一个资源之后,又试图请求另一个资源。
    3.不可剥夺条件:
    当一个线程拥有了一个资源之后,如果不是这个线程主动的释放资源,其他线程就不能拥有这个线程。
    4.环路等待条件:
    两个或两个以上的线程拥有了资源之后,试图获取对方的资源的时候形成了一个环路。

    死锁的解决方案

    解决请求拥有和环路等待。
    最有效的解决方案就是控制加锁的顺序。

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 20:25;
     */
    public class ThreadDemo26 {
        public static void main(String[] args) throws InterruptedException {
            Object lockA = new Object();
            Object lockB = new Object();
    
    
            Thread thread1 = new Thread(() -> {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockA");
                    //让线程2获取lockB
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (lockB) {
    
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                    }
                }
    
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lockA) {
                        System.out.println(Thread.currentThread().getName() + "获取到lockA");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (lockB) {
                            System.out.println(Thread.currentThread().getName() + "获取到lockB");
    
                        }
    
                    }
    
                }
            });
    
            thread1.start();
    
            thread2.start();
        }
    }
    
    

    在这里插入图片描述

    线程间通信

    线程之间的通讯是指在一个线程中的操作可以影响另一个线程。

    wait/notify机制的原理

    拥有相同锁的线程之间才能使用wait/notify机制。
    wait()是Object()的方法,它的作用是是当前执行wait()方法的线程等待,在wati()所在的代码出停止执行,并释放锁,直到接到通知或者被中断为止。即在调用wait()的方法之前,线程必需先获取到对象级别的锁,也就是只能在同步方法或者同步块中使用wait()方法。
    如果在使用wait()方法之前线程没有获得相应的锁,那么程序在执行时就会抛出异常。
    notify()方法要在同步方法或者同步块中执行,即在调用notify()方法之前,线程必需要先获取到锁对象。如果线程没有持有锁对象的话,那么也会抛出异常。该方法用来通知可能在等待该锁的其它线程,如果有多个线程,那么则按照执行wait()方法的顺序来对处于wait()方法的线程发出通知,并使该线程重新获取锁。执行notify()方法之后,当前线程不会马上释放锁,处于wait()状态的线程也不会立马得到这个对象锁。而是要等notify的synchronized同步区域执行完成之后才会释放锁,处于wait()状态的线程才会得到锁对象。

    总结:wait()方法用于让线程停止运行,而notify()方法用于通知暂停的线程继续运行。
    在使用wait()或者notify()方法之前没有对象锁,就会报异常👇:

            lock.notify();
    
    

    在这里插入图片描述
    正确的使用之后

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-12;
     * time: 21:11;
     */
    public class ThreadDemo27 {
        //设置锁对象
    
        private static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
    
                @Override
    
                public void run() {
                    synchronized (lock) {
                        System.out.println("在wait()");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("被notify()唤醒之后");
                    }
                }
            });
            thread.start();
    
            Thread.sleep(1000);
    
            synchronized (lock) {
                lock.notify();
    
            }
    
        }
    }
    
    

    在这里插入图片描述

    注意:使用wait()方法的时候一定要和线程的锁对象是一个锁。

    notifyAll

    在多线程的情况下使用notify()方法只可以唤醒一个线程👇
    在这里插入图片描述

    package ThreadDeom;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 8:06;
     */
    public class ThreadDemo28 {
        //设置锁对象
    
        private static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(new Runnable() {
    
                @Override
    
                public void run() {
                    synchronized (lock) {
                        System.out.println("thread1在wait()");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("thread1被notify()唤醒之后");
                    }
                }
            });
    
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println("thread2在wait()");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("thread2被notify()唤醒之后");
                }
            });
    
            Thread thread3 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        System.out.println("thread3在wait()");
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("thread3被notify()唤醒之后");
                    }
    
                }
            });
    
    
            thread1.start();
            thread2.start();
            thread3.start();
            Thread.sleep(1000);
    
            synchronized (lock) {
    
                System.out.println("主线程调用notify()之后");
    
                lock.notify();
    
            }
    
        }
    }
    
    

    那么如果使用notifyAll()方法呢?
    在这里插入图片描述
    可以看到所有的线程都被唤醒了
    在这里插入图片描述

    那么使用notify()唤醒的线程有没有什么顺序呢?
    使用notify()唤醒线程的顺序是正序、倒序、还是随机的,这取决与JVM的具体实现,并不是所有的JVM在执行notify()时都是按照wait()的执行顺序进行唤醒的,也不是所有的notidyAll()都是按照wait()方法的倒序进行唤醒的,这取决于JVM的具体实现。
    wait()和notify()不能唤醒指定的线程。

    wait()和sleep()的区别

    也可以让wait()等待指定的时间,如果超过给定的时间,wait()不会无限期的等待下去.
    在这里插入图片描述
    没有被notify()唤醒,过了1000毫秒之后会自动停止。
    在这里插入图片描述

    wait()在不传入任何参数的时候,线程会进入waiting 的状态,而在wait()中加入一个大于0的参数的时候,线程会进入time_wating的状态。

    sleep()和wait()的区别 : 线程在sleep()的时候是不会释放锁的,而执行wait()的时候它就会释放锁。👇:

    package ThreadDeom;
    
    import jdk.nashorn.internal.ir.Block;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 8:45;
     */
    public class ThreadDemo29 {
        private static Object lock = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (lock) {
                        try {
                            System.out.println("thread获取到了锁");
                            //如果sleep释放锁的话,会在thread获取到了锁和thread释放了锁之间打印
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
    
                    }
                    System.out.println("thread释放了锁");
    
                }
            });
            thread.start();
    
            //让thread 先获取到锁
            Thread.sleep(1000);
            synchronized (lock) {
                System.out.println("主线程获取到了锁");
            }
        }
    }
    
    

    在这里插入图片描述

    可以看到线程在sleep()的时候,线程是不会释放锁的。再来看看wait()方法👇:
    在这里插入图片描述

    在这里插入图片描述
    线程使用wait()的时候它就会释放掉锁。

    1.wait()和sleep()都是让线程进行休眠的
    2.wait()和sleep()方法都有可能在执行的过程接收到线程终止的通知
    3.wait()必须和synchronzied一起使用,而sleep()不用。
    4.wait()会释放锁,而sleep()不会释放锁。
    5.wait()时Object的方法,而sleep()时Thread的方法。
    6.默认情况下,wait()不传任何的参数的情况下,wait()会进入waiting的状态,如果传递了参数,wait()会进入time_waiting的状态。而sleep()进入的是time_waiting的状态。
    sleep(0) 和wait(0)的区别:
    1.sleep(0)表示0毫秒之后继续执行,而wait(0)表示线程会一直休眠下去wait(0)和wait()是一样的,wait()的源码就是调用了wait(0)方法。
    2.sleep(0)表示重新出发一次CPU的竞争。
    为什么wait()会释放锁,而sleep()不会释放锁?
    sleep()需要传递一个最大的等待时间,也就是说sleep()是可控的,而wait()是不可以传递参数的,从设计的层面来说,如果让wait()一直持有所得话,那么线程就可能一直阻塞。
    为什么wait()是Object的方法,而sleep()是线程的方法?
    wait()需要操作锁,而锁是属于对象级别的,所有的锁都是放在对象头中的,它不是线程级别的,一个线程可以有多把的锁,为了灵活,就将wait()放在Object中了。

    LockSupport park()/unpark()

    使用LockSupport可以解决wait()/notify()随机唤醒的问题。

    package ThreadDeom;
    
    import java.util.concurrent.locks.LockSupport;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 9:36;
     */
    public class ThreadDemo30 {
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //让线程休眠
                    LockSupport.park();
                    System.out.println("unPark()了thread1");
                }
            });
    
            Thread thread2 = new Thread(() -> {
                LockSupport.park();
                System.out.println("unPark()了thread2");
    
            });
    
    
            Thread thread3 = new Thread() {
                @Override
                public void run() {
                    LockSupport.park();
                    System.out.println("unPark()了thread3");
    
                }
            };
    
    
            thread1.start();
            thread2.start();
            thread3.start();
    
    
            LockSupport.unpark(thread1);
            LockSupport.unpark(thread2);
    
    
        }
    }
    
    

    在这里插入图片描述

    Java线程池

    线程的缺点:
    1.线程的创建它会开辟本地方法栈、JVM栈、程序计数器私有的内存,同时消耗的时候需要销毁以上三个区域,因此频繁的创建和销毁线程比较消耗系统的资源。
    2.在任务量远远大于线程可以处理的任务量的时候,不能很好的拒绝任务。
    所以就有了线程池:
    使用池化的而技术来管理和使用线程。

    线程池的优点

    1.可以避免频繁的创建和销毁线程
    2.可以更好的管理线程的个数和资源的个数。
    3.线程池拥有更多的功能,比如线程池可以进行定时任务的执行。
    4.线程池可以更友好的拒绝不能处理的任务。

    线程池的6种创建方式

    一共有7种创建方式
    创建方式一:
    创建固定个数的线程池:

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 10:24;
     */
    public class ThreadPoolDemo1 {
        public static void main(String[] args) {
            //创建一个固定个数的线程池
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            //执行任务
            for (int i = 0; i < 10; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("线程名" + Thread.currentThread().getName());
                    }
                });
            }
    
        }
    }
    
    

    在这里插入图片描述
    那么如果执行次数大于10次呢?
    线程池不会创建新的线程,它会复用之前的线程。
    在这里插入图片描述

    在这里插入图片描述
    那么如果只执行两个任务呢?它创建了是10个线程还是两个线程呢?
    我们可以使用Jconsole来看一看:
    在这里插入图片描述

    在这里插入图片描述

    结果是只有2个线程被创建。

    创建方式二:
    创建带有缓存的线程池:
    适用于短期有大量的任务的时候使用

    public class ThreadPoolDemo2 {
        public static void main(String[] args) {
            //创建带缓存的线程池
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 100; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    

    在这里插入图片描述

    方式三:
    创建执行定时任务的线程池

    package ThreadPoolDemo;
    
    import java.util.Date;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 11:32;
     */
    public class ThreadPoolDemo3 {
        public static void main(String[] args) {
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
    
            System.out.println("执行定时任务前的时间:" + new Date());
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务的时间:" + new Date());
                }
            },1,2, TimeUnit.SECONDS);
        }
    }
    
    

    在这里插入图片描述
    执行任务的四个参数的意义:
    参数1:延迟执行的任务
    参数2:延迟一段时间后执行
    参数3:定时任务执行的频率
    参数4:配合前两个参数使用,是2、3参数的时间单位

    还有两种执行的方法:
    只会执行一次的方法:
    在这里插入图片描述

    在这里插入图片描述
    第三种的执行方式:
    在这里插入图片描述

    在这里插入图片描述
    那么这种的执行方式和第一种的执行方式有什么区别呢?
    当在两种执行的方式中分别加上sleep()之后:
    在这里插入图片描述

    方式一:
    在这里插入图片描述

    方式三:
    在这里插入图片描述
    结论很明显了:
    第一种方式是以上一个任务的开始时间+定时的时间作为当前任务的开始时间

    第三种方式是以上一个任务的结束时间来作为当前任务的开始时间。

    创建方式四:

    package ThreadPoolDemo;
    
    import java.util.Date;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 12:38;
     */
    public class ThreadPoolDemo4 {
        public static void main(String[] args) {
            //创建单个执行任务的线程池
            ScheduledExecutorService scheduledExecutorService
                    = Executors.newSingleThreadScheduledExecutor();
            System.out.println("执行任务之前" + new Date());
            scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
                @Override
                public void run() {
                    System.out.println("我是SingleThreadSchedule"+ new Date());
                }
            },3,1, TimeUnit.SECONDS);
        }
    }
    
    

    在这里插入图片描述
    在这里插入图片描述
    创建方式五:
    创建单个线程的线程池

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 12:55;
     */
    public class ThreadPoolDemo5 {
        public static void main(String[] args) {
            //创建单个线程的线程池
    
            ExecutorService executorService = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 20; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("线程名 " +  Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    

    在这里插入图片描述
    创建单个线程池的作用是什么?
    1.可以避免频繁创建和销毁线程所带来的性能的开销
    2.它有任务队列,可以存储多余的任务
    3.可以更好的管理任务
    4.当有大量的任务不能处理的时候,可以友好的执行拒绝策略
    创建方式六:
    创建异步线程池根据当前CPU来创建对应个数的线程池

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 13:12;
     */
    public class ThreadPoolDemo6 {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newWorkStealingPool();
    
            for (int i = 0; i < 10; i++) { 
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("线程名" + Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    

    在这里插入图片描述
    运行结果为什么什么都没有呢?
    看下面的异步与同步的区别就知道了。
    加上这个
    在这里插入图片描述

    就可以输出结果了
    在这里插入图片描述

    线程池的第七种创建方式

    前六种的创建方式有什么问题呢?
    1.线程的数量不可控(比如带缓存的线程池)
    2.工作任务量不可控(默认的任务队列的大小时Integer.MAX_VALUE),任务比较大肯会导致内存的溢出。
    所以就可以使用下面的创建线程池的方式了:

    package ThreadPoolDemo;
    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadFactory;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 15:05;
     */
    public class ThreadPoolDemo7 {
        private static int threadId = 0;
    
        public static void main(String[] args) {
            ThreadFactory threadFactory = new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setName("我是threadPool-" + ++threadId);
                    return thread;
                }
            };
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 100,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(12),
                    threadFactory, new ThreadPoolExecutor.AbortPolicy());
    
            for (int i = 0; i < 15; i++) {
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName());
                    }
                });
            }
        }
    }
    
    
    

    在这里插入图片描述

    参数说明:
    在这里插入图片描述

    参数一:核心线程数|线程池正常情况下的线程 数量
    参数二:最大线程数|当有大量的任务的时候可以创建的最多的线程数
    参数三:最大线程的存活时间
    参数四:配合参数三一起使用的表示参数三的时间单位
    参数五:任务队列
    参数六:线程工厂
    参数七:决绝策略

    注意事项:最大的线程数要大于等于核心的线程数
    在这里插入图片描述

    在这里插入图片描述

    五种拒绝策略

    在这里插入图片描述
    在这里插入图片描述

    为什么拒绝策略可以舍弃最新的任务或者最旧的任务呢?
    因为LinkedBlockingDeque时FIFO的。
    第五种:自定义的拒绝策略
    在这里插入图片描述

    在这里插入图片描述

    ThreadPoolExecutor的执行方式

    在这里插入图片描述

    package ThreadPoolDemo;
    
    import java.util.concurrent.*;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 16:58;
     */
    public class ThreadPoolDemo9 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 100,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.DiscardOldestPolicy());
    
    
            //线程池的执行方式一
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("使用了execute()执行了线程池");
                }
            });
    
            //线程池的执行方式二
    
            Future<String> futureTask =
                    threadPoolExecutor.submit(new Callable<String>() {
                        @Override
                        public String call() throws Exception {
                            return "使用submit(new Callable<>())执行了线程池";
                        }
                    });
    
            System.out.println(futureTask.get());
            
            
    
        }
    }
    
    

    无返回值的执行方式
    在这里插入图片描述

    有返回值的执行方式
    在这里插入图片描述

    ThreadPoolExecutor的执行流程

    当任务量小于核心线程数的时候,ThreadPoolExecutor会创建线程来执行任务
    当任务量大于核心的线程数的时候,并且没有空闲的线程时候,且当线程池的线程数小于最大线程数的时候,此时会将任务存放到任务队列中
    如果任务队列也被存满了,且最大线程数大于线程池的线程数的时候,会创建新的线程来执行任务。
    如果线程池的线程数等于最大的线程数,并且任务队列也已经满了,就会执行拒绝策略。👇
    在这里插入图片描述

    线程池的终止

    shutdown()
    线程池的任务会执行完
    shutdownNow()
    立即终止线程池,线程池的任务不会执行完

    线程池的状态

    在这里插入图片描述

    异步、同步

    1. Java 线程 同步与异步
      多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理。显然这是由于全局资源造成的,有时为了解决此问题,优先考虑使用局部变量,退而求其次使用同步代码块,出于这样的安全考虑就必须牺牲系统处理性能,加在多线程并发时资源挣夺最激烈的地方,这就实现了线程的同步机制

    同步
    A线程要请求某个资源,但是此资源正在被B线程使用中,因为同步机制存在,A线程请求不到,怎么办,A线程只能等待下去

    异步
    A线程要请求某个资源,但是此资源正在被B线程使用中,因为没有同步机制存在,A线程仍然请求的到,A线程无需等待
    同步的方式:
    1.发送请求
    2.等待执行完成
    3.有结果的返回

    异步的方式
    1.发请求
    2.执行完成
    3.另一个线程异步处理
    4.处理完成之后返回回调结果

    显然,同步最最安全,最保险的。而异步不安全,容易导致死锁,这样一个线程死掉就会导致整个进程崩溃,使用异步的机制,性能会有所提升

    线程工厂

    设想这样一种场景,我们需要一个线程池,并且对于线程池中的线程对象,赋予统一的线程优先级、统一的名称、甚至进行统一的业务处理或和业务方面的初始化工作,这时工厂方法就是最好用的方法了

    package ThreadPoolDemo;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 11:12;
     */
    public class ThreadFactoryDemo {
        public static void main(String[] args) {
            MyThreadFactory myThreadFactory = new MyThreadFactory();
            ExecutorService executorService =  Executors.newFixedThreadPool(10,myThreadFactory);
    
            for (int i = 0; i < 10; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("使用线程工厂设置的线程名:"+ Thread.currentThread().getName() +
                                " 使用线程工厂设置的线程的优先级" + Thread.currentThread().getPriority());
                    }
                });
            }
    
    
    
        }
    
        private static int count = 0;
         static class MyThreadFactory implements ThreadFactory{
             @Override
             public Thread newThread(Runnable r) {
                 Thread thread = new Thread(r);
                 thread.setPriority(8);
                 thread.setName("thread--" + count++);
                 return thread;
             }
         }
    
    }
    
    
    
    

    在这里插入图片描述

    SimpleDateFormat非线程安全问题

    实现1000个线程的时间格式化

    package SimpleDateFormat;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 17:30;
     */
    public class SimpleDateFormat1 {
        private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
    
        public static void main(String[] args) {
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,10,100,
                    TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(1000),new ThreadPoolExecutor.DiscardPolicy());
    
    
            for (int i = 0; i < 1001; i++) {
                int finalI = i;
                threadPoolExecutor.submit(new Runnable() {
                    @Override
                    public void run() {
                        Date date = new Date(finalI * 1000);
                        myFormatTime(date);
                    }
                });
            }
    
            threadPoolExecutor.shutdown();
        }
    
        private static void myFormatTime(Date date){
            System.out.println(simpleDateFormat.format(date));
        }
    
    }
    
    

    产生了线程不安全的问题👇:

    在这里插入图片描述
    这是因为:
    在这里插入图片描述
    多线程的情况下:
    在这里插入图片描述
    线程1在时间片用完之后,线程2来setTime()那么线程1的得到了线程2的时间。

    所以可以使用加锁的操作:

    在这里插入图片描述

    就不会有重复的时间了
    在这里插入图片描述
    但是虽然可以解决线程不安全的问题,但是排队等待锁,性能就会变得低

    所以可以使用局部变量:
    在这里插入图片描述
    也解决了线程不安全的问题:
    在这里插入图片描述

    但是每次也都会创建新的私有变量
    那么有没有一种方案既可以避免加锁排队执行,又不会每次创建任务的时候不会创建私有的变量呢?
    那就是ThreadLocal👇:

    ThreadLocal

    ThreadLocal的作用就是让每一个线程都拥有自己的变量。
    那么选择锁还是ThreadLocal?
    看创建实列对象的复用率,如果复用率比较高的话,就使用ThreadLocal。

    ThreadLocal的原理

    类ThreadLocal的主要作用就是将数据放到当前对象的Map中,这个Map时thread类的实列变量。类ThreadLocal自己不管理、不存储任何的数据,它只是数据和Map之间的桥梁。
    执行的流程:数据—>ThreadLocal—>currentThread()—>Map。
    执行后每个Map存有自己的数据,Map中的key中存储的就是ThreadLocal对象,value就是存储的值。每个Thread的Map值只对当前的线程可见,其它的线程不可以访问当前线程对象中Map的值。当前的线程被销毁,Map也随之被销毁,Map中的数据如果没有被引用、没有被使用,则随时GC回收。

    ThreadLocal常用方法

    在这里插入图片描述

    set(T):将内容存储到ThreadLocal
    get():从线程去私有的变量
    remove():从线程中移除私有变量

    package ThreadLocalDemo;
    
    import java.text.SimpleDateFormat;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 18:37;
     */
    public class ThreadLocalDemo1 {
        private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
    
        public static void main(String[] args) {
            //设置私有变量
            threadLocal.set(new SimpleDateFormat("mm:ss"));
    
            //得到ThreadLocal
            SimpleDateFormat simpleDateFormat = threadLocal.get();
            
            //移除
            threadLocal.remove();
        }
    }
    
    

    ThreadLocal的初始化

    ThreadLocal提供了两种初始化的方法
    initialValue()和
    initialValue()初始化:

    package ThreadLocalDemo;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 19:07;
     */
    public class ThreadLocalDemo2 {
        //创建并初始化ThreadLocal
    
        private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal() {
            @Override
            protected SimpleDateFormat initialValue() {
                System.out.println(Thread.currentThread().getName() + "执行了自己的threadLocal中的初始化方法initialValue()");
                return new SimpleDateFormat("mm:ss");
            }
        };
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                Date date = new Date(5000);
                System.out.println("thread0格式化时间之后得结果时:" + threadLocal.get().format(date));
            });
            thread1.setName("thread0");
            thread1.start();
    
    
            Thread thread2 = new Thread(() -> {
                Date date = new Date(6000);
                System.out.println("thread1格式化时间之后得结果时:" + threadLocal.get().format(date));
            });
            thread2.setName("thread1");
    
            thread2.start();
    
        }
    }
    
    

    在这里插入图片描述
    withInitial方法初始化:

    package ThreadLocalDemo;
    
    import java.util.function.Supplier;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 17:23;
     */
    public class ThreadLocalDemo3 {
        private static ThreadLocal<String> stringThreadLocal =
                ThreadLocal.withInitial(new Supplier<String>() {
                    @Override
                    public String get() {
                        System.out.println("执行了withInitial()方法");
                        return "我是" + Thread.currentThread().getName() + "的ThreadLocal";
                    }
                });
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                System.out.println(stringThreadLocal.get());
            });
    
            thread1.start();
    
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(stringThreadLocal.get());
                }
            });
    
            thread2.start();
        }
    }
    
    

    在这里插入图片描述
    注意:
    ThreadLocal如果使用了set()方法的话,那么它的初始化方法就不会起作用了。
    来看:👇

    package ThreadLocalDemo;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 18:43;
     */
    
    class Tools {
        public static ThreadLocal t1 = new ThreadLocal();
    }
    
    class ThreadA extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadA中取值:" + Tools.t1.get());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public class ThreadLocalDemo4 {
        public static void main(String[] args) throws InterruptedException {
            //main是ThreadA 的 父线程 让main线程set,ThreadA,是get不到的
    
            if (Tools.t1.get() == null) {
                Tools.t1.set("main父线程的set");
            }
    
            System.out.println("main get 到了: " + Tools.t1.get());
    
    
            Thread.sleep(1000);
            ThreadA a = new ThreadA();
            a.start();
    
        }
    }
    
    

    在这里插入图片描述
    类ThreadLocal不能实现值的继承,那么就可以使用InheritableThreadLocal了👇

    InheritableThreadLocal的使用

    使用InheritableThreadLocal可以使子线程继承父线程的值

    在这里插入图片描述
    在来看运行的结果:
    在这里插入图片描述

    子线程有最新的值,父线程依旧是旧的值

    package ThreadLocalDemo;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 19:07;
     */
    class ThreadB extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadB中取值:" + Tools.t1.get());
                if (i == 5){
                    Tools.t1.set("我是ThreadB中新set()");
                }
                try {
                    Thread.sleep(100);
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public class ThreadLocalDemo5 {
        public static void main(String[] args) throws InterruptedException {
            if (Tools.t1.get() == null) {
                Tools.t1.set("main父线程的set");
            }
    
            System.out.println("main get 到了: " + Tools.t1.get());
    
    
            Thread.sleep(1000);
            ThreadA a = new ThreadA();
            a.start();
            Thread.sleep(5000);
    
            for (int i = 0; i < 10; i++) {
                System.out.println("main的get是:" + Tools.t1.get());
                Thread.sleep(100);
            }
    
        }
    }
    
    

    在这里插入图片描述
    ThreadLocal的脏读问题
    来看👇

    package ThreadLocalDemo;
    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 19:49;
     */
    
    public class ThreadLocalDemo6 {
        private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
        private static class MyThread extends Thread {
            private static boolean flag = false;
    
            @Override
            public void run() {
                String name = this.getName();
    
                if (!flag) {
                    threadLocal.set(name);
                    System.out.println(name + "设置了" + name);
                    flag = true;
                }
    
                System.out.println(name + "得到了" + threadLocal.get());
            }
        }
    
        public static void main(String[] args) {
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10));
    
    
            for (int i = 0; i < 2; i++) {
                threadPoolExecutor.execute(new MyThread());
            }
    
            threadPoolExecutor.shutdown();
        }
    }
    
    

    在这里插入图片描述

    发生了脏读:
    线程池复用了线程,也复用了这个线程相关的静态属性,就导致了脏读
    那么如何避免脏读呢?
    去掉static 之后:
    在这里插入图片描述
    在这里插入图片描述

    单例模式与多线程

    单例模式就是全局唯一但是所有程序都可以使用的对象
    写单例模式步骤:
    1.将构造函数设置为私有的
    2.创建一个静态的类变量
    3.提供获取单例的方法

    立即加载/饿汉模式

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:02;
     */
    //饿汉方式实现单例模式
    public class Singleton {
        //1.将构造函数设置为私有的,不然外部可以创建
        private Singleton(){
        }
        
        //2.创建静态的类变量(让第三步的方法进行返回)
        private static Singleton singleton = new Singleton();
        
        //给外部接口提供的获取单例的方法
        public static Singleton getInstance(){
            return singleton;
        }
        
    }
    
    

    测试饿汉的单例模式

        //测试饿汉方式实现的单例模式,创建两个线程,看是不是得到了一个实列对象,如果为true就说明饿汉的单例模式没有问题
    
        static Singleton singleton1 = null;
        static Singleton singleton2 = null;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                singleton1 = Singleton.getInstance();
            });
            Thread thread2 = new Thread(() -> {
                singleton2 = Singleton.getInstance();
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(singleton1 == singleton2);
        }
    
    

    在这里插入图片描述

    延时加载/懒汉模式

    不会随着程序的启动而启动,而是等到有人调用它的时候,它才会初始化

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:22;
     */
    //懒汉方式实现单例模式
    public class Singleton2 {
    
        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() {
                if (singleton == null) {
                    singleton = new Singleton();
                }
    
                return singleton;
            }
        }
    }
    
    

    那么这样写有什么问题呢?
    我们来看看多线程情况下的懒汉方式实现单例模式:

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:22;
     */
    //懒汉方式实现单例模式
    public class Singleton2 {
    
        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() throws InterruptedException {
                if (singleton == null) {
                    Thread.sleep(100);
                    singleton = new Singleton();
                }
    
                return singleton;
            }
        }
    
        static Singleton singleton1 = null;
        static Singleton singleton2 = null;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                try {
                    singleton1 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            Thread thread2 = new Thread(() -> {
                try {
                    singleton2 = Singleton.getInstance();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
    
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(singleton1 == singleton2);
        }
    
    }
    
    

    结果:
    在这里插入图片描述

    所以发生了线程不安全的问题
    那么要如何更改呢?
    加锁:👇
    在这里插入图片描述
    结果就是true了:
    在这里插入图片描述
    给方法加锁可以实现线程安全,但是所锁的粒度太大。
    使用双重校验锁优化后:

        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() {
                if (singleton == null) {
                    synchronized (Singleton.class) {
                        if (singleton == null) {
                            singleton = new Singleton();
                        }
                    }
                }
    
                return singleton;
            }
        }
    
    

    在这里插入图片描述
    那么这样写就没有问题了吗?

    不是的:有可能还会发生指令重排的问题
    当有线程在进行第一次初始化的时候,就有可能发生问题👇
    先来看初始化的过程
    1。先分配内存空间
    2.初始化
    3.将singleton指向内存

    有可能指令重排序之后:
    线程1执行的顺序变成了 1 --> 3 --> 2
    在线程1执行完1、3之后时间片使用完了
    线程2再来执行,线程2得到了未初始化的singleton,也就是的到了一个空的对象
    也就发生了线程不安全的问题

    那么要如何解决指令重排序的问题呢?
    那就是使用volatile关键字👇:

    /**
     * user:ypc;
     * date:2021-06-13;
     * time: 21:22;
     */
    //懒汉方式实现单例模式
    public class Singleton2 {
    
        static class Singleton {
            //1.设置私有的构造函数
            private Singleton() {
            }
    
            //2.提供一个私有的静态变量
            private static volatile Singleton singleton = null;
    
            //3.提供给外部调用,返回一个单例对象给外部
    
            public static Singleton getInstance() {
                if (singleton == null) {
                    synchronized (Singleton.class) {
                        if (singleton == null) {
                            singleton = new Singleton();
                        }
                    }
                }
    
                return singleton;
            }
        }
    
    
    

    这样就没有问题了

    饿汉/懒汉对比

    饿汉方式:
    优点:实现简单,不存在线程安全的问题,因为饿汉的方式是随着程序的启动而初始化的,因为类加载是线程安全的,所以它是线程安全的。
    缺点:随着程序的启动而启动,有可能在整个程序的运行周期都没有用到,这样就带来了不必要的开销。

    阻塞队列的实现

    import java.util.Random;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 8:57;
     */
    public class MyBlockingQueue {
        private int[] values;
        private int first;
        private int last;
        private int size;
    
        MyBlockingQueue(int maxSize) {
            this.values = new int[maxSize];
            this.first = 0;
            this.last = 0;
            this.size = 0;
        }
    
        public void offer(int val) throws InterruptedException {
    
            synchronized (this) {
                if (this.size == values.length) {
                    this.wait();
                }
                this.values[last++] = val;
                size++;
    
                //变为循环队列
                if (this.last == values.length) {
                    this.last = 0;
                }
    
                //唤醒消费者
                this.notify();
            }
    
        }
    
        public int poll() throws InterruptedException {
            int result = 0;
            synchronized (this) {
                if (size == 0) {
                    this.wait();
                }
                result = this.values[first++];
                this.size--;
                if (first == this.values.length) {
                    this.first = 0;
                }
                //唤醒生产者开生产数据
                this.notify();
    
            }
            return result;
        }
    
        public static void main(String[] args) {
            MyBlockingQueue myBlockingQueue = new MyBlockingQueue(100);
            //生产者
            Thread thread1 = new Thread(() -> {
    
                while (true) {
                    try {
                        int num = new Random().nextInt(100);
                        myBlockingQueue.offer(num);
                        System.out.println("生产者生产数据:" + num);
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                }
            });
    
    
            //消费者
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            int res = myBlockingQueue.poll();
    
                            System.out.println("消费者消费数据:" + res);
                        }
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            thread1.start();
            thread2.start();
        }
    }
    
    

    可以看到生产者每生产一个数据都会被取走:
    在这里插入图片描述

    常见的锁策略

    乐观锁

    它认为程序在一般的情况下不会发生问题,所以他在使用的时候不会加锁,只有在数据修改的时候才会判断有没有锁竞争,如果没有就会直接修改数据,如果有就会返回失败信息给用户自行处理。

    CAS

    乐观锁的经典实现
    Compare and Swap
    CAS 实现的三个重要的属性:
    (V,A,B)

    V:内存中的值
    A:预期的旧值
    B:新值
    V == A? V -> B : 修改失败

    修改失之后:
    自旋对比和替换
    CAS 的底层实现:
    CAS在Java中是通过unsafe来实现的,unsafe时本地类和本地方法,它是c/c++实现的原生方法,通过调用操作系统Atomic:: cmpxchg原子指令来实现的

    CAS在java中的应用

    i++、i–问题
    可以使用加锁、ThreadLocal 解决问题
    也可以使用atomic.AtomicInteger来解决问题,底层也使用了乐观锁。

    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 10:12;
     */
    public class ThreadDemo1 {
        private static AtomicInteger count  = new AtomicInteger(0);
        private static final int MaxSize = 100000;
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < MaxSize; i++) {
                        count.getAndIncrement();//i++
                    }
                }
            });
    
            thread1.start();
    
            Thread thread2 = new Thread(()->{
                for (int i = 0; i < MaxSize; i++) {
                 count.getAndDecrement();//i--
                }
            });
    
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(count);
        }
    }
    
    

    在这里插入图片描述

    CAS 的ABA问题

    当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值A修改为B,又马上将其修改为A,此时其他线程不感知,还是会修改成功。
    来看:

    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 10:43;
     */
    public class ThreadDemo2 {
        //线程操作资源,原子类ai的初始值为4
        static AtomicInteger ai = new AtomicInteger(4);
        public static void main(String[] args) {
            new Thread(() -> {
                //利用CAS将ai的值改成5
                boolean b = ai.compareAndSet(4, 5);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为5:"+b);
                //休眠一秒
                try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
                //利用CAS将ai的值改回4
                b = ai.compareAndSet(5,4);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为4:"+b);
            },"A").start();
            new Thread(() -> {
                //模拟此线程执行较慢的情况
                try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
                //利用CAS将ai的值从4改为10
                boolean b = ai.compareAndSet(4, 10);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为10:"+b);
            },"B").start();
    
            //等待其他线程完成,为什么是2,因为一个是main线程,一个是后台的GC线程
            while (Thread.activeCount() > 2) {
                Thread.yield();
            }
    
            System.out.println("ai最终的值为:"+ai.get());
        }
    }
    
    

    上面例子模拟的是A、B两个线程操作一个资源ai,A的执行速度比B的快,在B执行前,A就已经将ai的值改为5之后马上又把ai的值改回为4,但是B不感知,所以最后B就修改成功了。

    那么会造成会有什么问题呢?
    假设A现在有100元,要给B转账100元,点击了两次转账按钮,第一次B只会得到100元,A现在剩余0元。第二次A是0元,预期的旧值是100,不相等,就不会执行转账操作。
    如果点击第二次按钮之前,A又得到了100元,B不能感知的到,此时A得到了转账100元,预期的旧值就是100,又会转给B100元。

    那么如何解决这个问题呢?👇

    ABA 问题的解决

    我们可以给操作加上版本号,每次修改的时候判断版本号和预期的旧值,如果不一样就不会执行操作了。
    即是预期的旧值和V值相等,但是版本号不一样,也不会执行操作。
    在Java中的实现:

    
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 11:05;
     */
    public class ThreadDemo3 {
        static AtomicStampedReference<Integer> ai = new AtomicStampedReference<>(4,0);
        public static void main(String[] args) {
            new Thread(() -> {
                //四个参数分别是预估内存值,更新值,预估版本号,初始版本号
                //只有当预估内存值==实际内存值相等并且预估版本号==实际版本号,才会进行修改
                boolean b = ai.compareAndSet(4, 5,0,1);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为5:"+b);
                try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
                b = ai.compareAndSet(5,4,1,2);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为4:"+b);
            },"A").start();
            new Thread(() -> {
                try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
                boolean b = ai.compareAndSet(4, 10,0,1);
                System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改为10:"+b);
            },"B").start();
    
            while (Thread.activeCount() > 2) {
                Thread.yield();
            }
    
            System.out.println("ai最终的值为:"+ai.getReference());
        }
    }
    
    

    在这里插入图片描述

    注意:里面的旧值对比的是引用。
    如果范围在-128 - 127 里,会使用缓存的值,如果超过了这个范围,就会重新来new对象
    可以将Integer 的高速缓存的值的边界调整

    悲观锁

    悲观锁认为只要执行多线程的任务,就会发生线程不安全的问题,所以正在进入方法之后会直接加锁。
    直接使用synchronzied关键字给方法加锁就可以了

    独占锁、共享锁、自旋锁、可重入锁

    独占锁:指的是这一把锁只能被一个线程所拥有
    比如:synchronzied、Lock
    共享锁: 指的是一把锁可以被多个线程同时拥有
    ReadWriterLock读写锁就是共享锁
    读锁就是共享的,将锁的粒度更加的细化

    import java.util.Date;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 11:42;
     */
    public class ThreadDemo4 {
        //创建读写锁
        public static void main(String[] args) {
            ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
            //读锁
    
            ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
            //写锁
            ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1000,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100), new ThreadPoolExecutor.DiscardPolicy());
    
    
            //任务一:读锁演示
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    readLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了读锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        readLock.unlock();
                    }
                }
            });
    
            //任务二:读锁演示
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    readLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了读锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        readLock.unlock();
                    }
                }
            });
    
            //任务三:写锁
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    writeLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了写锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        writeLock.unlock();
                    }
                }
            });
            //任务四:写锁
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    writeLock.lock();
    
                    try {
                        System.out.println(Thread.currentThread().getName() + "进入了写锁,时间:" + new Date());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        writeLock.unlock();
                    }
                }
            });
    
    
        }
    }
    
    

    在这里插入图片描述

    可重入锁:
    当一个线程拥有了锁之后,可以重复的进入,就叫可重入锁。
    synchronzied就是典型的可重入锁的代表
    读锁的时间在一秒内,所以两个线程读到的锁是一把锁,即读锁是共享锁
    而写锁的时间刚好是一秒,所以写锁是独占锁。

    在这里插入图片描述

    在这里插入图片描述

    自旋锁:相当于死循环,一直尝试获取锁

    详解synchronized锁的优化问题

    synchroized加锁的整个过程,都是依赖于Monitor(监视器锁)实现的,监视器锁在虚拟机中又是根据操作系统的Metux Lock(互斥量)来实现的,这就导致在加锁的过程中需要频繁的在操作系统的内核态和和JVM级别的用户态进行切换,并且涉及到线程上下文的切换,是比较消耗性能的。所以后来有一位大佬Doug Lea基于java实现了一个AQS的框架,提供了Lock锁,性能远远高于synchroized。这就导致Oracle公司很没有面子,因此他们在JDK1.6对synchroized做了优化,引入了偏向锁和轻量级锁。存在一个从无锁-》偏向锁–》轻量级锁–》重量级锁的升级过程,优化后性能就可以和Lock锁的方式持平了。
    对象头
    HotSpot虚拟机中,对象在内存中分为三块区域:对象头、实例数据和对齐填充。
    在这里插入图片描述

    对象头包括两部分:Mark Word 和 类型指针。类型指针是指向该对象所属类对象的指针,我们不关注。mark word用于存储对象的HashCode、GC分代年龄、锁状态等信息。在32位系统上mark word长度为32bit,64位系统上长度为64bit。他不是一个固定的数据结构,是和对象的状态紧密相关,有一个对应关系的,具体如下表所示:

    在这里插入图片描述

    当某一线程第一次获得锁的时候,虚拟机会把对象头中的锁标志位设置为“01”,把偏向模式设置为“1”,表示进入偏向锁模式。同时使用CAS操作将获取到这个锁的线程的ID记录在对象的Mark Word中。如果CAS操作成功,持有偏向锁的线程每次进入这个锁的相关的同步块的时候。虚拟机都可以不在进行任何的同步操作。

    当其他线程进入同步块时,发现已经有偏向的线程了,偏向模式马上结束。根据锁对象目前是否处于被锁定的状态决定是否撤销偏向,也就是将偏向模式设置为“0”,撤销后标志位恢复到“01”,也就是未锁定的状态或者轻量级锁定,标志位为“00”的状态,后续的同步操作就按照下面的轻量级锁那样去执行
    1、在线程进入同步块的时候,如果同步对象状态为无锁状态(锁标志为 01),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录的空间,用来存储锁对象目前的 Mark Word 的拷贝。拷贝成功后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,并将 Lock Record 里的 owner 指针指向锁对象的 Mark Word。如果更新成功,则执行 2,否则执行 3。

    在这里插入图片描述
    2、如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且锁对象的 Mark Word 中的锁标志位设置为 “00”,即表示此对象处于轻量级锁定状态,这时候虚拟机线程栈与堆中锁对象的对象头的状态如图所示。
    在这里插入图片描述
    3、如果这个更新操作失败了,虚拟机首先会检查锁对象的 Mark Word 是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重要量级锁,锁标志的状态值变为 “10”,Mark Word 中存储的就是指向重量级锁的指针,后面等待锁的线程也要进入阻塞状态。而当前线程便尝试使用自旋来获取锁。自旋失败后膨胀为重量级锁,被阻塞。

    Semaphore

    Semaphore的作用:

    在java中,使用了synchronized关键字和Lock锁实现了资源的并发访问控制,在同一时间只允许唯一了线程进入临界区访问资源(读锁除外),这样子控制的主要目的是为了解决多个线程并发同一资源造成的数据不一致的问题。也就是做限流的作用

    Semaphore实现原理:

    Semaphore是用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。

    如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。

    就好比一个厕所管理员,站在门口,只有厕所有空位,就开门允许与空侧数量等量的人进入厕所。多个人进入厕所后,相当于N个人来分配使用N个空位。为避免多个人来同时竞争同一个侧卫,在内部仍然使用锁来控制资源的同步访问。

    Semaphore的使用:

    Semaphore使用时需要先构建一个参数来指定共享资源的数量,Semaphore构造完成后即是获取Semaphore、共享资源使用完毕后释放Semaphore。

    使用Semaphore 来模拟有四辆车同时到达了停车场的门口,但是停车位只有两个,也就是只能停两辆车,这就可以使用信号量来实现。👇:

    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 14:00;
     */
    public class ThreadDemo6 {
        public static void main(String[] args) {
            Semaphore semaphore = new Semaphore(2);
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100), new ThreadPoolExecutor.DiscardPolicy());
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    try {
                        Thread.sleep(1000);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
                }
            });
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(2000);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
    
                }
            });
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    try {
                        Thread.sleep(500);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
                }
            });
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "到达了停车场");
    
                    try {
                        Thread.sleep(1000);
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "进入了停车场");
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
    
                    try {
                        Thread.sleep(1500);
    
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(Thread.currentThread().getName() + "出了了停车场");
    
                    semaphore.release();
    
                }
            });
    
            threadPoolExecutor.shutdown();
        }
    }
    
    

    在这里插入图片描述

    CountDownLatch\CyclicBarrier

    CountDownLatch
    一个可以用来协调多个线程之间的同步,或者说起到线程之间的通信作用的工具类。

    它能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成了任务,然后在CountDownLatch上等待的线程就可以恢复执行任务。

    CountDownLatch的用法

    某一线程在开始运行前等待n个线程执行完毕。
    将CountDownLatch的计数器初始化为n:new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1, countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

    实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。做法是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。

    CountDownLatch的不足
    CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。

    在这里插入图片描述
    模拟赛跑:当三个运动员都到达终点的时候宣布比赛结束

    import java.util.Random;
    import java.util.concurrent.*;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 14:27;
     */
    public class ThreadDemo7 {
        public static void main(String[] args) throws InterruptedException {
            CountDownLatch countDownLatch = new CountDownLatch(3);
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
    
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开跑");
    
                    int num = new Random().nextInt(4);
                    num += 1;
    
                    try {
                        Thread.sleep(1000*num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
    
                    countDownLatch.countDown();
                }
            });
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开跑");
    
                    int num = new Random().nextInt(4);
                    num += 1;
    
                    try {
                        Thread.sleep(1000*num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
    
                    countDownLatch.countDown();
                }
            });
    
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "开跑");
    
                    int num = new Random().nextInt(4);
                    num += 1;
    
                    try {
                        Thread.sleep(1000*num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "到达了终点");
    
                    countDownLatch.countDown();
                }
            });
            countDownLatch.await();
            System.out.println("所有的选手都到达了终点");
            threadPoolExecutor.shutdown();
        }
    }
    

    在这里插入图片描述
    CyclicBarrier

    CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBarrier的await()方法。

    CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

    import java.util.concurrent.*;
    
    /**
     * user:ypc;
     * date:2021-06-14;
     * time: 15:03;
     */
    public class ThreadDemo8 {
        public static void main(String[] args) {
            CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
                @Override
                public void run() {
                    System.out.println("到达了循环屏障");
                }
            });
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
                    TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
    
            for (int i = 0; i < 10; i++) {
    
                int finalI = i;
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
    
                        try {
                            Thread.sleep(finalI * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "进入了任务");
    
                        try {
    
                            cyclicBarrier.await();
    
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (BrokenBarrierException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "退出了任务");
    
                    }
                });
            }
    
    
            threadPoolExecutor.shutdown();
        }
    }
    
    

    在这里插入图片描述
    CyclicBarrier原理
    每当线程执行await,内部变量count减1,如果count!= 0,说明有线程还未到屏障处,则在锁条件变量trip上等待。
    当count == 0时,说明所有线程都已经到屏障处,执行条件变量的signalAll方法唤醒等待的线程。
    其中 nextGeneration方法可以实现屏障的循环使用:
    重新生成Generation对象
    恢复count值
    CyclicBarrier可以循环的使用。

    hashmap/ConcurrentHashMap

    hashmap在JDK1.7中头插死循环问题

    来看👇JDK1.7 hashMap transfer的源码

    void transfer(Entry[] newTable, boolean rehash) {
            int newCapacity = newTable.length;
            for (Entry<K,V> e : table) {
                while(null != e) {
                    Entry<K,V> next = e.next;
                    if (rehash) {
                        e.hash = null == e.key ? 0 : hash(e.key);
                    }
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                }
            }
        }
    

    来看多线程情况下的问题:
    在这里插入图片描述
    这样就会造成死循环。

    hashmap在JDK1.8中值覆盖问题

    在JDK1.8的时候使用的是尾插法
    来看👇:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    for (int binCount = 0; ; ++binCount) {
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }
    
    
    

    在多线程的情况下:
    在这里插入图片描述
    其中第六行代码是判断是否出现hash碰撞,假设两个线程1、2都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程1执行完第六行代码后由于时间片耗尽导致被挂起,而线程2得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程2插入的数据被线程1覆盖了,从而线程不安全。

    除此之前,还有就是代码的第38行处有个++size,我们这样想,还是线程1、2,这两个线程同时进行put操作时,假设当前HashMap的zise大小为10,当线程1执行到第38行代码时,从主内存中获得size的值为10后准备进行+1操作,但是由于时间片耗尽只好让出CPU,线程2快乐的拿到CPU还是从主内存中拿到size的值10进行+1操作,完成了put操作并将size=11写回主内存,然后线程1再次拿到CPU并继续执行(此时size的值仍为10),当执行完put操作后,还是将size=11写回内存,此时,线程1、2都执行了一次put操作,但是size的值只增加了1,所有说还是由于数据覆盖又导致了线程不安全。

    ConcurrentHashMap & HashTable

    来看这个🤣

    展开全文
  • 今天开始就来总结一下java线程的基础知识点,下面是本篇的主要内容(大部分知识点参考java核心技术卷1): 1.什么是线程以及多线程... 在现代操作在运行一个程序时,会为其创建一个进程。例如启动一个QQ程序,操作

    多线程系列教程:

    java多线程-概念&创建启动&中断&守护线程&优先级&线程状态( 多线程编程之一)
    java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()( 多线程编程之二)
    java&android线程池-Executor框架之ThreadPoolExcutor&ScheduledThreadPoolExecutor浅析(多线程编程之三)
    Java多线程:Callable、Future和FutureTask浅析(多线程编程之四)

    今天开始就来总结一下java多线程的基础知识点,下面是本篇的主要内容(大部分知识点参考java核心技术卷1):

    1.什么是线程以及多线程与进程的区别
    2.多线程的创建与启动
    3.中断线程和守护线程以及线程优先级
    4.线程的状态转化关系

    1.什么是线程以及多线程与进程的区别
              在现代操作在运行一个程序时,会为其创建一个进程。例如启动一个QQ程序,操作系统就会为其创建一个进程。而操作系统中调度的最小单位元是线程,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。因此我们可以这样理解:
    进程: 正在运行的程序,是系统进行资源分配和调用的独立单位。 每一个进程都有它自己的内存空间和系统资源。
    线程: 是进程中的单个顺序控制流,是一条执行路径 一个进程如果只有一条执行路径,则称为单线程程序。 一个进程如果有多条执行路径,则称为多线程程序。

    2.多线程的创建与启动
        创建多线程有两种方法,一种是继承Thread类重写run方法,另一种是实现Runnable接口重写run方法。下面我们分别给出代码示例,继承Thread类重写run方法:
    package com.zejian.test;
    /**
     * @author zejian
     * @time 2016年3月11日 下午9:57:44
     * @decrition 继承Thread实现线程
     */
    public class ThreadByEx extends Thread{
    	/**
    	 * 重写run方法
    	 */
    	@Override
    	public void run() {
    		System.out.println("I'm a thread that extends Thread!");
    	}
    }
    实现Runnable接口重写run方法:
    package com.zejian.test;
    /**
     * @author zejian
     * @time 2016年3月11日 下午10:00:36
     * @decrition 实现Runnable接口重写run方法
     */
    public class ThreadByRunnable implements Runnable{
    	/**
    	 * 实现run方法
    	 */
    	@Override
    	public void run() {
    		System.out.println("I'm a thread that implements Runnable !");
    	}
    }
    怎么启动线程?
    package com.zejian.test;
    public class MainTest {
    	
    	public static void main(String[] args) {
    		//继承Thread启动的方法
    		ThreadByEx t1=new ThreadByEx();
    		t1.start();//启动线程
    		
    		
    		//实现Runnable启动线程的方法
    		ThreadByRunnable r = new ThreadByRunnable();
    		Thread t2 =new Thread(r);
    		t2.start();//启动线程
    	}
    }
    运行结果:
    I'm a thread that extends Thread!
    I'm a thread that implements Runnable !
    代码相当简单,不过多解释。这里有点需要注意的是调用start()方法后并不是是立即的执行多线程的代码,而是使该线程变为可运行态,什么时候运行多线程代码是由操作系统决定的。

    3.中断线程和守护线程以及线程优先级
    什么是中断线程?
    我们先来看看中断线程是什么?(该解释来自java核心技术一书,我对其进行稍微简化), 当线程的run()方法执行方法体中的最后一条语句后,并经由执行return语句返回时,或者出现在方法中没有捕获的异常时线程将终止。在java早期版本中有一个stop方法,其他线程可以调用它终止线程,但是这个方法现在已经被弃用了,因为这个方法会造成一些线程不安全的问题。我们可以把中断理解为一个标识位的属性,它表示一个运行中的线程是否被其他线程进行了中断操作,而中断就好比其他线程对该线程打可个招呼,其他线程通过调用该线程的 interrupt方法对其进行中断操作,当一个线程调用interrupt方法时,线程的中断状态(标识位)将被置位(改变),这是每个线程都具有的boolean标志,每个线程都应该不时的检查这个标志,来判断线程是否被中断。而要判断线程是否被中断,我们可以使用如下代码
    Thread.currentThread().isInterrupted()
    while(!Thread.currentThread().isInterrupted()){
        do something
    }
    但是如果此时线程处于阻塞状态(sleep或者wait),就无法检查中断状态,此时会抛出 InterruptedException异常。 如果每次迭代之后都调用sleep方法(或者其他可中断的方法),isInterrupted检测就没必要也没用处了,如果在中断状态被置位时调用sleep方法,它不会休眠反而会清除这一休眠状态并抛出InterruptedException。所以如果在循环中调用sleep,不要去检测中断状态,只需捕获InterruptedException。代码范例如下:
    public void run(){
    		while(more work to do ){
    			try {
    				Thread.sleep(5000);
    			} catch (InterruptedException e) {
    				//thread was interrupted during sleep
    				e.printStackTrace();
    			}finally{
    				//clean up , if required
    			}
    		}
    同时还有点要注意的就是我们在捉中断异常时尽量按如下形式处理,不要留空白什么都不处理!
    不妥的处理方式:
    void myTask(){
        ...
       try{
           sleep(50)
          }catch(InterruptedException e){
       ...
       }
    }
    正确的处理方式:
    void myTask()throw InterruptedException{
        sleep(50)
    }
    或者
    void myTask(){
        ...
        try{
        sleep(50)
        }catch(InterruptedException e){
         Thread.currentThread().interrupt();
        }
    }
    最后关于中断线程,我们这里给出中断线程的一些主要方法:
    void interrupt():向线程发送中断请求,线程的中断状态将会被设置为true,如果当前线程被一个sleep调用阻塞,那么将会抛出interrupedException异常。
    static boolean interrupted():测试当前线程(当前正在执行命令的这个线程)是否被中断。注意这是个静态方法,调用这个方法会产生一个副作用那就是它会将当前线程的中断状态重置为false。
    boolean isInterrupted():判断线程是否被中断,这个方法的调用不会产生副作用即不改变线程的当前中断状态。
    static Thread currentThread() : 返回代表当前执行线程的Thread对象。
    什么是守护线程?
    首先我们可以通过t.setDaemon(true)的方法将线程转化为守护线程。而守护线程的唯一作用就是为其他线程提供服务。计时线程就是一个典型的例子,它定时地发送“计时器滴答”信号告诉其他线程去执行某项任务。当只剩下守护线程时,虚拟机就退出了,因为如果只剩下守护线程,程序就没有必要执行了。另外JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。最后还有一点需要特别注意的是在java虚拟机退出时Daemon线程中的finally代码块并不一定会执行哦,代码示例:
    package com.zejian.test;
    /**
     * @author zejian
     * @time 2016年3月12日 上午10:42:19
     * @decrition 守护线程代码示例
     */
    public class Demon {
    	public static void main(String[] args) {
    		Thread deamon = new Thread(new DaemonRunner(),"DaemonRunner");
    		//设置为守护线程
    		deamon.setDaemon(true);
    		deamon.start();//启动线程
    	}
    	
    	
    	static class DaemonRunner implements Runnable{
    		@Override
    		public void run() {
    			try {
    				Thread.sleep(500);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}finally{
    				System.out.println("这里的代码在java虚拟机退出时并不一定会执行哦!");
    			}
    		}
    	}
    }
    因此在构建Daemon线程时,不能依靠finally代码块中的内容来确保执行关闭或清理资源的逻辑。
    什么是线程优先级
              在现代操作系统中基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下一次分配。线程分配到的时间片多少也决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。在java线程中,通过一个整型的成员变量 Priority来控制线程优先级 ,每一个线程有一个优先级,默认情况下,一个线程继承它父类的优先级。可以用setPriority方法提高或降低任何一个线程优先级。可以将优先级设置在MIN_PRIORITY(在Thread类定义为1)与MAX_PRIORITY(在Thread类定义为10)之间的任何值。线程的默认优先级为NORM_PRIORITY(在Thread类定义为5)。 尽量不要依赖优先级,如果确实要用,应该避免初学者常犯的一个错误。如果有几个高优先级的线程没有进入非活动状态,低优先级线程可能永远也不能执行。每当调度器决定运行一个新线程时,首先会在具有高优先级的线程中进行选择,尽管这样会使低优先级的线程可能永远不会被执行到。因此我们在设置优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高的优先级,而偏重计算(需要较多CPU时间或者运算)的线程则设置较低的优先级,这样才能确保处理器不会被长久独占。当然还有要注意就是在不同的JVM以及操作系统上线程的规划存在差异,有些操作系统甚至会忽略对线程优先级的设定,如mac os系统或者Ubuntu系统........

    4.线程的状态转化关系
    (1). 新建状态(New):新创建了一个线程对象。
    (2). 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
    (3). 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
    (4). 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

    - 等待阻塞(WAITING):运行的线程执行wait()方法,JVM会把该线程放入等待池中。

    - 同步阻塞(Blocked):运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

    - 超时阻塞(TIME_WAITING):运行的线程执行sleep(long)或join(long)方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。

    (5). 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。



    图中的方法解析如下:
    Thread.sleep():在指定时间内让当前正在执行的线程暂停执行,但不会释放"锁标志"。不推荐使用。
    Thread.sleep(long):使当前线程进入阻塞状态,在指定时间内不会执行。 
    Object.wait()和 Object.wait(long) :在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的"锁标志",从而使别的线程有机会抢占该锁。  当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。 唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常, waite()和notify()必须在synchronized函数或synchronized中进行调用。如果在non-synchronized函数或non-synchronized中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。 
    Object.notifyAll():则从对象等待池中唤醒所有等待等待线程
    Object.notify():则从对象等待池中唤醒其中一个线程。
    Thread.yield()方法 暂停当前正在执行的线程对象,yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,yield()只能使同优先级或更高优先级的线程有执行的机会。 
    Thread.Join():把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
    好了。本篇线程基础知识介绍到此结束。

    展开全文
  • 万字图解Java线程

    万次阅读 多人点赞 2020-09-06 14:45:07
    前言 java线程我个人觉得是javaSe中最难的一部分,我以前也是感觉学会了,但是真正有多线程的需求却不知道怎么下手,实际上还是对多线程这块知识了解不深刻...当一个程序被运行,就开启了一个进程, 比如启动了qq,w.
  • Java线程(二、创建线程

    千次阅读 2018-06-05 19:08:10
    大多数情况下,通过实例化一个Thread对象来创建一个线程,Java定义了两种方式:实现Runnable接口继承Thread类实现Runnable接口创建线程的最简单的方法就是创建一个Runnable口的类。Runnable抽象了一个执行代码单元。...
  • 在学习JAVA线程时候的遇到一个很奇怪的现象。让我们先来看代码 ```java public class TestThread { public static void main(String[] args) { IRun ir = new IRun(); Thread it = new Thread(ir); it.start();...
  • ????使用 ThreadGroup,首先创建一个线程组,把创建的子线程都放到这个线程组里,然后循环判断这个线程组的活跃线程数量 是否等于0,不等于0继续,否则代表子线程全部执行完了,进行下一步。
  • Java线程学习(吐血超详细总结)

    万次阅读 多人点赞 2015-03-14 13:13:17
    本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。
  • Java线程创建任务和线程

    千次阅读 2016-12-14 17:19:47
    Runnable创建线程任务就是对象。为了创建任务,必须首先为任务定义一个类。任务类必须实现Runnable接口。Runnable接口非常简单,它只包含一个run方法。需要实现这个方法来告诉系统线程将如何运行。开发一个任务类的...
  • 主要介绍了Java线程线程的两种创建方式及比较代码示例,简单介绍了线程的概念,并行与并发等,然后通过实例代码向大家展示了线程创建,具有一定参考价值,需要的朋友可以了解下。
  • JAVA线程与多线程

    千次阅读 多人点赞 2016-08-25 19:10:10
    一个进程中至少有一个线程。多线程:解决多任务同时执行的需求,合理使用CPU资源。多线程的运行是根据CPU切换完成,如何切换由CPU决定,因此多线程运行具有不确定性。● 线程java中的线程使用java.lang.Thread类或者...
  • 一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备
  • 传统的线程技术中有两种创建线程的方式:是继承Thread类,并重写run()方法;二是实现Runnable接口,覆盖接口中的run()方法,并把Runnable接口的实现扔给Thread。这两种方式大部分人可能都知道,但是为什么这样玩就...
  • Java线程创建

    千次阅读 2018-04-29 13:38:11
    本文章主要介绍进程与线程的概念以及创建线程的三种方式:1.继承Thread类 2.实现Runnable接口 3.实现Callable接口。
  • 如何创建和启动一个线程

    千次阅读 2015-09-02 13:05:39
    原文转自:http://www.tqcto.com/article/recommend/137.html、定义线程 1、继承java.lang.Thread类。 此类中有run()方法,应该注意其用法: public void run()如果该线程是使用独立的 Runnable 运行对象构造...
  • Java继承Thread类创建线程

    千次阅读 2018-04-15 23:10:26
    Java继承Thread类创建线程线程示例 示例,Example01.java public class Example01{ public static void main(String[] args){ // 创建MyThread实例对象 ... // 该循环是一个死循环,打...
  • 如果这台电脑上有多个处理器或者是一个多核处理器,那么这时是实实在在的“同时运行”;但是,如果计算机只有一个单核处理器,那么这时的“同时运行”只是表象而已。所有的现代操作系统全部支持任务的并发执行。你...
  • Java实现Runnable接口创建线程

    千次阅读 2018-04-15 23:14:18
    Java实现Runnable接口创建多线程 ...比如,学生类Student继承了Person类,就无法通过继承Thread类,创建线程 针对这种情况,Thread类提供了另外一个构造方法Runnable target 其中,Runnable是一个...
  • 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务。下面先讲述一下Java中的应用程序和进程相关的概念知识,然后再阐述如何创建线程以及如何创建进程。...
  • Java线程总结(

    千次阅读 2016-05-22 21:45:58
    如果这是还有另一个线索同时执行,也就是两个代码并行执行,这就是多线程。 创建线程的两种方式? 创建线程的第一种方式:继承Thread类 步骤:(1)定义类继承Thread(2)覆写Thread类中的run方法(3)调用线程的...
  • Java创建线程(Runnable接口和Thread类)

    千次阅读 2017-04-17 21:21:23
    Java创建线程(Runnable接口和Thread类) 大多数情况,通过实例化一个Thread对象来创建一个线程。Java定义了两种方式: ...创建线程的最简单的方法就是创建一个实现Runnable 接口的类。Runnable抽象了一个执行代码单元
  • Java线程-守护线程与用户线程

    千次阅读 2019-08-27 16:19:35
    Java线程-守护线程与用户线程
  • Java如何获取当前线程

    万次阅读 2018-07-05 08:26:48
    前言 Java 中经常会遇到要获取当前线程的情况,这时一般我们就...以下是一个简单的例子,获取当前线程并打印线程名称,输出是”main”,即主线程。 public class CurrentThreadTest { public static void mai...
  • 一个线程对象能否多次调用start方法,搞清楚这个问题,首先需要了解线程的生命周期 一、线程生命周期 更多线程状态细节描述可查看Thread内部枚举类:State 从上图线程状态转换图可以看出: 新建(NEW)状态是...
  • package cn.itcast.other; import java.text.SimpleDateFormat; import java.util.Date; public class Date {  public static void main(String[] args) { ... //创建线程  final Thread thread...
  • Java线程详解

    千次阅读 多人点赞 2019-07-29 17:18:03
    今天我们聊一聊多线程,谈到多线程,很多人就开始难受,这是一个一听就头疼的话题,但是,我希望你在看完这篇文章后能对多线程一个深入的了解。 案例 那么,首先我就举一个电影院卖票的例子来模拟多线程。 复仇者...
  • java线程安全

    千次阅读 2018-12-12 16:03:33
    线程不安全产生的主要原因:因为多个线程共享一个内存,所以当多个线程共享一个全局变量的时候,可能会受到其他干扰。 如线程更新会先在本地内存更新,然后再同步到共享内存中,当多个线程同时读写的时候,数据会...
  • 线程java5的线程并发库

    千次阅读 2011-12-27 21:48:39
    package com.partner4java.itcast.util.thread; /** * 两种传统的线程创建方式 * * @author partner4java * */ public class TraditionalThread { ...// 涉及一个以往知识点:能否在run方法

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 209,784
精华内容 83,913
关键字:

创建一个新线程语句java

java 订阅