• 多线程


    课程笔记

    ###多线程的引入(了解)

    • 1.什么是线程
      • 线程是程序执行的一条路径, 一个进程中可以包含多条线程
      • 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
    • 2.多线程的应用场景
      • 红蜘蛛同时共享屏幕给多个电脑
      • 迅雷开启多条线程一起下载
      • QQ同时和多个人一起视频
      • 服务器同时处理多个客户端请求

    多线程并行和并发的区别(了解)

    • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
    • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
    • 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
    • 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。

    Java程序运行原理和JVM的启动是多线程的吗(了解)

    • A:Java程序运行原理

      • Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
    • B:JVM的启动是多线程的吗

      • JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

    多线程程序实现的方式1(掌握)

    • 1.继承Thread
      • 定义类继承Thread
      • 重写run方法
      • 把新线程要做的事写在run方法中
      • 创建线程对象
      • 开启新线程, 内部会自动执行run方法
    public class Demo2_Thread {
        public static void main(String[] args) {
            MyThread mt = new MyThread();                            //4,创建自定义类的对象
            mt.start();                                                //5,开启线程
            
            for(int i = 0; i < 3000; i++) {
                System.out.println("bb");
            }
        }
    
    }
    class MyThread extends Thread {                                    //1,定义类继承Thread
        public void run() {                                            //2,重写run方法
            for(int i = 0; i < 3000; i++) {                            //3,将要执行的代码,写在run方法中
                System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
            }
        }
    }

    多线程程序实现的方式2(掌握)

    • 2.实现Runnable
      • 定义类实现Runnable接口
      • 实现run方法
      • 把新线程要做的事写在run方法中
      • 创建自定义的Runnable的子类对象
      • 创建Thread对象, 传入Runnable
      • 调用start()开启新线程, 内部会自动调用Runnable的run()方法
    public class Demo3_Runnable {
            public static void main(String[] args) {
                MyRunnable mr = new MyRunnable();                        //4,创建自定义类对象
                //Runnable target = new MyRunnable();
                Thread t = new Thread(mr);                                //5,将其当作参数传递给Thread的构造函数
                t.start();                                                //6,开启线程
                
                for(int i = 0; i < 3000; i++) {
                    System.out.println("bb");
                }
            }
        }
        
        class MyRunnable implements Runnable {                            //1,自定义类实现Runnable接口
            @Override
            public void run() {                                            //2,重写run方法
                for(int i = 0; i < 3000; i++) {                            //3,将要执行的代码,写在run方法中
                    System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
                }
            }
            
        }

    实现Runnable的原理(了解)

    • 查看源码
      • 1,看Thread类的构造函数,传递了Runnable接口的引用
      • 2,通过init()方法找到传递的target给成员变量的target赋值
      • 3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法

    两种方式的区别(掌握)

    • 查看源码的区别:

      • a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
      • b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
    • 继承Thread

      • 好处是:可以直接使用Thread类中的方法,代码简单
      • 弊端是:如果已经有了父类,就不能用这种方法
    • 实现Runnable接口

      • 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
      • 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

    匿名内部类实现线程的两种方式(掌握)

    • 继承Thread类
            new Thread() {                                                    //1,new 类(){}继承这个类
                public void run() {                                            //2,重写run方法
                    for(int i = 0; i < 3000; i++) {                            //3,将要执行的代码,写在run方法中
                        System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
                    }
                }
            }.start();
    • 实现Runnable接口
            new Thread(new Runnable(){                                        //1,new 接口(){}实现这个接口
                public void run() {                                            //2,重写run方法
                    for(int i = 0; i < 3000; i++) {                            //3,将要执行的代码,写在run方法中
                        System.out.println("bb");
                    }
                }
            }).start(); 

    获取名字和设置名字(掌握)

    • 1.获取名字
      • 通过getName()方法获取线程对象的名字
    • 2.设置名字
      • 通过构造函数可以传入String类型的名字
                new Thread("xxx") {
                    public void run() {
                        for(int i = 0; i < 1000; i++) {
                            System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
                        }
                    }
                }.start();
                
                new Thread("yyy") {
                    public void run() {
                        for(int i = 0; i < 1000; i++) {
                            System.out.println(this.getName() + "....bb");
                        }
                    }
                }.start(); 
    * 通过setName(String)方法可以设置线程对象的名字
    
                Thread t1 = new Thread() {
                    public void run() {
                        for(int i = 0; i < 1000; i++) {
                            System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
                        }
                    }
                };
                
                Thread t2 = new Thread() {
                    public void run() {
                        for(int i = 0; i < 1000; i++) {
                            System.out.println(this.getName() + "....bb");
                        }
                    }
                };
                t1.setName("芙蓉姐姐");
                t2.setName("凤姐");
                
                t1.start();
                t2.start();

    获取当前线程的对象(掌握)

    • Thread.currentThread(), 主线程也可以获取
                new Thread(new Runnable() {
                    public void run() {
                        for(int i = 0; i < 1000; i++) {
                            System.out.println(Thread.currentThread().getName() + "...aaaaaaaaaaaaaaaaaaaaa");
                        }
                    }
                }).start();
                
                new Thread(new Runnable() {
                    public void run() {
                        for(int i = 0; i < 1000; i++) {
                            System.out.println(Thread.currentThread().getName() + "...bb");
                        }
                    }
                }).start();
                Thread.currentThread().setName("我是主线程");                    //获取主函数线程的引用,并改名字
                System.out.println(Thread.currentThread().getName());        //获取主函数线程的引用,并获取名字

    休眠线程(掌握)

    • Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000
        new Thread() {
            public void run() {
                for(int i = 0; i < 10; i++) {
                    System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    
        new Thread() {
            public void run() {
                for(int i = 0; i < 10; i++) {
                    System.out.println(getName() + "...bb");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

    守护线程(掌握)

    • setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
                Thread t1 = new Thread() {
                    public void run() {
                        for(int i = 0; i < 50; i++) {
                            System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                };
                
                Thread t2 = new Thread() {
                    public void run() {
                        for(int i = 0; i < 5; i++) {
                            System.out.println(getName() + "...bb");
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                };
                
                t1.setDaemon(true);                        //将t1设置为守护线程
                
                t1.start();
                t2.start();

    加入线程(掌握)

    • join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
    • join(int), 可以等待指定的毫秒之后继续
    final Thread t1 = new Thread() {
        public void run() {
            for(int i = 0; i < 50; i++) {
                System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    
    Thread t2 = new Thread() {
        public void run() {
            for(int i = 0; i < 50; i++) {
                if(i == 2) {
                    try {
                        //t1.join();                        //插队,加入
                        t1.join(30);                        //加入,有固定的时间,过了固定时间,继续交替执行
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(getName() + "...bb");
            }
        }
    };
    
    t1.start();
    t2.start();

    礼让线程(了解)

    • yield让出cpu

    设置线程的优先级(了解)

    • setPriority()设置线程的优先级

    同步代码块(掌握)

    • 1.什么情况下需要同步
      • 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
      • 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
    • 2.同步代码块
      • 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
      • 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
    class Printer {
        Demo d = new Demo();
        public static void print1() {
            synchronized(d){                //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
                System.out.print("黑");
                System.out.print("马");
                System.out.print("程");
                System.out.print("序");
                System.out.print("员");
                System.out.print("
    ");
            }
        }
    public static void print2() {    
        synchronized(d){    
            System.out.print("传");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("
    ");
        }
    }
    }

    同步方法(掌握)

    • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
    class Printer {
        public static void print1() {
            synchronized(Printer.class){                //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
                System.out.print("黑");
                System.out.print("马");
                System.out.print("程");
                System.out.print("序");
                System.out.print("员");
                System.out.print("
    ");
            }
        }
        /*
         * 非静态同步函数的锁是:this
         * 静态的同步函数的锁是:字节码对象
         */
        public static synchronized void print2() {    
            System.out.print("传");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("
    ");
        }
    }

    线程安全问题(掌握)

    • 多线程并发操作同一数据时, 就有可能出现线程安全问题
    • 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
    public class Demo2_Synchronized {
    
        /**
         * @param args
         * 需求:铁路售票,一共100张,通过四个窗口卖完.
         */
        public static void main(String[] args) {
            TicketsSeller t1 = new TicketsSeller();
            TicketsSeller t2 = new TicketsSeller();
            TicketsSeller t3 = new TicketsSeller();
            TicketsSeller t4 = new TicketsSeller();
            
            t1.setName("窗口1");
            t2.setName("窗口2");
            t3.setName("窗口3");
            t4.setName("窗口4");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    
    }
    
    class TicketsSeller extends Thread {
        private static int tickets = 100;
        static Object obj = new Object();
        public TicketsSeller() {
            super();
            
        }
        public TicketsSeller(String name) {
            super(name);
        }
        public void run() {
            while(true) {
                synchronized(obj) {
                    if(tickets <= 0) 
                        break;
                    try {
                        Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡
                    } catch (InterruptedException e) {
                        
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "...这是第" + tickets-- + "号票");
                }
            }
        }
    }

    火车站卖票的例子用实现Runnable接口(掌握)

    死锁(了解)

    • 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
      • 尽量不要嵌套使用
    private static String s1 = "筷子左";
    private static String s2 = "筷子右";
    public static void main(String[] args) {
        new Thread() {
            public void run() {
                while(true) {
                    synchronized(s1) {
                        System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
                        synchronized(s2) {
                            System.out.println(getName() + "...拿到" + s2 + "开吃");
                        }
                    }
                }
            }
        }.start();
        
        new Thread() {
            public void run() {
                while(true) {
                    synchronized(s2) {
                        System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
                        synchronized(s1) {
                            System.out.println(getName() + "...拿到" + s1 + "开吃");
                        }
                    }
                }
            }
        }.start();
    }

    以前的线程安全的类回顾(掌握)

    • A:回顾以前说过的线程安全问题
      • 看源码:Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
      • Vector是线程安全的,ArrayList是线程不安全的
      • StringBuffer是线程安全的,StringBuilder是线程不安全的
      • Hashtable是线程安全的,HashMap是线程不安全的

    总结

    • 多线程:多任务。同时执行多项工作。

    • 实现多线程的两种方式(掌握):

      • 继承Thread类,重写run()方法,把要执行的代码放到run里面,然后创建子类对象,调用start()方法启动线程;

      • 实现Runnable接口,重写润run()方法,把要执行的代码放到run里面,然后创建实现类对象,并且以参数的形式传递给Thread类的构造方法,通过Thread类的对象来启动线程;

      • getName();

      • setName(String);

      • Thread.currentThread();

      • 建议用匿名内部类的方式来实现多线程代码;

    • sleep() -->休眠

    • setDaemon() -->守护

    • join() -->加入,插队

    • yield() -->礼让线程

    • setPriority() -->从1-10,默认是5

    • 同步代码块:synchronized

      • 把操作共享数据的代码包起来
    • 同步方法:

      • 普通方法:锁是this对象
      • 静态方法:锁是该类的字节码文件对象
    • 掌握火车站买票的例子:用Runnable接口的实现方式

    • 死锁:两个线程相互持有对方的锁就会造成死锁现象;(避免方法:不要嵌套使用同步代码块)

    单例设计模式(掌握)

    • 单例设计模式:保证类在内存中只有一个对象。

    • 如何保证类在内存中只有一个对象呢?

      • (1)控制类的创建,不让其他类来创建本类的对象。private
      • (2)在本类中定义一个本类的对象。Singleton s;
      • (3)提供公共的访问方式。 public static Singleton getInstance(){return s}
    • 单例写法两种:

      • (1)饿汉式 开发用这种方式。
    //饿汉式
    class Singleton {
        //1,私有构造函数
        private Singleton(){}
        //2,创建本类对象
        private static Singleton s = new Singleton();
        //3,对外提供公共的访问方法
        public static Singleton getInstance() {
            return s;
        }
        
        public static void print() {
            System.out.println("11111111111");
        }
    }
    * (2)懒汉式 面试写这种方式。多线程的问题?
    
    //懒汉式,单例的延迟加载模式
    class Singleton {
        //1,私有构造函数
        private Singleton(){}
        //2,声明一个本类的引用
        private static Singleton s;
        //3,对外提供公共的访问方法
        public static Singleton getInstance() {
            if(s == null)
                //线程1,线程2
                s = new Singleton();
            return s;
        }
        
        public static void print() {
            System.out.println("11111111111");
        }
    }
    * (3)第三种格式
    
    class Singleton {
        private Singleton() {}
        public static final Singleton s = new Singleton();//final是最终的意思,被final修饰的变量不可以被更改
    }

    Runtime类

    • Runtime类是一个单例类
    Runtime r = Runtime.getRuntime();
    //r.exec("shutdown -s -t 300");        //300秒后关机
    r.exec("shutdown -a");                //取消关机

    Timer(掌握)

    • Timer类:计时器
    public class Demo5_Timer {
        /**
         * @param args
         * 计时器
         * @throws InterruptedException 
         */
        public static void main(String[] args) throws InterruptedException {
            Timer t = new Timer();
            t.schedule(new MyTimerTask(), new Date(114,9,15,10,54,20),3000);
            
            while(true) {
                System.out.println(new Date());
                Thread.sleep(1000);
            }
        }
    }
    class MyTimerTask extends TimerTask {
        @Override
        public void run() {
            System.out.println("起床背英语单词");
        }
        
    }

    两个线程间的通信(掌握)

    • 1.什么时候需要通信
      • 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
      • 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
    • 2.怎么通信
      • 如果希望线程等待, 就调用wait()
      • 如果希望唤醒等待的线程, 就调用notify();
      • 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

    三个或三个以上间的线程通信

    • 多个线程通信的问题
      • notify()方法是随机唤醒一个线程
      • notifyAll()方法是唤醒所有线程
      • JDK5之前无法唤醒指定的一个线程
      • 如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件

    JDK1.5的新特性互斥锁(掌握)

    • 1.同步
      • 使用ReentrantLock类的lock()和unlock()方法进行同步
    • 2.通信
      • 使用ReentrantLock类的newCondition()方法可以获取Condition对象
      • 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
      • 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了

    线程组的概述和使用(了解)

    • A:线程组概述
      • Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
      • 默认情况下,所有的线程都属于主线程组。
        • public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
        • public final String getName()//通过线程组对象获取他组的名字
      • 我们也可以给线程设置分组
        • 1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
        • 2,创建线程对象
        • 3,Thread(ThreadGroup?group, Runnable?target, String?name)
        • 4,设置整组的优先级或者守护线程
      • B:案例演示
        • 线程组的使用,默认是主线程组
            MyRunnable mr = new MyRunnable();
            Thread t1 = new Thread(mr, "张三");
            Thread t2 = new Thread(mr, "李四");
            //获取线程组
            // 线程类里面的方法:public final ThreadGroup getThreadGroup()
            ThreadGroup tg1 = t1.getThreadGroup();
            ThreadGroup tg2 = t2.getThreadGroup();
            // 线程组里面的方法:public final String getName()
            String name1 = tg1.getName();
            String name2 = tg2.getName();
            System.out.println(name1);
            System.out.println(name2);
            // 通过结果我们知道了:线程默认情况下属于main线程组
            // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
            System.out.println(Thread.currentThread().getThreadGroup().getName());

    * 自己设定线程组
            // ThreadGroup(String name)
            ThreadGroup tg = new ThreadGroup("这是一个新的组");
    
            MyRunnable mr = new MyRunnable();
            // Thread(ThreadGroup group, Runnable target, String name)
            Thread t1 = new Thread(tg, mr, "张三");
            Thread t2 = new Thread(tg, mr, "李四");
            
            System.out.println(t1.getThreadGroup().getName());
            System.out.println(t2.getThreadGroup().getName());
            
            //通过组名称设置后台线程,表示该组的线程都是后台线程
            tg.setDaemon(true);

    线程的五种状态(掌握)

    • 看图说话
    • 新建,就绪,运行,阻塞,死亡

    线程池的概述和使用(了解)

    • A:线程池概述
      • 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
    • B:内置线程池的使用概述
      • JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
        • public static ExecutorService newFixedThreadPool(int nThreads)
        • public static ExecutorService newSingleThreadExecutor()
        • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
        • Future<?> submit(Runnable task)
        • Future submit(Callable task)
      • 使用步骤:
        • 创建线程池对象
        • 创建Runnable实例
        • 提交Runnable实例
        • 关闭线程池
      • C:案例演示
        • 提交的是Runnable
    // public static ExecutorService newFixedThreadPool(int nThreads)
    ExecutorService pool = Executors.newFixedThreadPool(2);
    
    // 可以执行Runnable对象或者Callable对象代表的线程
    pool.submit(new MyRunnable());
    pool.submit(new MyRunnable());
    
    //结束线程池
    pool.shutdown();

    多线程程序实现的方式3(了解)

    • 提交的是Callable
    // 创建线程池对象
    ExecutorService pool = Executors.newFixedThreadPool(2);
    
    // 可以执行Runnable对象或者Callable对象代表的线程
    Future<Integer> f1 = pool.submit(new MyCallable(100));
    Future<Integer> f2 = pool.submit(new MyCallable(200));
    
    // V get()
    Integer i1 = f1.get();
    Integer i2 = f2.get();
    
    System.out.println(i1);
    System.out.println(i2);
    
    // 结束
    pool.shutdown();
    
    public class MyCallable implements Callable<Integer> {
    
        private int number;
    
        public MyCallable(int number) {
            this.number = number;
        }
    
        @Override
        public Integer call() throws Exception {
            int sum = 0;
            for (int x = 1; x <= number; x++) {
                sum += x;
            }
            return sum;
        }
    
    }
    • 多线程程序实现的方式3的好处和弊端
      • 好处:

        • 可以有返回值
        • 可以抛出异常
      • 弊端:

        • 代码比较复杂,所以一般不用

    简单工厂模式概述和使用(了解)

    • A:简单工厂模式概述
      • 又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例
    • B:优点
      • 客户端不需要在负责对象的创建,从而明确了各个类的职责
    • C:缺点
      • 这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护
    • D:案例演示
      • 动物抽象类:public abstract Animal { public abstract void eat(); }
      • 具体狗类:public class Dog extends Animal {}
      • 具体猫类:public class Cat extends Animal {}
      • 开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。
    public class AnimalFactory {
        private AnimalFactory(){}
    
        //public static Dog createDog() {return new Dog();}
        //public static Cat createCat() {return new Cat();}
    
        //改进
        public static Animal createAnimal(String animalName) {
            if(“dog”.equals(animalName)) {}
            else if(“cat”.equals(animale)) {
    
            }else {
                return null;
            }
        }
    }

    工厂方法模式的概述和使用(了解)

    • A:工厂方法模式概述
      • 工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。
    • B:优点
      • 客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性
    • C:缺点
      • 需要额外的编写代码,增加了工作量
    • D:案例演示
    •   动物抽象类:public abstract Animal { public abstract void eat(); }
        工厂接口:public interface Factory {public abstract Animal createAnimal();}
        具体狗类:public class Dog extends Animal {}
        具体猫类:public class Cat extends Animal {}
        开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就知道了一个专门的类来创建对象。发现每次修改代码太麻烦,用工厂方法改进,针对每一个具体的实现提供一个具体工厂。
      
    		狗工厂:public class DogFactory implements Factory {
    			public Animal createAnimal() {…}
    		        }
    		猫工厂:public class CatFactory implements Factory {
    			public Animal createAnimal() {…}
    		        }  
    

    day25总结

    • 单例设计模式(重点掌握):
      • 饿汉式:
    class Singleton {
        // 1.既然是单例,在别的类中不能创建对象,所以
        private Singleton() {}
        private static Singleton s = new Singleton();
        public static Singleton getInstance() {
            return s;
        }
    }
    
    class Singleton {
        // 1.既然是单例,在别的类中不能创建对象,所以
        private Singleton() {}
        public static final Singleton s = new Singleton();
    }
    * 懒汉式:(多线程环境下可能会创建多个对象。解决:双重判断+同步代码块)
    class Singleton {
        private Singleton() {}
        private static Singleton s;
        
        /*
        public static Singleton getInstance() {
            if(s == null) {
                s = new Singleton();
            }
            return s;
        }
        */
        public static Singleton getInstance() {
            if(s == null) { // 提高效率
                synchronized(Singleton.class) {
                    if(s == null) { // 防止重复创建对象
                        s = new Singleton();
                    }                        
                }                    
            }
            return s;
        }
    }
    • 线程间的通信:多个线程对共享数据进行不同的操作;

    实战java高并发程序设计笔记

    Question:

    1. 线程等待,阻塞,挂起之间的区别?它们会释放资源和锁吗?
    1. 挂起是线程主动进入的,因此它也可以自主的恢复运行状态
    2. 阻塞是线程被动进入的,什么时候结束阻塞状态它自身是无法控制的
    1. 多线程之间如何通信?线程间的数据如何传递?

      1. 可以用流传递数据(管道流,pipestream)
      2. blockingqueue
    2. 多线程控制同步的方式有哪些?

      1. synchronized 关键字
      2. 重入锁
      1. 为什么叫重入锁?一个线程可以多次获得同一把锁,但同时在离开的时候也必须释放相同的次数.
      2. 为什么要使用重入锁? 因为重入锁的逻辑控制的灵活性要远远好于synchronizec关键字.
      3. 可以中断等待状态,对处理死锁有一定的帮助.
      4. 锁的申请限时等待.
    3. 什么是原子类?
      Java原子类实现原理分析

    java多线程什么时候释放锁—wait()、notify()

    由于等待一个锁定线程只有在获得这把锁之后,才能恢复运行,所以让持有锁的线程在不需要锁的时候及时释放锁是很重要的。在以下情况下,持有锁的线程会释放锁
    1. 执行完同步代码块。
    2. 在执行同步代码块的过程中,遇到异常而导致线程终止。
    3. 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进行对象的等待池。

    除了以上情况外,只要持有锁的线程还没有执行完同步代码块,就不会释放锁。因此在以下情况下,线程不会释放锁
    1. 在执行同步代码块的过程中,执行了Thread.sleep()方法,当前线程放弃CPU,开始睡眠,在睡眠中不会释放锁。
    2. 在执行同步代码块的过程中,执行了Thread.yield()方法,当前线程放弃CPU,但不会释放锁。
    3. 在执行同步代码块的过程中,其他线程执行了当前对象的suspend()方法,当前线程被暂停,但不会释放锁。但Thread类的suspend()方法已经被废弃。

    避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B和C时,保证使每个线程都按照同样的顺序去访问他们,比如都先访问A,再访问B和C。

    java.lang.Object类中提供了两个用于线程通信的方法:wait()和notify()。需要注意到是,wait()方法必须放在一个循环中,因为在多线程环境中,共享对象的状态随时可能改变。当一个在对象等待池中的线程被唤醒后,并不一定立即恢复运行,等到这个线程获得了锁及CPU才能继续运行,又可能此时对象的状态已经发生了变化。

    调用obj的wait(),notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {…} 代码段内。

    调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {…} 代码段内唤醒A。
      当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
      
       如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。
      
       obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。
      
       当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。

    wait()/sleep()的区别

    前面讲了wait/notify机制,Thread还有一个sleep()静态方法,它也能使线程暂停一段时间。sleep与wait的不同点是:sleep并不释放锁,并且sleep的暂停和wait暂停是不一样的。obj.wait会使线程进入obj对象的等待集合中并等待唤醒。
      
      但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。
      
      如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
      
      需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。

  • 相关阅读:
    迷茫之后的选择是理想
    Java 数组在内存中的结构
    Himi浅谈游戏开发de自学历程!(仅供参考)
    java+上传文件夹
    java+web+大文件上传下载
    http+断点续传
    浏览器上传大文件
    java大文件上传
    网页上传大文件
    php+大文件上传
  • 原文地址:https://www.cnblogs.com/hoonick/p/9820790.html
Copyright © 2020-2023  润新知