Fork me on GitHub

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("\r\n");
        }
    }

    public void print2() {
        //synchronized(new Demo()) {							//锁对象不能用匿名对象,因为匿名对象不是同一个对象
        synchronized(d) {
            System.out.print("传");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
        }
    }
}

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("\r\n");
    }

    public void print2() {
        synchronized(this) {
            System.out.print("传");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
        }
    }

    //静态的同步方法的锁对象是什么?
    //答:是该类的字节码对象
    public static synchronized void print3() {		//同步方法只需要在方法上加synchronized关键字即可
        System.out.print("黑");
        System.out.print("马");
        System.out.print("程");
        System.out.print("序");
        System.out.print("员");
        System.out.print("\r\n");
    }

    public static void print4() {
        synchronized(Printer2.class) {
            System.out.print("传");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
        }
    }
}

  

六、线程安全

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

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

【示例】:铁路售票,一共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("\r\n");
            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("\r\n");
            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("\r\n");
            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("\r\n");
            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("\r\n");
            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("\r\n");
        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("\r\n");
        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("\r\n");
        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();
    }

}

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

 

posted @ 2018-03-17 18:13  暴走小骚年  阅读(302)  评论(0编辑  收藏  举报