一、死锁

死锁:多个线程之间,由于针对多个对象进行上锁,导致的多个线程之间,需要相互等待对方释放对象的锁的过程
无标题.png

1.1 死锁产生的条件

互斥使用:针对同一个资源,提供了互斥锁(synchronized)
不可剥夺:当线程占用某一个对象锁以后,不可能被其他线程进行剥夺
请求和保持:当进程因请求资源而阻塞时,对已获得的资源保持不放。
相互等待:a线程需要等待b线程的释放,b线程需要等待a线程的释放
eg:

  1. package com.woniuxy.java25.study.deadLock;
  2. /**
  3. * 定义一个死锁的任务
  4. *
  5. * @author Administrator
  6. *
  7. */
  8. public class DeadLockTask implements Runnable {
  9. private static Object o1 = new Object();
  10. private static Object o2 = new Object();
  11. // 1-先锁o1 再锁o2 2-先锁o2 再锁o1
  12. private int flag;
  13. public DeadLockTask(int flag) {
  14. super();
  15. this.flag = flag;
  16. }
  17. @Override
  18. public void run() {
  19. // TODO Auto-generated method stub
  20. if (flag == 1) {
  21. synchronized (o1) {
  22. System.out.println(Thread.currentThread().getName() + "锁住了 o1");
  23. // 休眠1S
  24. try {
  25. Thread.sleep(1000);
  26. } catch (InterruptedException e) {
  27. // TODO Auto-generated catch block
  28. e.printStackTrace();
  29. }
  30. // 试图去锁o2
  31. synchronized (o2) {
  32. System.out.println(Thread.currentThread().getName() + "锁住了 o2");
  33. }
  34. }
  35. } else {
  36. synchronized (o2) {
  37. System.out.println(Thread.currentThread().getName() + "锁住了 o2");
  38. // 休眠1S
  39. try {
  40. Thread.sleep(1000);
  41. } catch (InterruptedException e) {
  42. // TODO Auto-generated catch block
  43. e.printStackTrace();
  44. }
  45. // 试图去锁o1
  46. synchronized (o1) {
  47. System.out.println(Thread.currentThread().getName() + "锁住了 o1");
  48. }
  49. }
  50. }
  51. }
  52. }
    package com.woniuxy.java25.study.deadLock;
    public class MainEnter {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Thread t1 = new Thread(new DeadLockTask(1));
            Thread t2 = new Thread(new DeadLockTask(2));
            //开启t1 t2
            t1.start();
            t2.start();
        }
    }

1.2 死锁的原因与解决

死锁的原因

死锁的发生多半是因为程序设计不佳所造成的,主要原因是来自于对象锁无法获得

死锁的解决

修正你的代码逻辑,让所有的线程都按照相同的顺序进行针对对象上锁,即可解决

    package com.woniuxy.java25.study.deadLock;
    /**
     * 定义一个死锁的任务
     * 
     * @author Administrator
     *
     */
    public class DeadLockTask implements Runnable {
        private static Object o1 = new Object();
        private static Object o2 = new Object();
        public DeadLockTask() {
            super();
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            synchronized (o1) {
                System.out.println(Thread.currentThread().getName() + "锁住了 o1");
                // 休眠1S
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                // 试图去锁o2
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + "锁住了 o2");
                }
            }
        }
    }
    package com.woniuxy.java25.study.deadLock;
    public class MainEnter {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Thread t1 = new Thread(new DeadLockTask());
            Thread t2 = new Thread(new DeadLockTask());
            //开启t1 t2
            t1.start();
            t2.start();
        }
    }

二、线程间的通讯

线程之间的数据传输,可以使用某个对象进行数据传输,但是线程之间的状态变化,这里就可能会使用到wait() notify() notifyAll()
wait() notify() notifyAll(),均来自于Object类
wait() 可以让线程处于等待式阻塞状态
notify() 可以让线程去唤醒处于 等待式阻塞状态的线程
notifyAll 可以让线程去唤醒,所有处于等待式阻塞状态的线程

2.1 不使用wait() notify() notifyAll()的情况

    package com.woniuxy.java25.study.communicate;
    import java.io.Serializable;
    /**
     * 消息类
     * @author Administrator
     *
     */
    public class InformationBean implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = 1401842938475530654L;
        /**
         * 消息
         */
        private String message;
        public InformationBean() {
            super();
            // TODO Auto-generated constructor stub
        }
        public InformationBean(String message) {
            super();
            this.message = message;
        }
        public synchronized String getMessage() {
            return message;
        }
        public synchronized void setMessage(String message) {
            this.message = message;
        }
        @Override
        public String toString() {
            return "InformationBean [message=" + message + "]";
        }
    }
    package com.woniuxy.java25.study.communicate;
    import java.util.UUID;
    /**
     * 产生消息
     * @author Administrator
     *
     */
    public class ProducerTask implements Runnable {
        private InformationBean msg;
        public ProducerTask(InformationBean msg) {
            super();
            this.msg = msg;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while(true) {
                //间隔2S产生一次消息!!!!
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                String str = UUID.randomUUID().toString();
                //一个36位的随机字符串
                msg.setMessage(str);
                System.out.println(Thread.currentThread().getName() + "产生的消息是:" + str);
            }
        }
    }
    package com.woniuxy.java25.study.communicate;
    /**
     * 消息的消费者
     * @author Administrator
     *
     */
    public class ConsumerTask implements Runnable {
        private InformationBean msg;
        public ConsumerTask(InformationBean msg) {
            super();
            this.msg = msg;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            //获取消息
            while(true) {
                System.out.println(Thread.currentThread().getName() + "接收的消息是:" + msg.getMessage());
            }
        }
    }
    package com.woniuxy.java25.study.communicate;
    /**
     * 主启动类
     * @author Administrator
     *
     */
    public class MainEnter {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            //实例化
            InformationBean msg = new InformationBean();
            Thread t1 = new Thread(new ProducerTask(msg));
            Thread t2 = new Thread(new ConsumerTask(msg));
            //启动
            t1.start();
            t2.start();
        }
    }

执行结果是:

    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24
    Thread-1接收的消息是:ae7e5f00-afc8-441a-a6bf-aad8d9fe9c24

消费者,这边一直在不断 输出内容!!!
而我们的需求是:生产者,产生一条,消费者,才消费一条

2.2 使用wait() notify() notifyAll()的情况

    package com.woniuxy.java25.study.communicate;
    import java.io.Serializable;
    /**
     * 消息类
     * @author Administrator
     *
     */
    public class InformationBean implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = 1401842938475530654L;
        /**
         * 消息
         */
        private String message;
        public InformationBean() {
            super();
            // TODO Auto-generated constructor stub
        }
        public InformationBean(String message) {
            super();
            this.message = message;
        }
        public synchronized String getMessage() {
            //让线程等待
            try {
                wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return message;
        }
        public synchronized void setMessage(String message) {
            this.message = message;
            //唤醒
    //        notify();
            notifyAll();
        }
        @Override
        public String toString() {
            return "InformationBean [message=" + message + "]";
        }
    }

其他代码不做更改
执行结果:

    Thread-1接收的消息是:d7c95585-45e5-4b1c-8f84-a1cc2255f2f5
    Thread-0产生的消息是:d7c95585-45e5-4b1c-8f84-a1cc2255f2f5
    Thread-0产生的消息是:b75412e2-f750-4f00-83ea-34e8c373e6b8
    Thread-1接收的消息是:b75412e2-f750-4f00-83ea-34e8c373e6b8
    Thread-0产生的消息是:1c60436f-cf80-4164-8164-858cff9fbd0f
    Thread-1接收的消息是:1c60436f-cf80-4164-8164-858cff9fbd0f

达到了产生一条消息,读取一条消息的目的

2.3 sleep()和 wait() 的区别(面试题)

1、sleep() 是Thread类的静态方法,代表是让当前线程休眠
wait() 是Object类提供的,代表是让当前线程处于等待
2、sleep()休眠一定时间,会自动唤醒,并且让线程出于Rannable就绪状态,
而wait()必须需要其他线程来唤醒(notify() notifyAll())
3、sleep()在休眠期间,不会释放线程所持有的内存资源,但是会释放CPU的执行权,wait() 在等待期间,会释放线程所持有的内存资源,同样也会释放CPU的执行权

三、Queue

栈:先进后出
Queue:先进先出

    package com.woniuxy.java25.study.queue;
    import java.util.UUID;
    import java.util.concurrent.ArrayBlockingQueue;
    public class QueueStudy {
        public static void main(String[] args) {
            //ArrayBlockingQueue 是线程安全的
            //rabbitmq == queue队列
            //创建一个队列,并指定队列的大小 500
            ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(500);
            for(int i = 0; i < 500; i ++) {
                queue.add(UUID.randomUUID().toString());
            }
            //遍历(1)
            queue.forEach(e -> System.out.println(e));
            System.out.println(queue.size());
            //遍历(2)
            //迭代器
            //检索(不删除)
            //从队列的头部开始,获取第一个元素
            System.out.println(queue.peek());
            System.out.println(queue.size());
            //检索(删除)
            //从队列的头部开始,获取第一个元素
            System.out.println(queue.poll());
            System.out.println(queue.size());
        }
    }

四、线程池

目前使用Thread 和 Runable这种方式,这种方式最大的毛病,就是会产生多个线程。计算机同时支持线程的数据是有限,它受到JVM内存限制。
这个时候,往往就需要线程池的支持:因为线程池可以重复利用线程。

4.1 线程池的执行原理

任务交给线程池后,它可以自动分配线程给任务,并且任务执行完毕之后,它会自动将线程归还到线程池中。 ——未来所学的:对象池,连接池,都是同样的原理。

4.2 线程池的提供类

线程池所在的包:java.util.concurrent
线程池主要使用的类:Executors ThreadPoolExecutor
这2个类,都可以产生线程池,区别在于:Executors 线程量不高的,需要快捷使用线程的;ThreadPoolExecutor 线程量很高

Executors

    package com.woniuxy.java25.study.executors;
    /**
     * 定义任务(1)
     * 如果线程,不需要返回一个结果,那么就需要使用Runnable接口
     * @author Administrator
     *
     */
    public class SayTask implements Runnable {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i = 0; i < 10; i ++) {
                System.out.println("我喜欢你!!!!");
            }
        }
    }
    package com.woniuxy.java25.study.executors;
    import java.util.concurrent.Callable;
    /**
     * 定义任务(2)
     * 如果线程,需要返回一个结果,那么就需要使用Callable接口
     * @author Administrator
     *
     */
    public class TalkTask implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            // TODO Auto-generated method stub
            for(int i = 0; i < 10; i ++) {
                System.out.println("我喜欢你!!!!");
            }
            //返回执行的次数
            return 10;
        }
    }
    package com.woniuxy.java25.study.executors;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    public class ExecutorsStudy {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            //产生一个内置了10根线程的线程池
            ExecutorService es = Executors.newFixedThreadPool(10);
    //        //产生一个不限量的线程的线程池
    //        ExecutorService es = Executors.newCachedThreadPool();
    //        //产生一个内置了1根线程的线程池
    //        ExecutorService es = Executors.newSingleThreadExecutor()
            Future future = es.submit(new SayTask());
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //关闭线程池
            es.shutdown();
        }
    }

4.3 Runnable 和Callable的区别

1、都是用来定义任务的,但是Runnable 不返回任务的结果;Callable ,返回任务的结果
2、Callable定义的任务,在线程执行时是有序

package exercise04;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorsStudy {

    public static void main(String[] args) {
        System.out.println("主线程开始!!!!!");
        study02();
        System.out.println("主线程结束!!!!!");
    }

    private static void study02() {
        // TODO Auto-generated method stub
        // 产生一个内置了10根线程的线程池
        ExecutorService es = Executors.newFixedThreadPool(10);
        Future<Integer> re01 = es.submit(() -> {
            System.out.println(10);
            return 10;
        });
        Future<Integer> re02 = es.submit(() -> {
            System.out.println(20);
            return 20;
        });
        Future<Integer> re03 = es.submit(() -> {
            System.out.println(30);
            return 30;
        });
        Future<Integer> re04 = es.submit(() -> {
            System.out.println(40);
            return 40;
        });
        try {
              System.out.println(re01.get());
            System.out.println(re02.get());
            System.out.println(re03.get());
            System.out.println(re04.get());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 关闭线程池
        es.shutdown();
    }
}

4.4 面试题(经典)

主线程-包含10根子线程,问你?一般来讲线程的执行顺序是无序的,而且是获取不到返回结果,问你:你如何保证 主线程 在所有的子线程执行完毕之后,方可继续执行,而且要输出子线程的结果
答案:使用Callable定义任务,使用Future 接收任务的结果。

五、守护线程

守护线程,又叫后台线程,它是运行线程内部的线程,它的特点是:外部线程,运行完毕了,它运行不完毕都直接停止。
隐藏在线程后面的线程,就是后台线程
后台线程 应用场景
GC时: GC垃圾回收线程
迅雷: 下载内容(可以使用)
上传:……

    package com.woniuxy.java26.study.daemon;
    /**
     * 守护线程
     * @author Administrator
     *
     */
    public class DaemonStudy {
        public static void main(String[] args) {
            System.out.println("主线程开启!!!!");
            /**
             * 外部线程
             */
            Thread t1 = new Thread(()->{
                for(int i = 0; i < 10; i ++) {
                    System.out.println("明天放假!!!!");
                }
                Thread it = new Thread(() ->{
                    while(true) {
                        System.out.println("后天也放假,而且还没有作业!!!!");
                    }
                }) ;
                //设置后台线程
                it.setDaemon(true);
                //开启内部线程
                it.start();
            });
            t1.start();
            System.out.println("主线程结束!!!!");
        }
    }

六、定时任务(重要)

有需要循环一定时间之后,需要做什么事情,可以使用定时任务。
会有一些定时框架,底层原理就是如下内容
无标题.png

    package com.woniuxy.java26.study.daemon;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    /**
     * 定时任务
     * @author Administrator
     *
     */
    public class TimeStudy {
        public static void main(String[] args) {
            System.out.println("主线程开启!!!");
            //时间类(定义定时任务,定义预安排任务)
            Timer timer = new Timer();
            //制定计划(间隔1秒钟,需要做任务) 
            //0 代表延迟时间   单位:毫秒
            //1000 间隔1S时间
    //        timer.schedule(new TimerTask() {
    //            
    //            @Override
    //            public void run() {
    //                // TODO Auto-generated method stub
    //                System.out.println("当前的时间:" + new Date());
    //            }
    //            
    //        }, 0, 1000);
            Date date = new Date();
            //推算1分钟以后的时间
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.MINUTE, 1);
            date = calendar.getTime();
            //从固定的某个时刻开始,间隔1分钟去循环执行任务
    //        timer.schedule(new TimerTask() {
    //            
    //            @Override
    //            public void run() {
    //                // TODO Auto-generated method stub
    //                System.out.println("半夜,起床上厕所,给女朋友发个短信!!!!");
    //            }
    //        }, date, 60000);
            //到达固定时间,只执行1次任务
            timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("半夜,起床上厕所,给女朋友发个短信!!!!");
            }
        }, date);
            //停止定时任务
            timer.cancel();
            System.out.println("主线程结束!!!");
        }
    }

七、JVM内存与线程(面试题)

class文件,无法直接直接执行,需要通过类装载器加载到JVM之后,才可以进行执行。
装载完毕之后,JVM会自动划分5片空间用于存储数据:
无标题.png

  • 线程共享区:方法区、堆
  • 线程私有区:虚拟机栈、本地方法栈、程序计数器