学了忘忘了看之Java多线程

Java多线程

什么是线程?

线程是相对于进程而言的,通常在计算机中,一个程序就是一个进程,而一个进程中可以有一个或多个的进程来完成该程序相关的功能。
举个不是很恰当的例子:例如乐队表演是一个进程,那么主唱和鼓手和和声等都可以理解为一个线程,他们共同来完成演奏一首曲子的工作。

什么是线程的并行和并发?

并行:指的是在同一台机器上,多个CPU同时执行同一段功能代码来完成功能;
并发:指的是在同一台机器上,CPU利用调度算法,以超高的速度切换进程来执行同一段代码来完成功能(让人感觉和并行一样);

理解:(图片源自网络)

Java中创建线程的3中方法

方法1 - 继承Thread类

public class Main extends Thread{

    @Override
    public void run() {
        System.out.println(this.getName());
        super.run();
    }

    public static void main(String[] args) {

        System.out.println("主线程" + Thread.currentThread().getName());
        new Main().start();
        new Main().start();
        new Main().start();
        /**
         * 输出结果:
             *    主线程main
         *  Thread-0
         *  Thread-1
         *  Thread-2
         */
    }
}

方法2 - 实现Runnable接口

public class Main implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args) {

        System.out.println("主线程" + Thread.currentThread().getName());
        Main main = new Main();
        new Thread(main).start();
        new Thread(main).start();
        new Thread(main).start();

        /**
         * 输出结果:
         *  Thread-0
         *  Thread-1
         *  Thread-2
         */
    }

}

方法3 - 实现Callable接口(jdk 1.5)

public class Main implements Callable<String>{

    /**
     * 可以抛异常
     * 可以有返回值
     */
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName());
        return null;
    }

    public static void main(String[] args) throws Exception {

        System.out.println("主线程" + Thread.currentThread().getName());
        Main main = new Main();
         // 使用FutureTask来包装Callable对象
        new Thread(new FutureTask<>(main), "线程0").start();
        new Thread(new FutureTask<>(main), "线程1").start();
        new Thread(new FutureTask<>(main)).start();

        /**
         * 输出结果:
         *  主线程main
         *  线程0
         *  线程1
         *  Thread-0
         */

    }

}

总结:

推荐使用Runnable或者Callable,因为在Java中,类只能实现单继承,所以如果使用继承Thread来实现多线程的话,并不利于该类的扩展。

线程的状态

新建:刚new出来的Thread对象;
就绪:第一种情况是刚调用start()方法的线程,第二种情况就是阻塞完后的线程,第三种情况主动调用yield()方法;
运行:获取到CPU执行权的线程,处于工作状态;
阻塞:
    1. 等待阻塞 -- 运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
    2. 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
    3. 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态;
死亡:执行完run方法之后任务结束了或者因异常终止退出了run()方法;

图解:(图片源自网络)

终止线程的方法

方法1:使用退出标志,使线程正常退出;
方法2:使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能导致死锁发生);
方法3:使用interrupt方法中断线程;

方法1 -- 标记退出

public class Main extends Thread{

    volatile boolean flag = false;

    @Override
    public void run() {

        while( !flag ) { System.out.println("::"); }
        System.out.println("线程结束!!!");

    }

    public static void main(String[] args) throws Exception {
        Main a = new Main();
        a.start();
        //终止操作
        a.flag = true;
        //a.join(); //多个线程的使用使用join来抢占资源关闭
    }

}

方法2 -- stop方法退出

thread.stop(); //不推荐使用

方法3 -- interrupt方法退出

使用该方法终止线程需要符合以下其中一种状态:
1. 终止线程处于阻塞状态,如sleep(),在该状态下可以使用interrupt()方法终止线程,且在终止的时候sleep会抛出一个InterruptedException异常(注意:如果线程的阻塞是I/O阻塞,需要先close(),后interrupt()才生效,否则不生效)
2. 使用isInterrupted()方法检查

interrupt()方法:

public class Main extends Thread{

    volatile boolean flag = false;

    @Override
    public void run() {

        try {
            System.in.read(); //  I/O阻塞:当通道还没关闭时,interrupt()不生效,解决方法是关闭通道close()
//          sleep(500000); // sleep阻塞:interrupt()生效
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return;
        }
        while( true ) {System.out.println("::");}
    }

    public static void main(String[] args) throws Exception {
        Main a = new Main();
        a.start();
        a.interrupt();
        a.join();
        System.out.println("线程已经退出!");  

    }

}

isInterrupted()方法:(结合interrupt()方法使用)

public class Main extends Thread{

    volatile boolean flag = false;

    @Override
    public void run() {

        while( true ) {
            System.out.println("::");
            if( Thread.currentThread().isInterrupted() ) {
                System.out.println(Thread.currentThread().getName() + "退出");
                return;
            }
        }

    }

    public static void main(String[] args) throws Exception {
        Main a = new Main();
        a.start();
        a.interrupt();
//        a.join();  

    }

}

总结:

interrupt()和isInterrupted()实质底层调用的都是同一个方法,该方法是一个本地方法(native修饰)
源码如下:
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
    return isInterrupted(false);
}
//本地方法
private native boolean isInterrupted(boolean ClearInterrupted);

线程的yield()和join()方法

方法说明:

void join():先让该线程执行,直到终止;
void join(long millis):先让该线程执行,指定可以执行的时间;
static void yield():暂停当前正在执行的线程对象,并执行其他线程;

join()方法例子:

public class Main extends Thread{
    private String name;
    public Main(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        synchronized(this) {
            for(int i=0; i<=20; i++) {
                System.out.println( name + "正在执行任务..." + i );
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Main main1 = new Main("线程1");
        Main main2 = new Main("线程2");
        Main main3 = new Main("线程3");

        main1.start();
        main1.join(); //当main1.join();不管运行多少次都是main1先完成任务其他线程才能工作
        main2.start();
        main3.start();
    }
}

yield()方法例子:

public class Main extends Thread{
    private String name;
    public Main(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        synchronized(this) {
            for(int i=0; i<=20; i++) {
                System.out.println( name + "正在执行任务..." + i );
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Main main1 = new Main("线程1");
        Main main2 = new Main("线程2");
        Main main3 = new Main("线程3");

        main1.start();
        main1.yield(); //礼让,注意:yield()后不一定该线程就不会执行,只是在调用的时候让步一下
        main2.start();
        main3.start();
    }
}

线程的优先级

线程的优先级,从字面上就很好理解,就是线程执行的优先权,权力越大,执行的频度就越高,但还是要注意的是,这只是理论的情况,再次强调CPU抢占式任务调度。

什么是抢占式和非抢占式:http://blog.csdn.net/u013176681/article/details/39256191

以下是Thread类为线程提供的3个优先级别:

public static final int MAX_PRIORITY        : 值为10
public static final int MIN_PRIORITY        : 值为1
public static final int NORM_PRIORITY       : 值为5 

使用例子:

public class Main extends Thread{
    private String name;
    public Main(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        synchronized(this) {
            for(int i=0; i<=20; i++) {
                System.out.println( name + "正在执行任务..." + i );
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Main main1 = new Main("线程1");
        Main main2 = new Main("线程2");
        Main main3 = new Main("线程3");

        //那么理论上,执行的优先级是 main1 > main2 >main3 当然这只是理论,但实际执行不一定
        main1.start();
        main1.setPriority(Thread.MAX_PRIORITY);
        main2.start();
        main2.setPriority(Thread.NORM_PRIORITY);
        main3.start();
        main1.setPriority(Thread.MIN_PRIORITY);
    }
}

线程同步

什么是线程同步?

线程同步,指的是在多个线程同时操作数据的时候能够保持数据的一致性和准确性;
通常线程同步问题会出现在非原子操作中,如 i++ , --i 这种非原子操作,线程如果不同步那么操作的数据就会不一致导致计算结果出错;
注意:
1. 使用锁,锁锁住的是一个对象而不是代码块;
2. 当synchronized中抛出异常,锁会被释放(当线程抛出异常,一般要做的事就是对事务的回滚);

解决线程同步的方法

方法1:使用synchronized方法

public class Main implements Runnable{

    private int i = 2000;

    //同步方法:这是锁是this
    public synchronized void show() {
        while( true ) {
            try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
            if( i<=0 ) { //如果小于等于0就退出
                return ;
            }
            System.out.println(Thread.currentThread().getName() + "::" +  i-- );
        }
    }

    @Override
    public void run() {
            show();
    }

    public static void main(String[] args) throws Exception {

        Main main = new Main();
        Thread a = new Thread(main);
        Thread b = new Thread(main);
        Thread c = new Thread(main);
        Thread d = new Thread(main);

        a.start();
        b.start();
        b.setPriority(Thread.MAX_PRIORITY);
        c.start();
        c.setPriority(Thread.NORM_PRIORITY);
        d.start();
        d.setPriority(Thread.MAX_PRIORITY);

    }

}

方法2:使用synchronized代码块

public class Main implements Runnable{

    private int i = 2000;

    public void show() {
        while( true ) {
            try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
            if( i<=0 ) { //如果小于等于0就退出
                return ;
            }
            System.out.println(Thread.currentThread().getName() + "::" +  i-- );
        }
    }

    //同步代码块
    @Override
    public void run() {
           //锁是字节码文件对象
        synchronized( Main.class ) {
            show();
        }
    }

    public static void main(String[] args) throws Exception {

        Main main = new Main();
        Thread a = new Thread(main);
        Thread b = new Thread(main);
        Thread c = new Thread(main);
        Thread d = new Thread(main);

        a.start();
        b.start();
        b.setPriority(Thread.MAX_PRIORITY);
        c.start();
        c.setPriority(Thread.NORM_PRIORITY);
        d.start();
        d.setPriority(Thread.MAX_PRIORITY);

    }

}

方法3:显示锁ReentrantLock - JDK 1.5

public class Main implements Runnable {

    //显示锁
    static final Lock lock = new ReentrantLock();

    private int i = 200;

    public void show() {

        while( true ) {
            //注意:上锁和解锁要在while内 否则出现IllegalMonitorStateException异常
            lock.lock(); //上锁
            try {
                Thread.sleep(50);
                if (i <= 0) return;
                System.out.println( Thread.currentThread().getName() + "::" + i--);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); //解锁
            }
        }

    }

    @Override
    public void run() {
        show();
    }

    public static void main(String[] args) throws Exception {

        Main main1 = new Main();
        Thread a = new Thread(main1);
        Thread b = new Thread(main1);
        Thread c = new Thread(main1);

        // 开启线程
        a.start();
        b.start();
        c.start();

    }

}

synchronized和ReentrantLock区别

这两种方式最大区别就是对于synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现;而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成 -- 来自CSDN博主 记忆力不好

注意:新版本 JVM对synchronized进行了内部的优化,所以推荐使用synchronized(减少获取锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁)可以参考:http://blog.csdn.net/wodewutai17quiet/article/details/78187386

类锁和对象锁

类锁:当使用synchronized修饰静态方法时或者使用 Class对象锁的时候,就会使用类锁,因为静态的方法中是没有this引用的,那么这时锁定的就是一个类;
对象锁:当使用synchronized修饰非静态方法或者使用synchronized(非class对象)代码块时,就会使用对象锁,该锁对当前对象生效;

类锁例子:

public class Main implements Runnable{

    private static int i = 20; 

    //类锁锁定
    public static synchronized void show() {

        /* 等同于synchronized(Main.class){....} */

        while( true ) {
            try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
            if( i<=0 ) { //如果小于等于0就退出
                return ;
            }
            System.out.println(Thread.currentThread().getName() + "::" +  i-- );
        }
    }

    @Override
    public void run() {
            show();
    }

    public static void main(String[] args) throws Exception {

        Main main1 = new Main();
        Main main2 = new Main();
        Main main3 = new Main();
        Thread a = new Thread(main1);
        Thread b = new Thread(main2);
        Thread c = new Thread(main3);

        //开启线程
        a.start();
        b.start();
        b.setPriority(Thread.MAX_PRIORITY);
        c.start();
        c.setPriority(Thread.NORM_PRIORITY);

    }

}

对象锁例子:

public class Main implements Runnable{

    private int i = 20;

    //对象锁锁定
    public synchronized void show() {
        while( true ) {
            try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
            if( i<=0 ) { //如果小于等于0就退出
                return ;
            }
            System.out.println(Thread.currentThread().getName() + "::" +  i-- );
        }
    }

    @Override
    public void run() {
            show();
    }

    public static void main(String[] args) throws Exception {

        Main main1 = new Main();
        Thread a = new Thread(main1);
        Thread b = new Thread(main1);
        Thread c = new Thread(main1);

        //开启线程
        a.start();
        b.start();
        b.setPriority(Thread.MAX_PRIORITY);
        c.start();
        c.setPriority(Thread.NORM_PRIORITY);

    }

}

synchronized和volatile

synchronized:既能保证共享资源 线程的可见性 又可以保证共享资源的 原子性;
volatile:只能保证共享资源对现成的可见性,不能保证原子性,即volatile不能修饰 i++ 这种非原子性操作(当线程取volatile修饰的变量的时候,会从共享内存中取最新的数据);

区别:
volatile不需要加锁,比synchronized更加轻量,效率更高,原因是不会导致线程的阻塞,但并不能保证操作的原子性。

死锁

什么是死锁?

其实死锁指的是一种现象;即线程之间锁的相互等待,导致线程停止工作;

死锁例子

public class Main2 {

    public static void main(String[] args) {

        DeadLock lock = new DeadLock();
        T1 t1 = new T1(lock); //m1方法
        T2 t2 = new T2(lock); //m2方法
        new Thread( t1 ).start();
        new Thread( t2 ).start();

    }

}

class T1 implements Runnable{
    private DeadLock lock;
    public T1(DeadLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
            lock.m1();
    }

}

class T2 implements Runnable{
    private DeadLock lock;
    public T2(DeadLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
            lock.m2();
    }

}

class DeadLock {

    //锁
    private static Object o1 = new Object();
    private static Object o2 = new Object();

    public void m1() {
        synchronized(o1) {
            System.out.println("o1");
            synchronized(o2) {
                System.out.println("o2");
            }
        }
    }

    public void m2() {
        synchronized(o2) {
            System.out.println("o2");
            synchronized(o1) {
                System.out.println("o1");
            }
        }
    }
}

后台线程

什么是后台线程?

后台线程也称作精灵线程,是一种为前台进程提供服务的进程,后台线程并不是不可或缺的线程,即实质可有可无。
举个通俗而不恰当的例子,就好比古装电视剧,皇上出巡,身边的侍卫一样,这里皇上就是主进程,侍卫就是后台进程,只要环境是安全的,就算士兵死不在也是没有问题的,因为并不是必要的;
注意:后台线程只在前台线程存在的时候才存在,即如果前台线程都结束了,后台线程就会停止

后台进程的基本使用

public class Main extends Thread{

    @Override
    public void run() {
        for(int i=0; i<10; i++) {
            try { Thread.sleep( 200 ); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println( "我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: " +  i);
        }
    }

    public static void main(String[] args) {
        //精灵线程
        Deamon deamon = new Deamon();
        Thread deamonThread = new Thread( deamon );
        deamonThread.setDaemon( true ); //精灵线程必须在线程开始之前设置
        deamonThread.start();

        //主线程
        Main main = new Main();
        Thread mainThread = new Thread( main );
        mainThread.start();
    }
}

//精灵进程
class Deamon implements Runnable{

    @Override
    public void run() {
        while(true) {
            try { Thread.sleep( 200 ); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("我是精灵线程,我为大佬呐喊 666!!!");
        }
    }

}

输出结果:(前台进程结束,精灵线程就会被JVM停止)
    我是精灵线程,我为大佬呐喊 666!!!
    我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 0
    我是精灵线程,我为大佬呐喊 666!!!
    我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 1
    我是精灵线程,我为大佬呐喊 666!!!
    我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 2
    我是精灵线程,我为大佬呐喊 666!!!
    我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 3
    我是精灵线程,我为大佬呐喊 666!!!
    我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 4
    我是精灵线程,我为大佬呐喊 666!!!
    我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 5
    我是精灵线程,我为大佬呐喊 666!!!
    我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 6
    我是精灵线程,我为大佬呐喊 666!!!
    我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 7
    我是精灵线程,我为大佬呐喊 666!!!
    我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 8
    我是精灵线程,我为大佬呐喊 666!!!
    我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 9

线程的异常捕获

步骤1:实现UncaughtExceptionHandler线程异常处理器接口
步骤2:调用thread.setUncaughtExceptionHandler( 异常处理器实例对象 );

例子:

public class Main extends Thread{

    @Override
    public void run() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        Main main = new Main();
        Thread mainThread = new Thread( main );
        mainThread.setUncaughtExceptionHandler( new myUncaughtExceptionHandler() );
        mainThread.start();
    }
}

//线程异常处理器
class myUncaughtExceptionHandler implements UncaughtExceptionHandler{

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("出现异常的线程 :: " + t.getName() );
        System.out.println("出现的异常为 :: " + e );
    }

}

线程通信

什么是线程通信?

可以简单理解为线程与线程之间的协作。即什么是线程的通信,就是线程之间是如何协作玩完成任务的;

线程通信常用的方法

第一组来自于Object类(推荐):
    void notify():唤醒正在等待的一个线程,注意:不一定是等待最久的哪一个(CPU的抢占式)
    void notifyAll():唤醒所有等待的线程
    void wait():让线程进入阻塞状态(等待阻塞)
    void wait(long timeout):让线程进入阻塞状态(等待阻塞),时间为timeout,超时后就会进入就绪状态

第二组来自于Condition类:
    void await():相当于wait()
    void signal():相当于notify()
    void signalAll():相当于notifyAll()

线程通信例子 -- 生产者与消费者

//商品Computer
class Computer {
    private int count = 0; //电脑的台数
    private boolean flag = true; //是否生产标记

    //生成方法
    public synchronized void add() { 

        while(!flag) { //生产完进入等待
            try {  this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
        }

        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        ++count;
        System.out.println(Thread.currentThread().getName() + "生产者在生产::" + count  + "++++++++++++++++++++++++");

        flag = false; //生产完
        this.notifyAll(); //唤醒等待的消费者

    }

    //消费方法
    public synchronized void remove() {

        while(flag) { //消费完进入消费
            try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
        }

        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "消费者在消费::" + --count + "---");

        flag = true; //消费完
         this.notifyAll(); //唤醒等待的生产者

    }
}

//生产者Producer
class  Producer implements Runnable {
    private Computer com;
    public Producer(Computer com) { this.com = com; }

    @Override
    public void run() {
        while(true)
            com.add();
    }

}

//消费产者Consumer
class  Consumer implements Runnable {
    private Computer com;
    public Consumer(Computer com) { this.com = com; }

    @Override
    public void run() {
        while(true)
            com.remove();
    }

}

//测试类
public class Main {

    public static void main(String[] args) throws Exception {
        Computer com = new Computer();
        Producer producer = new Producer(com);
        Consumer consumer = new Consumer(com);

        //3个线程生产
        new Thread(producer).start();
        new Thread(producer).start();
        new Thread(producer).start();
        //5个线程消费
        new Thread(consumer).start();
        new Thread(consumer).start();
        new Thread(consumer).start();
        new Thread(consumer).start();
        new Thread(consumer).start();

    }
}

以上是一种比较低级的解决方式,但是又是最基础。如果在真正解决生产和消费这种问题的时候,可以常识使用BlockingQueue接口,该接口的实现例如:

  1. ArrayBlockingQueue:有界队列,固定大小,填满后会阻塞,如果当该队列为空时,消费群获取,那么队列就会挂起消费者;
  2. LinkedBlockingQueue:无界队列;

使用例子:

//商品Computer
class Computer {
    int count = 1;
    BlockingQueue<Integer> bq = null;
    public Computer(BlockingQueue<Integer> bq) {
        this.bq = bq;
    }

    //生成方法
    public void add() {
        try {
            Thread.sleep(300);
            bq.put( count++ );
        } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "生产者才生产:::"+ count + "+++");
    }

    //消费方法
    public void remove() {
        Integer take = 0;
        try {
            Thread.sleep(300);
            take = bq.take();
        } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "消费者在消费:::"+ take + "-----------------------");
    }
}

//生产者Producer
class  Producer implements Runnable {
    private volatile boolean flag = true; //默认运行
    private Computer com;
    public Producer(Computer com) { this.com = com; }

    @Override
    public void run() {
        while(flag)
            com.add();
    }

    //停止生产方法
    public void stop() {
        this.flag = false;
    }

}

//消费产者Consumer
class  Consumer implements Runnable {
    private Computer com;
    public Consumer(Computer com) { this.com = com; }

    @Override
    public void run() {
        while(true)
            com.remove();
    }

}

//测试类
public class Main {

    public static void main(String[] args) throws Exception {
        BlockingQueue<Integer> bq = new ArrayBlockingQueue<>(1); //队列的容量,0开始
        Computer com = new Computer(bq);
        Producer producer = new Producer(com);
        Consumer consumer = new Consumer(com);

        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); //用多少创建多少
        //3个线程生产
        newCachedThreadPool.execute(producer);
        newCachedThreadPool.execute(producer);
        newCachedThreadPool.execute(producer);
        //5个线程消费
        newCachedThreadPool.execute(consumer);
        newCachedThreadPool.execute(consumer);
        newCachedThreadPool.execute(consumer);
        newCachedThreadPool.execute(consumer);
        newCachedThreadPool.execute(consumer);
//      producer.stop();
        newCachedThreadPool.shutdown(); //关闭线程池
    }
}

线程池

什么是线程池?

线程池,可以理解为一个用于存放线程的地方。
用到线程的时候就到线程池中取,所以并不需要自己手动创建,而线程完成任务后会自动返回线程池,等待下一个任务的到来(也有存活被设置闲置时间的线程,即线程闲置一定时间自动关闭)。
需要知道的是创建线程是消耗系统资源的,那么当线程并发很高的时候,就会使系统性能急剧下降,甚至崩溃。
所以线程池就解决了这一点,线程一开始就会创建好指定数量的线程,存放在池中供任务使用,线程池对线程的创建数量有所限制,因此并不会出现以上的情况;

Java自带的线程池

基本队列知识:

LinkedBlockingQueue:阻塞式无界队列
SynchronousQueue:阻塞式同步队列,例如,生产者消费者模式在该队列中,生产者生成了一样商品必须被消费掉才能继续生成

以下为Executors类提供的线程池创建方法:

//创建缓存线程池,每execute()或者submit()都会创建新线程,线程数超过核心线程数时,空闲的线程超时60秒就失效,基于SynchronousQueue
static ExecutorService newCachedThreadPool()
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

//创建固定线程数的线程池,线程空闲时间没有限制;基于LinkedBlockingQueue
static ExecutorService newFixedThreadPool(int nThreads)
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 

//可以理解为newFixedThreadPool(1)的线程池,特点是按照任务加入执行的顺序执行任务,基于LinkedBlockingQueue
static ExecutorService newSingleThreadExecutor()
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) 

//创建任务线程数的线程池,类似timer
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) 

//创建工作窃取的线程池,JDK 1.8中新加入的,基于ForkJoinPool,创建的线程都是精灵线程
static ExecutorService newWorkStealingPool()
static ExecutorService newWorkStealingPool(int parallelism)

还在研究,待续。。。

fork/join任务拆分的基本使用

简单说一下,所谓的任务才分就是将一个任务拆分成多个任务,即原本这个任务是交给一个线程来完成的,但这线程却使用了ForkJoinPool,那么就可以将任务拆分给其他线程来去共同完成这个任务;

以下为马老师的一段代码,看一下就明白了

import java.io.IOException;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class T12_ForkJoinPool {
    static int[] nums = new int[1000000];
    static final int MAX_NUM = 50000; //一段的数量
    static Random r = new Random();

    static {
        for(int i=0; i<nums.length; i++) {
            nums[i] = r.nextInt(100);
        }

        System.out.println("正确结果::" + Arrays.stream(nums).sum()); //stream api
    }

    /*
    //RecursiveAction无返回值
    static class AddTask extends RecursiveAction { 

        int start, end;

        AddTask(int s, int e) {
            start = s;
            end = e;
        }

        @Override
        protected void compute() {

            if(end-start <= MAX_NUM) {
                long sum = 0L;
                for(int i=start; i<end; i++) sum += nums[i];
                System.out.println("from:" + start + " to:" + end + " = " + sum);
            } else {

                int middle = start + (end-start)/2;

                AddTask subTask1 = new AddTask(start, middle);
                AddTask subTask2 = new AddTask(middle, end);
                subTask1.fork();
                subTask2.fork();
            }

        }

    }
    */

    //RecursiveTask有返回值
    static class AddTask extends RecursiveTask<Long> { 

        int start, end;

        AddTask(int s, int e) {
            start = s;
            end = e;
        }

        @Override
        protected Long compute() {

            //MAX_NUM=50000
            if(end-start <= MAX_NUM) {
                long sum = 0L;
                for(int i=start; i<end; i++) sum += nums[i];
                return sum;
            } 

            int middle = start + (end-start)/2;

            AddTask subTask1 = new AddTask(start, middle);
            AddTask subTask2 = new AddTask(middle, end);

            //拆分线程来完成计算
            subTask1.fork();
            subTask2.fork();

            return subTask1.join() + subTask2.join();
        }

    }

    //测试
    public static void main(String[] args) throws IOException {
        //1.创建ForkJoinPool
        ForkJoinPool fjp = new ForkJoinPool();

        //2.创建计算任务
        AddTask task = new AddTask(0, nums.length); // 初始化时: 0 - 1000000

        //3.利用ForkJoinPool执行任务
        fjp.execute(task);
        long result = task.join();//因为当前AddTask实现与RecursiveTask,所以可以join返回整合结果

        System.out.println(result);

        //System.in.read();

    }
}
时间: 2024-10-01 00:28:48

学了忘忘了看之Java多线程的相关文章

Java多线程——&lt;一&gt;概述、定义任务

一.概述 为什么使用线程?从c开始,任何一门高级语言的默认执行顺序是“按照编写的代码的顺序执行”,日常开发过程中写的业务逻辑,但凡不涉及并发的,都是让一个任务顺序执行以确保得到想要的结果.但是,当你的任务需要处理的业务比较多时,且这些业务前后之间没有依赖(比如, a执行的过程中b也可以执行,b没有必要必须等待a执行完毕再去执行),那么此时,我们可以将一个任务拆分成多个小任务. 例如,任务a负责接收键盘的输入,b负责将一些参数及计算提前做好(假设计算量比较大),c负责将a的输入和b的结果做和.此时

设计模式看了又忘,忘了又看?

文章首发:设计模式看了又忘,忘了又看? 设计模式收藏这篇就够了 耗时了 5 个月,终于把设计模式一整个系列写完.其实设计模式这一系列文章网上已经有很多非常好.非常优秀的文章,为什么要写呢? 一方面是为了学得更扎实,印象中设计模式学习了 2 遍,记得牢的基本就那几个众所周知的,反思前面 2 次学习过程,缺少了思考的过程,没有把知识消化掉转化成自己的,就像动物一样,吃进去的东西没有消化只能排出. 另一方面是利用这个学习过程,学会把知识用文字表达出来,也把这份知识分享给各位同道中人. 没有期望说这系列

Android开发者:我学了就忘怎么办?

我们在学习的时候,多多少少都会遇到这么一个问题: "学了就忘" 之前也聊过这个问题,在我看来,这个问题是个共性问题,尤其初学者,更是不知所措,倍受打击,那么,为啥会学了就忘呢? 我根据自己的学习经验,大致总结以下三点原因: 知识没有用起来 没有及时复习 没有反馈输出 当然,导致我们学了就忘的原因不止如此,而且根据每个人的学习状况不同,可能远远不止如此,但是正如这个问题是个共性问题一样,我们在这里只说普遍性原因,可能很多人会因为出现上面三个原因导致学习效果不理想. 在此之前,你可能听过别

学计算机的如果你有耐心看下去,我敢保证这绝对是一种收获

大师提醒: 计算机专业不是学编程,而是懂得计算机的工作原理,以及和计算机相关的学科技术.一个高手不必懂得编程,coder是最底层的人物,最重要的是思想,解决问题的思想.对计算机专业的学生来说,英语和数学也是最重要的.编程不用于实际,编得最好也是花哨,毫无意义.对于初学者来说,应该懂得更多的why 原理,而不是更多的how,那是工作后的事情. 每一计算机学者都应该有自己的思想,不要跟着别人走,不要看着别人用什么新技术作出什么来的时候,你就心动了,也就想马上学这门技术.而是有自己的学习思路,和自己学

Rhythmk 一步一步学 JAVA (21) JAVA 多线程

1.JAVA多线程简单示例 1.1 .Thread  集成接口 Runnable 1.2 .线程状态,可以通过  Thread.getState()获取线程状态: New (新创建) Runnable (可以运行) Blocked  (被阻塞) Waiting  (等待) Timed waiting (计时等待) Terminated  (被终止) ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

《写给大忙人看的java se 8》笔记

现在才来了解java8,是不是后知后觉了点? 新的编程技术,个人不喜欢第一时间跟进. 待社区已有实践积淀再切入似乎更划算些? 一点点精明的考虑. 不多说,上代码. //读<写给大忙人看的java se 8>做的笔记代码 //希望对忙到连这书都没工夫看的你,匆匆一瞥,留下印象 //祝编程愉快 public class MainTest { //第一章,讲lambda表达式 //lambda表达式类似javascript的函数字面量,可用于替代java的匿名内部类 //基本型为 (形参列表)-&g

面试题: java多线程 !=!=未看

如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其实只有一半对,因为反应"多角色"的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的"生产者,消费者模型". 很多人都对其中的一些概念不够明确,如同步.并发等等,让我们先建立一个数据字典,以免产生误会. 多线程

一文看懂java io系统 (转)

出处:  一文看懂java io系统 学习java IO系统,重点是学会IO模型,了解了各种IO模型之后就可以更好的理解java IO Java IO 是一套Java用来读写数据(输入和输出)的API.大部分程序都要处理一些输入,并由输入产生一些输出.Java为此提供了java.io包 java中io系统可以分为Bio,Nio,Aio三种io模型 关于Bio,我们需要知道什么是同步阻塞IO模型,Bio操作的对象:流,以及如何使用Bio进行网络编程,使用Bio进行网络编程的问题 关于Nio,我们需

JAVA读书推荐----《深入分析Java Web技术内幕》--《java多线程编程核心技术》--《大型网站技术架构 核心原理与案例分析》-《Effective Java中文版》

(1)  首先推荐的不是一本书,而是一个博客,也是我们博客园另外一位博友java_my_life. 目前市面上讲解设计模式的书很多,虽然我前面讲了看书是最好的,但是对设计模式感兴趣的朋友们,我推荐的是这个博客.这位博友的设计模式讲得非常非常好,我认为90%的内容都是没有问题且很值得学习的,其讲解设计模式的大体路线是: 1.随便开篇点明该设计模式的定义 2.图文并茂讲解该设计模式中的结构 3.以详细的代码形式写一下该种设计模式的实现 4.补充内容 5.讲解该设计模式的优缺点 对于一个设计模式我们关