Java多线程

本节内容:html

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

 

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

 

1、什么是线程

1. 线程是什么程序员

线程是程序执行的一条路径,一个进程中能够包含多条线程,那么这个进程就是多线程的。面试

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

多线程并发执行能够提升程序的效率,能够同时完成多项工做。设计模式

 

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

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

 

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

 

2. 多线程的应用场景并发

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

 

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

  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行(须要多核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("垃圾被清扫了");
    }

}

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

 

3、多线程程序实现方式

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,开启线程
    }

}

 

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()

待补充。。。

  

5、多线程之线程同步

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");
        }
    }
}

  

6、线程安全

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

使用同步技术能够解决这种问题,把操做数据的代码进行同步,不要多个线程一块儿操做。

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

  

7、多线程的死锁

多线程同步的时候,若是代码嵌套,使用相同锁,就有可能出现死锁。因此尽可能不要嵌套使用。

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

【示例】: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...获取筷子右等待筷子左

此时已经产生死锁了,程序并无中止。。。

 

8、线程安全的类

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)

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

 

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

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

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

  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("起床背英语单词");
    }
}

  

10、线程通讯

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();
    }
}

  

11、线程组的概述和使用

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);
        }
    }

}

  

12、线程的六种状态

其中,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。

 

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

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;
    }

}

 

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

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();
    }

}

  

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

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();
    }

}

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

相关文章
相关标签/搜索