zoukankan      html  css  js  c++  java
  • Java多线程

    本节内容:

    • 什么是线程
    • 多线程并行和并发的区别
    • 多线程程序实现方式
    • 多线程中的一些方法
    • 多线程之线程同步
    • 线程安全
    • 多线程的死锁
    • 线程安全的类
    • 多线程设计模式之单例设计模式
    • 线程组的概述和使用
    • 线程的五种状态
    • 线程池的概述和使用
    • 设计模式之简单工厂模式
    • 设计模式之工厂方法模式

    如果对什么是线程、什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内。一篇浅显易懂的介绍进程和线程的文章:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

    一、什么是线程

    1. 线程是什么

    线程是程序执行的一条路径,一个进程中可以包含多条线程,那么这个进程就是多线程的。

    多线程:指的是这个程序(一个进程)运行时产生了不止一个线程。

    多线程并发执行可以提高程序的效率,可以同时完成多项工作。

    比如我打开360,点击“查杀修复”、“电脑清理”和“优化加速”,如下图。

    这3个都在运行,这就是多线程。如果是单线程,在运行“查杀修复”时,“电脑清理”和“优化加速”都得等着。等“查杀修复”运行完了,才能运行下一个。

    多线程的效率为什么会高?见:https://www.cnblogs.com/shann/p/6851889.html

    2. 多线程的应用场景

    • 红蜘蛛同时共享屏幕给多个电脑
    • 迅雷就是多条线程一起下载
    • QQ同时和多个人一起视频
    • 服务器同时处理多个客户端请求

    二、多线程并行和并发的区别

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

    Java程序运行原理:

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

    JVM的启动是多线程的

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

    Demo1_Thread.java

    package com.wisedu.thread;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo1_Thread {
    
        /**
         * @param args
         * 证明jvm是多线程的
         */
        public static void main(String[] args) {
            for(int i = 0; i < 1000000; i++) {
                new Demo(); //创造些垃圾出来,匿名对象就是垃圾
            }
    
            for(int i = 0; i < 100000; i++) { //看看主线程和垃圾回收线程是否会在cpu上切换
                System.out.println("我是主线程的执行代码");
            }
        }
    
    }
    
    class Demo {
    
        @Override
        public void finalize() { //Object类中的方法
            System.out.println("垃圾被清扫了");
        }
    
    }
    

    “我是主线程的执行代码”和“垃圾被清扫了”这两句话会交替打印,说明是间隔执行的,是多线程的。如果不是多线程的,一定是全部打印完了“垃圾被清扫了”,才会去打印“我是主线程的执行代码”。

    三、多线程程序实现方式

    1. 继承Thread

    【步骤】:

    1. 定义类继承Thread
    2. 重写run方法
    3. 把新线程要做的事情写在run方法中
    4. 创建线程对象
    5. 开启新线程,内部会自动执行run方法

    【示例】:Demo2_Thread.java

    package com.wisedu.thread;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo2_Thread {
    
        /**
         * @param args
         */
        public static void main(String[] args) { //执行main方法,发现有b有a间隔输出了
            MyThread mt = new MyThread();		//4,创建Thread类的子类对象
            mt.start();				//5,开启线程 注意这里不是调用run方法
    
            for(int i = 0; i < 1000; i++) { //为了看出是多线程执行,在主方法里也写点内容
                System.out.println("bb");
            }
        }
    
    }
    
    class MyThread extends Thread {				//1,继承Thread
        public void run() {						//2,重写run方法
            for(int i = 0; i < 1000; i++) {		//3,将要执行的代码写在run方法中
                System.out.println("aaaaaaaaaaaa");
            }
        }
    }
    

      

    2. 实现Runnable

    【步骤】:

    1. 定义类实现Runnable接口
    2. 实现run方法
    3. 把新线程要做的事情写在run方法中
    4. 创建自定义的Runnable的子类对象
    5. 创建Thread对象,传入Runnable
    6. 调用start()开启新线程,内部会自动调用Runnable的run()方法

    【示例】:Demo3_Thread.java

    package com.wisedu.thread;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo3_Thread {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            MyRunnable mr = new MyRunnable();	//4,创建Runnable的子类对象
            //Runnable target = mr;	假如mr = 0x0011 //父类引用指向子类对象,编译看的是父类,运行看的是子类
            Thread t = new Thread(mr);			//5,将其当作参数传递给Thread的构造函数
            t.start();							//6,开启线程 Thread类中才有start方法
    
            for(int i = 0; i < 1000; i++) {
                System.out.println("bb");
            }
        }
    
    }
    
    class MyRunnable implements Runnable {		//1,定义一个类实现Runnable
    
        @Override
        public void run() {						//2,重写run方法
            for(int i = 0; i < 1000; i++) {		//3,将要执行的代码写在run方法中
                System.out.println("aaaaaaaaaaaa");
            }
        }
    
    }
    

      

    3. 实现Runnable的原理

    查看源码,可以发现:

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

    4. 两种实现多线程方式的区别

    查看源码的区别:

    • 继承Thread:由于子类重写了Thread类的run(),当调用start()时,可以找子类的run()方法(调用start方法时,找子类的run方法,这是底层JVM帮我们完成的)
    • 实现Runnable:构造方法中传入了Runnable的引用,成员变量记住了它,start()方法调用run()方式时内部判断成员变量Runnable的引用是否为空,不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法。

    继承Thread:

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

    实现Runnable:

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

    5. 匿名内部类实现线程的两种方式

    好处就是不用找一个类去继承Thread类或者实现Runnable接口了。

    【示例】:Demo4_Thread.java

    package com.wisedu.thread;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo4_Thread {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            new Thread() {										//1,继承Thread类
                public void run() {								//2,重写run方法
                    for(int i = 0; i < 1000; i++) {				//3,将要执行的代码写在run方法中
                        System.out.println("aaaaaaaaaaaaaa");
                    }
                }
            }.start();											//4,开启线程
    
            new Thread(new Runnable() {							//1,将Runnable的子类对象传递给Thread的构造方法
                public void run() {								//2,重写run方法
                    for(int i = 0; i < 1000; i++) {				//3,将要执行的代码写在run方法中
                        System.out.println("bb");
                    }
                }
            }).start();											//4,开启线程
        }
    
    }

    四、多线程中的一些方法

    1. 获取名字和设置名字

    (1)获取名字

    通过getName()方法获取线程对象的名字

    (2)设置名字

    通过构造函数可以传入String类型的名字

    通过setName(String )方法可以设置线程对象的名字

    【示例】:Demo1_Name.java

    package com.wisedu.threadmethod;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo1_Name {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            //demo1();
            Thread t1 = new Thread() {
                public void run() {
                    //this.setName("张三");
                    System.out.println(this.getName() + "....aaaaaaaaaaaaa"); //this就相当于这个匿名内部类对象
                }
            };
    
            Thread t2 = new Thread() {
                public void run() {
                    //this.setName("李四");
                    System.out.println(this.getName() + "....bb");
                }
            };
    
            t1.setName("张三");
            t2.setName("李四");
            t1.start();
            t2.start();
        }
    
        public static void demo1() {
            new Thread("芙蓉姐姐") {	//通过构造方法给name赋值
                public void run() {
                    System.out.println(this.getName() + "....aaaaaaaaa");
                }
            }.start();
    
            new Thread("凤姐") {
                public void run() {
                    System.out.println(this.getName() + "....bb");
                }
            }.start();
        }
    
    }
    

      

    2. 获取当前线程的对象

    Thread.currentThread(),主线程也可以获取。

    【示例】:Demo2_CurrentThread.java

    package com.wisedu.threadmethod;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo2_CurrentThread {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            new Thread() {
                public void run() {
                    System.out.println(getName() + "....aaaaaa");
                }
            }.start();
    
            new Thread(new Runnable() {
                public void run() {
                    //Thread.currentThread()获取当前正在执行的线程
                    System.out.println(Thread.currentThread().getName() + "...bb");
                }
            }).start();
    
            Thread.currentThread().setName("我是主线程");
            System.out.println(Thread.currentThread().getName());
        }
    
    }
    

      

    3. 休眠线程

    Thread.sleep(毫秒,纳秒),控制当前线程休眠若干毫秒,windows不太支持纳秒值。 1秒 = 1000毫秒,1秒 = 1000 * 1000 * 1000m纳秒。

    【示例】:Demo3_Sleep.java

    package com.wisedu.threadmethod;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo3_Sleep {
    
        /**
         * @param args
         * @throws InterruptedException
         */
        public static void main(String[] args) throws InterruptedException {
            //demo1();
            new Thread() {
                public void run() {
                    for(int i = 0; i < 10; i++) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(getName() + "...aaaaaaaaaa");
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    for(int i = 0; i < 10; i++) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(getName() + "...bb");
                    }
                }
            }.start();
        }
    
        public static void demo1() throws InterruptedException {
            for(int i = 20; i >= 0; i--) {
                Thread.sleep(1000); //毫秒
                System.out.println("倒计时第" +i + "秒");
            }
        }
    
    }
    

      

    4. 守护线程

    setDaemon(),设置一个线程为守护线程,该线程不会单独执行,当其他非守护线程都执行结束后,自动退出。

    就像下象棋一样,非守护线程相当于帅,守护线程相当于车马相士。帅死掉了,其他的车马相士也随之停止,车马相士自杀有个缓冲时间。

    再比如,用QQ向别人传文件的时候,qq的主界面用的是非守护线程做的,而传输的窗口用的守护线程,主界面一关掉,传输并不是立马就停止的,它还会再发几个数据包,因为它要等待接收退出的命令。

    【示例】:Demo4_Daemon.java

    package com.wisedu.threadmethod;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo4_Daemon {
    
        /**
         * @param args
         * 守护线程
         */
        public static void main(String[] args) {
            Thread t1 = new Thread() { //帅
                public void run() {
                    for(int i = 0; i < 2; i++) {
                        System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaa");
                    }
                }
            };
    
            Thread t2 = new Thread() { //车马相士
                public void run() {
                    for(int i = 0; i < 50; i++) {
                        System.out.println(getName() + "...bb");
                    }
                }
            };
    
            t2.setDaemon(true); //当传入true,意味着设置为守护线程
    
            t1.start();
            t2.start();
        }
    
    }
    

      

    5. 加入线程

    • join(),当前线程暂停,等待指定的线程执行结束后,当前线程再继续。就相当于插队。
    • join(int),可以等待指定的毫秒之后继续

    【示例】:Demo5_Join.java

    package com.wisedu.threadmethod;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo5_Join {
    
        /**
         * @param args
         * join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
         */
        public static void main(String[] args) {
            final Thread t1 = new Thread() {
                public void run() {
                    for(int i = 0; i < 10; i++) {
                        System.out.println(getName() + "...aaaaaaaaaaaaa");
                    }
                }
            };
    
            Thread t2 = new Thread() {
                public void run() {
                    for(int i = 0; i < 10; i++) {
                        if(i == 2) {
                            try {
                                //t1.join();  //插队线程执行完后,当前线程才能执行。这里匿名内部类在使用它所在方法中的局部变量的时候,该变量必须用final修饰
                                t1.join(1);	  //插队指定的时间,过了指定时间后,两条线程交替执行
                            } catch (InterruptedException e) { //插队过来,当前线程出现中断异常
                                e.printStackTrace();
                            }
                        }
                        System.out.println(getName() + "...bb");
                    }
                }
            };
    
            t1.start();
            t2.start();
        }
    
    }
    

      

    6. 礼让线程

    yield让出CPU,但是这个yield实现的效果不好,会达不到这个效果。

    【示例】:Demo6_Yield.java

    package com.wisedu.threadmethod;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo6_Yield {
    
        /**
         * yield让出cpu,礼让线程
         */
        public static void main(String[] args) {
            new MyThread().start();
            new MyThread().start();
        }
    
    }
    
    class MyThread extends Thread {
        public void run() {
            for(int i = 1; i <= 1000; i++) {
                System.out.println(getName() + "..." + i);
                if(i % 10 == 0) {  //10的倍数
                    Thread.yield();	 //让出CPU
                }
            }
        }
    }
    

      

    7. 设置线程优先级

    setPriority() 设置线程优先级

    【示例】:Demo7_Priority.java

    package com.wisedu.threadmethod;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo7_Priority {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            Thread t1 = new Thread(){
                public void run() {
                    for(int i = 0; i < 100; i++) {
                        System.out.println(getName() + "...aaaaaaaaa" );
                    }
                }
            };
    
            Thread t2 = new Thread(){
                public void run() {
                    for(int i = 0; i < 100; i++) {
                        System.out.println(getName() + "...bb" );
                    }
                }
            };
    
            //t1.setPriority(10);
            //t2.setPriority(1);    //直接给值也可以,但是不能超过下面两个常量
    
            t1.setPriority(Thread.MIN_PRIORITY);		//设置最小的线程优先级
            t2.setPriority(Thread.MAX_PRIORITY);		//设置最大的线程优先级
    
            t1.start();
            t2.start();
        }
    
    }
    

      

    Thread.interrupted()

    待补充。。。

      

    五、多线程之线程同步

    1. 什么情况下需要同步

    当多线程并发,有多段代码同时执行时,我们希望某一段代码执行的过程中CPU不要切换到其他线程工作,等当前这段代码执行完再切换到其它线程,这时就需要同步。

    如果两段代码是同步的,那么同一个时间只能执行一段,在一段代码没执行结束之前,不会执行另外一段代码。

    举个例子:赚了3000块钱存银行,然后你有一存款折和一张银行卡,都是对应于同一个账户,你拿这个折子去柜台取钱去,柜台服务人员问你取多少钱,你说2000。这个时候服务人员得把2000输入电脑,电脑会去检查你现在有没有2000块钱。一查发现你有多余2000块的存款,这时候就把钱吐给你。然后把你账户上的钱减掉2000。我们假设检查完你账户发现钱够,正要把钱出给你的时候,你老婆拿着你的银行卡在取款机上取钱,取2000,取款机一检查发现你的账户里有超过2000的存款(此时你的账户上钱还没减),然后取款机就把这2000吐给你老婆了,然后取款机把你的账户的余额更新为1000。然后柜台那边继续执行,又给了你2000,把你的账户余额更新为1000。

    这显然对银行不公平,你和你老婆好比两个线程,这两个线程在执行一个对账户取款的方法的过程之中,但是你们两个线程同时访问同一个账户(同一个资源),这种情况下线程顺序协调不好的话,很容易出现前后数据不一致的情况。我们对多个线程访问同一个资源的时候,我们对这多个线程进行协调的这个东西叫做线程同步。

    怎么解决这个问题呢?

    当某个线程在调用取款方法的时候,这个账户归这个线程独占,其他线程不能访问。

    代码模拟:

    public class TestSync implements Runnable {
        Timer timer = new Timer();
    
        public static void main(String[] args) {
            TestSync test = new TestSync();
            Thread t1 = new Thread(test);
            Thread t2 = new Thread(test);
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            t2.start();
    
        }
    
        public void run(){
            timer.add(Thread.currentThread().getName());
        }
    }
    
    class Timer{
        private int num = 0;
    
        public void add(String name){ //用来做计数用的
            num ++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {}
    
            System.out.println(name+", 你是第"+num+"个使用timer的线程");
        }
    }

    运行结果:

    t1, 你是第2个使用timer的线程
    t2, 你是第2个使用timer的线程
    
    Process finished with exit code 0

    问题出在执行 num ++ 和 打印语句 之间被打断了(因为第一个线程睡眠了),第二个线程调add方法去修改同一个对象的成员变量num的值,接着第二个线程睡眠,第一个线程醒了过来,打印num的值为2。然后第2个线程醒过来打印num的值为2。

    num ++ 和 打印语句 应作为原子操作,不可再分。

    怎么解决呢?锁住当前对象。

    class Timer{
        private int num = 0;
    
        public void add(String name){ //用来做计数用的
            synchronized (this) { //锁定当前对象,对于下面的语句,一个线程在执行的时候不会被另外的线程打断。这个对象里面的成员变量num也被锁定了。这叫互斥锁
                num++;
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                }
    
                System.out.println(name + ", 你是第" + num + "个使用timer的线程");
            }
        }
    }

    还有一种更为简洁的写法:

    class Timer{
        private int num = 0;
    
        public synchronized void add(String name){ //在执行该方法时锁定当前对象,也就是timer对象
            //synchronized (this) { //锁定当前对象,对于下面的语句,一个线程在执行的时候不会被另外的线程打断。这个对象里面的成员变量num也被锁定了。这叫互斥锁
                num++;
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                }
    
                System.out.println(name + ", 你是第" + num + "个使用timer的线程");
            //}
        }
    }

    2. 同步的机制:每个对象都有的方法

    synchronized, wait, notify 是任何对象都具有的同步工具。他们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。

    wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。

    当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。

    用法如下:

    • synchronized单独使用:
      • 同步代码块:如下,在多线程环境下,synchronized块中的方法获取了lock实例的monitor,如果实例相同,那么只有一个线程能执行该块内容
      • public class Thread1 implements Runnable {
           Object lock;
           public void run() {  
               synchronized(lock){
                 ..do something
               }
           }
        }
      • 直接用于方法: 相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。更进一步,如果修饰的是static方法,则锁定该类所有实例。
      • public class Thread1 implements Runnable {
           public synchronized void run() {  
                ..do something
           }
        }
    • synchronized, wait, notify结合:典型场景生产者消费者问题
      • /**
           * 生产者生产出来的产品交给店员
           */
          public synchronized void produce()
          {
              if(this.product >= MAX_PRODUCT)
              {
                  try
                  {
                      wait();  
                      System.out.println("产品已满,请稍候再生产");
                  }
                  catch(InterruptedException e)
                  {
                      e.printStackTrace();
                  }
                  return;
              }
        
              this.product++;
              System.out.println("生产者生产第" + this.product + "个产品.");
              notifyAll();   //通知等待区的消费者可以取出产品了
          }
        
          /**
           * 消费者从店员取产品
           */
          public synchronized void consume()
          {
              if(this.product <= MIN_PRODUCT)
              {
                  try 
                  {
                      wait(); 
                      System.out.println("缺货,稍候再取");
                  } 
                  catch (InterruptedException e) 
                  {
                      e.printStackTrace();
                  }
                  return;
              }
        
              System.out.println("消费者取走了第" + this.product + "个产品.");
              this.product--;
              notifyAll();   //通知等待去的生产者可以生产产品了
          }

    下面将进行更详细的讲解。

    3. 同步代码块

    使用synchronized关键字加上一个锁对象来定义一段代码,这就叫同步代码块。

    多个同步代码块如果使用相同的锁对象,那么它们就是同步的。

    【示例】:Demo1_Synchronized.java

    package com.wisedu.sync;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo1_Synchronized {
    
        /**
         * @param args
         * 同步代码块
         */
        public static void main(String[] args) {
            final Printer p = new Printer();
    
            new Thread() {
                public void run() {
                    while(true) {
                        p.print1();
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    while(true) {
                        p.print2();
                    }
                }
            }.start();
        }
    
    }
    
    class Printer {
        Demo d = new Demo();
        public void print1() {
            //synchronized(new Demo()) {							//同步代码块,锁机制,锁是对象来做的,锁对象可以是任意的,直接创建个Object对象也可以
            synchronized(d) {
                System.out.print("黑");
                System.out.print("马");
                System.out.print("程");
                System.out.print("序");
                System.out.print("员");
                System.out.print("
    ");
            }
        }
    
        public void print2() {
            //synchronized(new Demo()) {							//锁对象不能用匿名对象,因为匿名对象不是同一个对象
            synchronized(d) {
                System.out.print("传");
                System.out.print("智");
                System.out.print("播");
                System.out.print("客");
                System.out.print("
    ");
            }
        }
    }
    
    class Demo{}
    

      

    4. 多线程同步方法

    使用synchronized关键字修饰一个方法,该方法中所有的代码都是同步的。

    【示例】:Demo2_Synchronized.java

    package com.wisedu.sync;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo2_Synchronized {
    
        /**
         * @param args
         * 同步代码块
         */
        public static void main(String[] args) {
            final Printer2 p = new Printer2();
    
            new Thread() {
                public void run() {
                    while(true) {
                        p.print1();
                        //p.print3();
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    while(true) {
                        p.print2();
                        //p.print4();
                    }
                }
            }.start();
        }
    
    }
    
    class Printer2 {
        //非静态的同步方法的锁对象是神马?
        //答:非静态的同步方法的锁对象是this
        public synchronized void print1() {		//同步方法只需要在方法上加synchronized关键字即可
            System.out.print("黑");
            System.out.print("马");
            System.out.print("程");
            System.out.print("序");
            System.out.print("员");
            System.out.print("
    ");
        }
    
        public void print2() {
            synchronized(this) {
                System.out.print("传");
                System.out.print("智");
                System.out.print("播");
                System.out.print("客");
                System.out.print("
    ");
            }
        }
    
        //静态的同步方法的锁对象是什么?
        //答:是该类的字节码对象
        public static synchronized void print3() {		//同步方法只需要在方法上加synchronized关键字即可
            System.out.print("黑");
            System.out.print("马");
            System.out.print("程");
            System.out.print("序");
            System.out.print("员");
            System.out.print("
    ");
        }
    
        public static void print4() {
            synchronized(Printer2.class) {
                System.out.print("传");
                System.out.print("智");
                System.out.print("播");
                System.out.print("客");
                System.out.print("
    ");
            }
        }
    }
    

      

    六、线程安全

    多线程并发操作同一数据时,就有可能出现线程安全问题。

    使用同步技术可以解决这种问题,把操作数据的代码进行同步,不要多个线程一起操作。

    【示例】:铁路售票,一共100张,通过四个窗口卖完(四个窗口也就是四个线程)。

    Demo3_Ticket.java --继承Thread类

    package com.wisedu.sync;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo3_Ticket {
    
        /**
         * 需求:铁路售票,一共100张,通过四个窗口卖完.
         */
        public static void main(String[] args) {
            new Ticket().start();
            new Ticket().start();
            new Ticket().start();
            new Ticket().start();
        }
    
    }
    
    class Ticket extends Thread {
        private static int ticket = 100; //所有对象共享这100张票
        //private static Object obj = new Object();		//如果用 引用数据类型的成员变量 当作锁对象,必须是静态的
        public void run() {
            while(true) {
                synchronized(Ticket.class) { //obj
                    if(ticket == 0) {
                        break;
                    }
                    try { //模拟这边可能有n多行代码执行
                        Thread.sleep(10);				//10毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "...这是第" + ticket-- + "号票");
                }
            }
        }
    }

    Demo4_Ticket.java --实现Runnable接口

    package com.wisedu.sync;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo4_Ticket {
    
        /**
         * @param args
         * 火车站卖票的例子用实现Runnable接口
         */
        public static void main(String[] args) {
            MyTicket mt = new MyTicket();
            new Thread(mt).start();
            new Thread(mt).start();
            new Thread(mt).start();
            new Thread(mt).start();
    
    		/*Thread t1 = new Thread(mt);				//多次启动一个线程是非法的
    		t1.start();
    		t1.start();
    		t1.start();
    		t1.start();*/
        }
    
    }
    
    class MyTicket implements Runnable {
        private int tickets = 1000; //因为我们不需要创建4个对象,所以不需要定义成静态的
        @Override
        public void run() {
            while(true) {
                synchronized(Ticket.class) { //可以用this,因为只创建了一个对象
                    if(tickets == 0) {
                        break;
                    }
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "...这是第" + tickets-- + "号票");
                }
            }
        }
    }
    

      

    七、多线程的死锁

    多线程同步的时候,如果代码嵌套,使用相同锁,就有可能出现死锁。所以尽量不要嵌套使用。

    比如,桌子上摆了一桌满汉全席,桌子周围坐着一群哲学家,然后给哲学家发筷子,一人一根,然后哲学家就用自己的三寸不烂之舌说服别人把筷子给自己。互相去说服,最后可能导致谁都没有说服谁,导致成了死锁,全部活活饿死在满汉全席的边上。

    【示例】:Demo5_DeadLock.java

    package com.wisedu.sync;
    
    /**
     * Created by jkzhao on 1/22/18.
     */
    public class Demo5_DeadLock {
    
        /**
         * @param args
         */
        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();
        }
    }

    执行结果:

    Thread-0...获取筷子左等待筷子右
    Thread-0...拿到筷子右开吃
    Thread-0...获取筷子左等待筷子右
    Thread-0...拿到筷子右开吃
    Thread-0...获取筷子左等待筷子右
    Thread-0...拿到筷子右开吃
    Thread-0...获取筷子左等待筷子右
    Thread-1...获取筷子右等待筷子左

    此时已经产生死锁了,程序并没有停止。。。

    八、线程安全的类

    Vertor、StringBuffer、Hashtable、Collections.synchronized(xxx)

    1. Vector是线程安全的

    查看源码java.util.Vector.java,找到add方法

        public synchronized boolean add(E e) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = e;
            return true;
        }
    

      

    2. ArrayList是线程不安全的

    查看源码,java.util.ArrayList.java,找到add方法

        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    

      

    3. StringBuffer是线程安全的,StringBuilder是线程不安全的

    查看源码,java.lang.StringBuffer,java.lang.StringBuilder,找到append方法

        public synchronized StringBuffer append(Object obj) {
            super.append(String.valueOf(obj));
            return this;
        }
    
        public synchronized StringBuffer append(String str) {
            super.append(str);
            return this;
        }
    ...
        public StringBuilder append(Object obj) {
            return append(String.valueOf(obj));
        }
    
        public StringBuilder append(String str) {
            super.append(str);
            return this;
        }
        ...
    

     

    4. Hashtable是线程安全的,HashMap是线程不安全的

    查看源码,java.lang.Hashtable,java.lang.HashMap,找到append方法

    java.lang.Hashtable

        public synchronized V put(K key, V value) {
            // Make sure the value is not null
            if (value == null) {
                throw new NullPointerException();
            }
            ...
    

    java.lang.HashMap

        public V put(K key, V value) {
            if (table == EMPTY_TABLE) {
                inflateTable(threshold);
            }
            ...
    

      

    5. Collections.synchronized(xxx)

    可以将线程不安全的线程变成线程安全的。可以到API文档里搜索下Collections这个类,里面有方法synchronized(xxx)

    调用上面的这些方法就可以将这几种集合变成同步的。

    九、多线程设计模式之单例设计模式

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

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

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

    1. 单例模式两种写法

    (1)饿汉式,开发使用这种方式

    (2)懒汉式,面试写这种方式。多线程问题?

    Demo1_Singleton.java

    package com.wisedu.thread2;
    
    /**
     * Created by jkzhao on 1/23/18.
     */
    public class Demo1_Singleton {
    
        /**
         * @param args
         * * 单例设计模式:保证类在内存中只有一个对象。
         */
        public static void main(String[] args) {
    
            Singleton s1 = Singleton.s;				//成员变量被私有,不能通过类名.调用
            //Singleton.s = null;    //修改成员变量s
            Singleton s2 = Singleton.s;
    
            System.out.println(s1 == s2);
    
    	/*	Singleton s1 = Singleton.getInstance();
    		Singleton s2 = Singleton.getInstance();
    
    		System.out.println(s1 == s2);*/
        }
    
    }
    
    /*
     * 饿汉式
     * 上来就是new对象,所以称为饿汉式 private static Singleton s = new Singleton();
     * /
    /*class Singleton {
    	//1,私有构造方法,其他类不能访问该构造方法了
    	private Singleton(){}
    	//2,创建本类对象
    	private static Singleton s = new Singleton(); //私有化是为了防止被修改
    	//3,对外提供公共的访问方法
    	public static Singleton getInstance() {	 //由于上面把成员变量s私有化,这里提供get方法获取实例
    		return s;
    	}
    }*/
    /*
     * 懒汉式,单例的延迟加载模式
     */
    /*class Singleton {
    	//1,私有构造方法,其他类不能访问该构造方法了
    	private Singleton(){}
    	//2,声明一个引用
    	private static Singleton s ;
    	//3,对外提供公共的访问方法
    	public static Singleton getInstance() {				//获取实例
    		if(s == null) {
    			//假设线程1刚进来,结果CPU执行权被别的线程抢走了,那么线程1等待,线程2刚进来,CPU正好也切换去执行其他线程了,线程2等待。
    			//然后线程1抢回了CPU,于是执行下面语句创建了一个对象;线程2也抢回了CPU,于是执行下面语句又创建了一个对象。这就创建了两个对象
    			//所以开发的时候不用懒汉式,面试的时候用这个懒汉式,因为面试的时候会让写一个单例的延迟加载模式
    			s = new Singleton();
    		}
    
    		return s;
    	}
    }*/
    
    /*
     * 饿汉式和懒汉式的区别
     * 1,饿汉式是空间换时间,懒汉式是时间换空间
     * 2,在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象
     */
    
    /**
     * 第三种,没有名字
     */
    class Singleton {
        //1,私有构造方法,其他类不能访问该构造方法了
        private Singleton(){}
        //2,声明一个引用
        public static final Singleton s = new Singleton();
    
    }
    

      

    2. 单例设计模式应用场景之Runtime类

    饿汉式

    Demo2_Runtime.java

    package com.wisedu.thread2;
    
    /**
     * Created by jkzhao on 1/23/18.
     */
    import java.io.IOException;
    
    public class Demo2_Runtime {
    
        /**
         * @param args
         * @throws IOException
         */
        public static void main(String[] args) throws IOException {
            Runtime r = Runtime.getRuntime();			//获取运行时对象
            //r.exec("shutdown -s -t 300"); //在单独的进程中执行指定的字符串命令
            r.exec("shutdown -a");
            //为什么使用单例设计模式?
            // 第一条代码设置了5min后关机,第二条代码取消关机,修改的是第一条语句修改后的结果
        }
    
    }
    

      

    3. 单例设计模式应用场景之Timer类

    Timer类,计时器。一种工具,线程用其安排以后在后台线程中执行的任务,可以安排任务执行一次,或者定期重复执行。

    与每个Timer对象相对应的是单个后台线程,用于顺序地执行所有计时器任务。

    Demo3_Timer.java

    package com.wisedu.thread2;
    
    /**
     * Created by jkzhao on 1/23/18.
     */
    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class Demo3_Timer {
    
        /**
         * @param args
         * @throws InterruptedException
         */
        public static void main(String[] args) throws InterruptedException {
            Timer t = new Timer();
            //在指定时间安排指定任务
            //第一个参数,是安排的任务,第二个参数是执行的时间,第三个参数是过多长时间再重复执行
            t.schedule(new MyTimerTask(), new Date(118, 0, 22, 16, 39, 51),3000); //year是要-1900   最后的3000是毫秒值
    
            while(true) {
                Thread.sleep(1000);
                System.out.println(new Date()); //隔1s打一次时间,看看到指定时间是否执行任务
            }
        }
    
    }
    
    class MyTimerTask extends TimerTask {
    
        @Override
        public void run() {
            System.out.println("起床背英语单词");
        }
    }
    

      

    十、线程通信

    1. 什么时候需要通信

    多个线程并发执行时,在默认情况下CPU是随机切换线程的

    如果我们希望它们有规律的执行,就可以使用通信,例如每个线程执行一次打印

    2. 两个线程间通信

    • 如果需要线程等待,就调用wait()
    • 如果希望唤醒等待的线程,就调用notify():唤醒此对象监听器上等待的单个线程
    • 这两个方法必须在同步代码中执行,并且使用同步锁对象来调用

    Demo1_Notify.java

    package com.wisedu.thread_communication;
    
    /**
     * Created by jkzhao on 1/24/18.
     */
    public class Demo1_Notify {
    
        /**
         * @param args
         * 等待唤醒机制
         */
        public static void main(String[] args) {
            final Printer p = new Printer();
    
            new Thread() {
                public void run() {
                    while(true) {
                        try {
                            p.print1();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    while(true) {
                        try {
                            p.print2();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
    
    }
    
    //等待唤醒机制
    class Printer {
        private int flag = 1;
        public void print1() throws InterruptedException {
            synchronized(this) {
                if(flag != 1) {
                    this.wait();					//当前线程等待,没有人唤醒它的话,就一直在等待
                }
                System.out.print("黑");
                System.out.print("马");
                System.out.print("程");
                System.out.print("序");
                System.out.print("员");
                System.out.print("
    ");
                flag = 2;
                this.notify();						//随机唤醒单个等待的线程(假如此时没有等待的线程,随机唤醒下也是可以的)
            }
        }
    
        public void print2() throws InterruptedException {
            synchronized(this) {
                if(flag != 2) {
                    this.wait();
                }
                System.out.print("传");
                System.out.print("智");
                System.out.print("播");
                System.out.print("客");
                System.out.print("
    ");
                flag = 1;
                this.notify();
            }
        }
    }
    

      

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

    多个线程通信的问题

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

    Demo2_NotifyAll.java

    package com.wisedu.thread_communication;
    
    /**
     * Created by jkzhao on 1/24/18.
     */
    public class Demo2_NotifyAll {
    
        /**
         * 循环打印:黑马程序员、传智播客、itheima
         * @param args
         */
        public static void main(String[] args) {
            final Printer2 p = new Printer2();
            new Thread() {
                public void run() {
                    while(true) {
                        try {
                            p.print1();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    while(true) {
                        try {
                            p.print2();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    while(true) {
                        try {
                            p.print3();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
    
    }
    /* 1,在同步代码块中,用哪个对象锁,就用哪个对象去调用wait方法,比如下面用的是this
     * 2,为什么wait方法和notify方法定义在Object这类中?
     * 	 因为锁对象可以是任意对象,Object是所有类的基类,所以wait方法和notify方法需要定义在Object这个类中
     * 3,sleep方法和wait方法的区别?
     *   a,sleep方法必须传入参数,参数就是时间,时间到了自动醒来
     *     wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
     *   b,sleep方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡(就是在sleep的时间内,也占着CPU)
     * 	   wait方法在同步函数或者同步代码块中,释放锁(就是该线程调用wait方法等待了,把锁释放了,这样CPU才能去执行其他线程)
     */
    class Printer2 {
        private int flag = 1;
        public void print1() throws InterruptedException {
            synchronized(this) {
                while(flag != 1) {
                    this.wait();					//当前线程等待
                }
                System.out.print("黑");
                System.out.print("马");
                System.out.print("程");
                System.out.print("序");
                System.out.print("员");
                System.out.print("
    ");
                flag = 2;
                //this.notify();						//不能用这个
                this.notifyAll();
            }
        }
    
        public void print2() throws InterruptedException {
            synchronized(this) {
                while(flag != 2) {
                    this.wait();					//线程2在此等待
                }
                System.out.print("传");
                System.out.print("智");
                System.out.print("播");
                System.out.print("客");
                System.out.print("
    ");
                flag = 3;
                //this.notify(); //不能用这个
                this.notifyAll();
            }
        }
    
        public void print3() throws InterruptedException {
            synchronized(this) {
                while(flag != 3) {
                    this.wait();						//线程3在此等待,if语句是在哪里等待,就在哪里起来
                                                        //while循环是循环判断,每次抢到执行权都会去判断标记flag
                }
                System.out.print("i");
                System.out.print("t");
                System.out.print("h");
                System.out.print("e");
                System.out.print("i");
                System.out.print("m");
                System.out.print("a");
                System.out.print("
    ");
                flag = 1;
                //this.notify(); //不能用这个
                this.notifyAll();
            }
        }
    }

    分析下执行过程:

    1. 极端情况下,上来线程2抢到CPU执行权,CPU先执行线程2,此时flag=1,所以条件满足,线程2等待;
    2. 接着执行线程3,此时flag=1,所以条件满足,线程3等待;
    3. 现在活着的只有线程1,执行线程1,此时flag=1,条件不满足,所以线程1的代码继续往下走,开始打印,打印完将flag改为2,然后通过notifyAll()唤醒所有等待的线程,很有可能线程1仍然有CPU执行权,于是继续走while里的判断,2!=1,条件满足,线程1等待;
    4. 现在线程2和线程3都是活着的,假设先执行的线程3,2!=3,条件满足,于是线程3等待;
    5. 现在活着的只有线程2,2!=2,条件不满足,继续往下走,于是开始打印,然后把flag改为3,然后通过notifyAll()唤醒所有等待的线程。。。

    但是这不合理,也就是说不管符合不符合规则,到没到时间,就把其它线程都叫起来。比如说园区有3个保安,一个值早上8点到下午4点的班,一个是下午4点到晚上12点,一个是晚上12点到第二天早上8点的班。第一个值班后并不知道谁盯下一个班,于是就把另外两个保安叫起来了。两个人起来后看谁满足条件就去值班,另一个回去睡觉。如果总这么叫起人的话,人家就可能怒了。

    这是JDK 1.5之前的解决方案,在JDK 1.5有个更好的解决方案叫互斥锁。

    4. JDK 1.5的新特性之互斥锁

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

    Condition 将Object监视器方法(wait、notify和notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition替代了Object监视器方法的使用。Condition实例实质上被绑定到一个锁上,要为特定 Lock 实例获得 Condition 实例,请用其 newCondition() 方法。

    Demo3_ReetrantLock.java

    package com.wisedu.thread_communication;
    
    /**
     * Created by jkzhao on 1/24/18.
     */
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Demo3_ReentrantLock {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            final Printer3 p = new Printer3();
    
            new Thread() {
                public void run() {
                    while(true) {
                        try {
                            p.print1();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    while(true) {
                        try {
                            p.print2();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    while(true) {
                        try {
                            p.print3();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
    
    }
    
    class Printer3 {
        private ReentrantLock r = new ReentrantLock();
        private Condition c1 = r.newCondition(); //创建3个监视器,一个线程上放一个监视器
        private Condition c2 = r.newCondition();
        private Condition c3 = r.newCondition();
    
        private int flag = 1;
        public void print1() throws InterruptedException {
            r.lock();								//获取锁
            if(flag != 1) {
                c1.await(); //等待
            }
            System.out.print("黑");
            System.out.print("马");
            System.out.print("程");
            System.out.print("序");
            System.out.print("员");
            System.out.print("
    ");
            flag = 2;
            //this.notify();
            c2.signal();  //唤醒
            r.unlock();								//释放锁
        }
    
        public void print2() throws InterruptedException {
            r.lock();
            if(flag != 2) {
                c2.await();
            }
            System.out.print("传");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("
    ");
            flag = 3;
            //this.notify();
            c3.signal();
            r.unlock();
        }
    
        public void print3() throws InterruptedException {
            r.lock();
            if(flag != 3) {
                c3.await();
            }
            System.out.print("i");
            System.out.print("t");
            System.out.print("h");
            System.out.print("e");
            System.out.print("i");
            System.out.print("m");
            System.out.print("a");
            System.out.print("
    ");
            flag = 1;
            c1.signal();
            r.unlock();
        }
    }
    

      

    十一、线程组的概述和使用

    1. 线程组的概述

    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. 设置整组的优先级或守护线程

    2. 线程组的使用

    【示例】:线程组的使用,默认是主线程组。

    Demo4_ThreadGroup.java

    package com.wisedu.thread_communication;
    
    /**
     * Created by jkzhao on 1/24/18.
     */
    public class Demo4_ThreadGroup {
    
        /**
         * @param args
         * ThreadGroup
         */
        public static void main(String[] args) {
            //demo1();
            ThreadGroup tg = new ThreadGroup("我是一个新的线程组");		//创建新的线程组
            MyRunnable mr = new MyRunnable();						//创建Runnable的子类对象
    
            Thread t1 = new Thread(tg, mr, "张三");					//将线程t1放在组中
            Thread t2 = new Thread(tg, mr, "李四");					//将线程t2放在组中
    
            System.out.println(t1.getThreadGroup().getName());		//获取线程组的名字
            System.out.println(t2.getThreadGroup().getName());
    
            tg.setDaemon(true); //整个组内的线程都变成守护线程了
        }
    
        public static void demo1() {
            MyRunnable mr = new MyRunnable();
            Thread t1 = new Thread(mr, "张三");
            Thread t2 = new Thread(mr, "李四");
    
            ThreadGroup tg1 = t1.getThreadGroup();
            ThreadGroup tg2 = t2.getThreadGroup();
    
            System.out.println(tg1.getName());				//打印出来是main,线程默认的是在主线程组
            System.out.println(tg2.getName());
        }
    
    }
    
    class MyRunnable implements Runnable {
    
        @Override
        public void run() {
            for(int i = 0; i < 1000; i++) {
                System.out.println(Thread.currentThread().getName() + "...." + i);
            }
        }
    
    }
    

      

    十二、线程的六种状态

    其中,stop()方法太暴力,已过时了。在以前通过thread.stop()可以停止一个线程,注意stop()方法是可以由一个线程去停止另外一个线程,这种方法太过暴力而且是不安全的,怎么说呢,线程A调用线程B的stop方法去停止线程B,调用这个方法的时候线程A其实并不知道线程B执行的具体情况,这种突然间地停止会导致线程B的一些清理工作无法完成,还有一个情况是执行stop方法后线程B会马上释放锁,这有可能会引发数据不同步问题。基于以上这些问题,stop()方法被抛弃了。

    再来两张图:

    线程状态

    线程状态转换

    各种状态一目了然,值得一提的是"blocked"这个状态:
    线程在Running的过程中可能会遇到阻塞(Blocked)情况

    • 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
    • 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
    • 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。

    此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。

    十三、线程池的概述和使用

    1. 线程池概述

    程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK 5之前,我们必须手动实现自己的线程池,从JDK 5开始,Java内置了线程池。

    2. 内置线程池的使用概述

    JDK 5新增了一个Executors工厂类来产生线程池,有如下几个方法:

    • public static ExecutorService newFixedThreadPool(int nThreads)
    • public static ExecutorService newSingleThreadExecutor()  
    • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法:
      • Future<?> submit(Runnable task) //提交一个返回值的任务用于执行,返回一个表示任务的未决结果的Future
      • <T> Future<T> submit(Callable<T> task) //提交一个Runnable任务用于执行,并返回一个表示该任务的Future

    使用步骤:

    1. 创建线程池对象
    2. 创建Runnable实例
    3. 提交Runnable实例
    4. 关闭线程池

    【示例】:提交的是Runnable

    Demo5_Executors.java

    package com.wisedu.thread_communication;
    
    /**
     * Created by jkzhao on 1/24/18.
     */
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Demo5_Executors {
    
        /**
         * public static ExecutorService newFixedThreadPool(int nThreads)
         * public static ExecutorService newSingleThreadExecutor()
         */
        public static void main(String[] args) {
            ExecutorService pool = Executors.newFixedThreadPool(2);//创建线程池
            pool.submit(new MyRunnable());				//将线程放进池子里并执行
            pool.submit(new MyRunnable());
    
            pool.shutdown();							//关闭线程池,启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
                                                        // 如果不关闭池子,线程会不停的执行它里面的代码
        }
    
    }
    

      

    3. 多线程程序的实现方式三

    Callable

    future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态

    ExecutorService e = Executors.newFixedThreadPool(3);
     //submit方法有多重参数版本,及支持callable也能够支持runnable接口类型.
    Future future = e.submit(new myCallable());
    future.isDone() //return true,false 无阻塞。如果任务已完成,返回true
    future.get() // return 返回值,阻塞直到该线程运行结束

    Demo6_Callable.java

    package com.wisedu.thread_communication;
    
    /**
     * Created by jkzhao on 1/24/18.
     */
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class Demo6_Callable {
    
        /**
         * @param args
         * @throws ExecutionException
         * @throws InterruptedException
         */
        public static void main(String[] args) throws InterruptedException, ExecutionException {
            ExecutorService pool = Executors.newFixedThreadPool(2);//创建线程池
            Future<Integer> f1 = pool.submit(new MyCallable(100));				//将线程放进池子里并执行
            Future<Integer> f2 = pool.submit(new MyCallable(50));
    
            System.out.println(f1.get());
            System.out.println(f2.get());
    
            pool.shutdown();							//关闭线程池
        }
    
    }
    
    class MyCallable implements Callable<Integer> {
        private int num;
    
        public MyCallable(int num) {
            this.num = num;
        }
    
        @Override
        public Integer call() throws Exception { //call()计算结果,如果无法计算结果,则抛出一个异常。原来的run()是不可以抛异常的
            int sum = 0;
            for(int i = 1; i <= num; i++) {
                sum += i;
            }
    
            return sum;
        }
    
    }

    十四、设计模式之简单工厂模式

    1. 概述

    简单工厂模式又叫静态工厂方法模式,它定义了一个具体的工厂类负责创建一些类的实例

    • 优点:客户端不需要在负责对象的创建,由工厂来创建,从而明确了各个类的职责
    • 缺点:这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护。

    2. 案例

    • 动物抽象类
      • public abstract Animal { public abstract void eat(); }
    • 具体狗类
      • public class Dog extends Animal {}
    • 具体猫类
      • public class Cat extends Animal {}

    开始,在测试类中每个具体的内容自己创建对象,但是,创建对象的工作如果比较麻烦,就需要有人专门做这个事情,所以就制造了一个专门的类来创建对象。

    先来3个类:

    Animal.java

    package com.wisedu.staticFactory;
    
    /**
     * Created by jkzhao on 1/25/18.
     */
    public abstract class Animal {
        public abstract void eat();
    }
    

    Dog.java

    package com.wisedu.staticFactory;
    
    /**
     * Created by jkzhao on 1/25/18.
     */
    public class Dog extends Animal {
    
        @Override
        public void eat() {
            System.out.println("狗吃肉");
        }
    
    }

    Cat.java

    package com.wisedu.staticFactory;
    
    /**
     * Created by jkzhao on 1/25/18.
     */
    public class Cat extends Animal {
    
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
    
    }

    接下来得做一个动物工厂来生产动物,如果没有这个工厂,用到对象就得自己创建。和上面的线程池工厂一个道理。

    Animal.java

    package com.wisedu.staticFactory;
    
    /**
     * Created by jkzhao on 1/25/18.
     */
    public class AnimalFactory {
        public static Dog createDog() {
            return new Dog();
        }
    
        public static Cat createCat() {
            return new Cat();
        }
    }

    接下来写个测试类

    test.java

    package com.wisedu.staticFactory;
    
    /**
     * Created by jkzhao on 1/25/18.
     */
    public class Test {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            Dog d = AnimalFactory.createDog(); //不用自己创建了,用工厂来创建
            d.eat();
        }
    
    }

    但是我们发现这种方式不太好,工厂类里面对于创建Dog和Cat各写了一个方法,如果动物很多的话,得定义很多方法,复用性太差。可以改进下:

    public class AnimalFactory {
    	/*public static Dog createDog() {
    		return new Dog();
    	}
    
    	public static Cat createCat() {
    		return new Cat();
    	}*/
    
        //发现方法会定义很多,复用性太差
        //改进
        public static Animal createAnimal(String name) {
            if("dog".equals(name)) {
                return new Dog();
            }else if("cat".equals(name)) {
                return new Cat();
            }else {
                return null;
            }
        }
    }

    修改测试类的main方法,如下:

    public class Test {
    
        /**
         * @param args
         */
        /*public static void main(String[] args) {
            Dog d = AnimalFactory.createDog(); //不用自己创建了,用工厂来创建
            d.eat();
        }*/
    
        public static void main(String[] args) {
            //Dog d = AnimalFactory.createDog();
    
            Dog d = (Dog) AnimalFactory.createAnimal("dog");
            d.eat();
    
            Cat c = (Cat) AnimalFactory.createAnimal("cat");
            c.eat();
        }
    
    }
    

      

    十五、设计模式之工厂方法模式

    1. 概述

    工厂方法模式中的抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现。

    • 优点:客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不影响已有的代码,后期维护容易,增强了系统的扩展性。
    • 缺点:需要额外的编写代码,增加了工作量。有的人想要增加这么多代码,还不如我自己创建对象了。但是有的时候你自己创建不允许,就得交给工厂来创建,就是你们家不能生产钱,就得交给国家来生产。

    2. 案例

    先来3个类,Animal.java Dog.java Cat.java,这几个和上面的案例中的代码一样。

    接着定义一个工厂接口,Factory.java

    package com.wisedu.FactoryMethod;
    
    /**
     * Created by jkzhao on 1/25/18.
     */
    public interface Factory {
        public Animal createAnimal(); //多态的思想
    }

    接着编写猫工厂和狗工厂

    CatFactory.java

    package com.wisedu.FactoryMethod;
    
    /**
     * Created by jkzhao on 1/25/18.
     */
    public class CatFactory implements Factory {
    
        @Override
        public Animal createAnimal() {
            return new Cat();
        }
    
    }
    

    DogFactory.java

    package com.wisedu.FactoryMethod;
    
    /**
     * Created by jkzhao on 1/25/18.
     */
    public class DogFactory implements Factory {
    
        @Override
        public Animal createAnimal() {
            return new Dog();
        }
    
    }  

    再来编写测试类

    package com.wisedu.FactoryMethod;
    
    /**
     * Created by jkzhao on 1/25/18.
     */
    public class Test {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            DogFactory df = new DogFactory(); //先创建狗工厂
            Dog d = (Dog) df.createAnimal();
            d.eat();
        }
    
    }

    你会发现也很麻烦,都有优缺点。

  • 相关阅读:
    SQL CHECKOUT
    Adobe CS4 " Licensing for this product has expired " FIX!!!
    sizeof()用法汇总
    Command
    EXP_FULL_DATABASE,IMP_FULL_DATABASE,DBA,CONNECT,RESOURCE
    inet_addr函数处理IP地址需要注意的问题 (转)
    Oracle:外键关联导致数据无法删除
    三范式
    Wireshark界面上展开数据帧
    我的HTML学习记录(三)
  • 原文地址:https://www.cnblogs.com/zhaojiankai/p/8330344.html
Copyright © 2011-2022 走看看