阅读 164

Java 多线程开发

目录

[toc]


进程和线程

进程

概念

  • 进程是指处于运行过程中的程序,是系统进行资源分配的一个调度单位。当程序进入内存运行时,即为进程。
  • 在java中一个进程至少有一个线程:主线程。 其实还有一个GC线程。

进程的三个特点:

独立性

  • 进程是系统中独立存在的实体,它可以独立拥有资源,每一个进程都有自己独立的地址空间,没有进程本身的运行,用户进程不可以直接访问其他进程的地址空间。

动态性

  • 进程和程序(写好的代码,不会动的)的区别在于进程是动态(在内存中执行起来了)的,进程中有时间的概念,进程具有自己的生命周期和各种不同的状态。

并发性

  • 多个进程可以在单个处理器上并发执行,互不影响。
并发和并行和串行

首先并发和并行是不同的概念。

  • 并发:时间段内有很多的线程或进程在执行,但何时间点上都只有一个在执行,多个线程或进程争抢时间片轮流执行。
  • 并行:时间段和时间点上都有多个线程或进程在执行。
  • 串行是指一个一个的执行,处理完一个才能处理下一个,不轮换;

备注

  • 单核cpu的话只能是并发,多核cpu才能做到并行执行。
  • 时间片:时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,

进程的三个状态

就绪(Ready)状态

当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。在一个系统中处于就绪状态的进程可能有多个,通常将他们排成一个队列,称为就绪队列。

执行(Running)状态

当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。

阻塞(Blocked)状态

正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。

线程

概念

  • 线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程,一个线程也至少有一个线程。线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不能拥有系统资源。它与父进程的其他线程共享该进程的所有资源。

ps:程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

线程的特点

  • 线程可以完成一定任务,可以和其它线程共享父进程的共享变量和部分环境,相互协作来完成任务。
  • 线程是独立运行的,其不知道进程中是否还有其他线程存在。
  • 线程的执行是抢占式的,也就是说,当前执行的线程随时可能被挂起,以便运行另一个线程。
  • 一个线程可以创建或撤销另一个线程,一个进程中的多个线程可以并发执行。

线程的5种状态

新建状态(New)

  • 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。

就绪状态(Runnable)

  • 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

运行状态(Running)

  • 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。

阻塞状态(Blocked)

  • 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分以下三种:
三种阻塞情况
  • 等待阻塞 通过调用线程的wait()方法,让线程等待某工作的完成。
  • 同步阻塞 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
  • 其他阻塞 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead)

  • 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程的创建及使用

三种创建方式

继承Thread
  • 创建一个类继承Thread重写run方法
实现Runnable接口
  • 创建一个类实现Runnable接口
实现Callable接口
  • 创建一个类实现Callable接口,这种方式在java1.5开始提供,是一个有返回值的线程
代码示例
public class MyTest {

    public static void main(String[] args) {

        //方式一启动线程
        MyThread t1 = new MyThread();
        t1.start();

        //方式二启动线程
        MyRunnable myRunnable = new MyRunnable();
        Thread t2 = new Thread(myRunnable,"线程2");
        t2.start();

        //方式三启动线程
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(myCallable);
        Thread t3 =new Thread(futureTask,"线程3");
        t3.start();
        //获取线程返回值
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        //结论:采取Runnable、Callable的优势在于——线程类只是实现了Runnable或Callable接口,
        // 还可以继承其它类;在这种方法下,多个线程可以共享一个target对象,因此非常适合多个
        // 相同线程处理同一份资源的情况,从而将CPU、代码和数据分开,形参清晰的模型,体现了面
        // 对对象的编程思想。劣势在于编程复杂度略高。


    }

}


/**
 * 方式一创建线程,继承Thread类
 */
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-" + i);
            try {

                //线程的休眠,使用Thread.sleep(ss);, 单线程进行休眠后,会释放CPU时间片,也就是,把CPU分配给进程的执行时间,让给本线程所在的进程的其他线程去抢占执行。

                Thread.sleep(2*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 方式二创建线程,实现Runnable接口
 */
class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-" + i);
            try {
                Thread.sleep(1*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 方式三创建线程,实现Callable接口,有返回值的线程,从java1.5开始提供
 */
class MyCallable implements Callable<Integer> {

    private int t = 0;

    @Override
    public Integer call() throws Exception {
        for (int i = 0; i < 10; i++) {
            t=i;
            System.out.println(Thread.currentThread().getName() + "-" + i);
            try {
                Thread.sleep(1*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return t;
    }
}
复制代码

线程的休眠(Thread.sleep)

  • 通过Thread.sleep()来对当前线程进行休眠,这时候会释放CPU时间片但不会丢失任何监视器的所有权。也就是把CPU分配给进程的执行时间,让给本线程所在的进程的其他线程去抢占执行,但并不会释放锁。

线程的等待(Join)

父线程创建子线程后,两者并不影响,所以是并行执行,如果我们需要子进程先执行,再执行父进程,就要使用join。

  • 代码示例
package main.com.domain.java.basics.threadprocess;

public class JoinDemo {

    public static void main(String[] args) throws InterruptedException {
        JoinRunnable runnable = new JoinRunnable();
        Thread thread =new Thread(runnable);
        thread.start();
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
            //当循环到5的时候,让子线程先执行完,再来执行父进程。
            if (i==5) {
                thread.join();
            }
        }
    }
}

class JoinRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
        }
    }
}



复制代码

线程的中断

  • interrupted
    interrupt()的作用是中断本线程。 本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。 如果本线程是处于阻塞状态调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过sleep()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。 如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。 如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。中断一个“已终止的线程”不会产生任何操作。
中断正在执行状态的线程

从上面的介绍中,可以知道,我们可以用interrupted来中断自己这个正在执行状态的线程。

  • 代码示例
package main.com.domain.java.basics.threadprocess;

public class InterruptDemo {

    public static void main(String[] args) {

        InterruptRunnable runnable = new InterruptRunnable();
        Thread thread =new Thread(runnable,"子线程");
        thread.start();
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
            //当循环到5的时候,让子线程终止。
            if (i==5) {
                thread.interrupt();
            }
        }

    }



}

class InterruptRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {

            if (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName()+"-"+i);
            }

        }
    }
}

复制代码
中断阻塞状态的线程

从介绍中可以知道, 通过interrupt来中断一个阻塞状态的线程,会抛出InterruptedException异常并且中断标记isInterrupted会变成false,导致无法做到线程的中断。这里举例用sleep让线程变成阻塞状态。,代码如下:

package main.com.domain.java.basics.threadprocess;

public class InterruptDemo {

    public static void main(String[] args) {

        InterruptRunnable runnable = new InterruptRunnable();
        Thread thread =new Thread(runnable,"子线程");
        thread.start();
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
            //当循环到5的时候,让子线程终止。
            if (i==5) {
                thread.interrupt();
            }
        }

    }



}

class InterruptRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            try {
                Thread.sleep(1000);
                if (!Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName()+"-"+i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码

上面这种做法是无法中断一个阻塞线程的,我们只有通过自定义标志来中断阻塞线程。代码示例如下:


package main.com.domain.java.basics.threadprocess;

public class InterruptDemo {

    public static void main(String[] args) {

        InterruptRunnable runnable = new InterruptRunnable();
        Thread thread = new Thread(runnable, "子线程");
        thread.start();
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName() + "-" + i);
                //当循环到5的时候,让子线程终止。
                if (i == 5) {
                    runnable.interrupt(false);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }


}

class InterruptRunnable implements Runnable {
    private boolean flag = true;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(500);
                if (flag) {
                    System.out.println(Thread.currentThread().getName() + "-" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void interrupt(boolean flag) {
        this.flag = flag;
    }
}

复制代码

守护线程

概念

  • 守护线程
    所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的用户线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

  • 用户线程(普通线程) 普通线程,和守护线程区别之一是当最后一个非守护线程结束时候,JVM会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响JVM的退出。言外之意是只要有一个用户线程还没结束正常情况下JVM就不会退出。

设置线程为守护线程

package main.com.domain.java.basics.threadprocess;

public class DaeMonDemo {
    public static void main(String[] args) {

        DaeMonRunnable runnable = new DaeMonRunnable();
        Thread thread = new Thread(runnable, "守护线程");
        //设置为守护线程
        thread.setDaemon(true);
        thread.start();

        for (int i = 0; i <10 ; i++) {
            try {
                Thread.sleep(300);
                System.out.println(Thread.currentThread().getName() + "-" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

class DaeMonRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            try {
                Thread.sleep(500);
                System.out.println(Thread.currentThread().getName() + "-" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

复制代码

代码中可以看出,单主线程执行完毕时,尽管守护线程没有执行完毕,JVM也退出了。

线程让步(yield)

Java线程中的Thread.yield()方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己争抢到的时间片让掉,让自己或者其它的线程去争抢,注意是让自己或者其他线程争抢,并不是单纯的让给其他线程。所以可能下一次拿到时间片的线程可能是其他线程也可能还是自己。

  • 代码示例
package main.com.domain.java.basics.threadprocess;

public class DaeMonDemo {
   public static void main(String[] args) {

       DaeMonRunnable runnable = new DaeMonRunnable();
       Thread thread = new Thread(runnable, "让步线程");
       //设置为守护线程
       thread.start();

       for (int i = 0; i < 100; i++) {
           try {
               Thread.sleep(500);
               System.out.println(Thread.currentThread().getName() + "-" + i);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }

       }
   }
}

class DaeMonRunnable implements Runnable {

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           try {
               Thread.sleep(500);
               if (i %2==0) {
                   Thread.yield();
                   System.out.println("让步");
               }
               System.out.println(Thread.currentThread().getName() + "-" + i);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
   }
}

复制代码

运行代码可以看出,当线程让步之后,可能执行的是主线程,也可能执行的是自己。

线程的一些方法

  • Thread.setName() 设置线程名字
  • Thread.setPriority() 设置线程优先级,加大争抢到时间片概率,参数有:Thread.MAX_PRIORITY,Thread.MIN_PRIORITY,Thread.NORM_PRIORITY
  • Thread.isAlive() 获取线程是否活动中

线程同步

线程和其所在进程的其他线程共享同一块内存区域。 java允许多线程并发,那么当多个线程同时操作一个可共享的资源变量时将会导致数据不准确,相互之间产生冲突。如下面这个例子, 两个线程同时操作变量count,得到的结果,并不是我们想的一样。

package main.com.domain.java.basics.threadprocess;

/**
 * 线程同步
 */
public class ThreadSynchronizeDemo {

    public static void main(String[] args) {
        ThreadRunnable runnable = new ThreadRunnable();
        Thread thread1 = new Thread(runnable, "窗口1");
        Thread thread2 = new Thread(runnable, "窗口2");
        thread1.start();
        thread2.start();
    }

}
class ThreadRunnable implements Runnable{

    private int count=30;

    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
                if (count>0) {
                    --count;
                    System.out.println(String.format("你还剩%d张票",count));
                }
        }
    }
}

复制代码

上面代码,count的输出结果是错乱的。那么多线程情况下的操作同一个资源变量时需要怎么做到同步呢, 有以下三种方法

  • synchronized 代码块
package main.com.domain.java.basics.threadprocess;

/**
 * 线程同步
 */
public class ThreadSynchronizeDemo {

    public static void main(String[] args) {
        ThreadRunnable runnable = new ThreadRunnable();
        Thread thread1 = new Thread(runnable, "窗口1");
        Thread thread2 = new Thread(runnable, "窗口2");
        thread1.start();
        thread2.start();
    }

}

class ThreadRunnable implements Runnable {

    private int count = 30;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //同步代码块
            synchronized (this) {
                if (count > 0) {
                    --count;
                    System.out.println(String.format("你还剩%d张票", count));
                }
            }
        }
    }
}

复制代码
  • synchronized 方法

package main.com.domain.java.basics.threadprocess;

/**
 * 线程同步
 */
public class ThreadSynchronizeDemo {

    public static void main(String[] args) {
        ThreadRunnable runnable = new ThreadRunnable();
        Thread thread1 = new Thread(runnable, "窗口1");
        Thread thread2 = new Thread(runnable, "窗口2");
        thread1.start();
        thread2.start();
    }

}

class ThreadRunnable implements Runnable {

    private int count = 30;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            buyTicket();
        }
    }
    //同步方法
    private synchronized void buyTicket(){
        if (count > 0) {
            --count;
            System.out.println(String.format("你还剩%d张票", count));
        }
    }
}
复制代码
  • 可重入锁(ReentrantLock)
package main.com.domain.java.basics.threadprocess;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程同步
 */
public class ThreadSynchronizeDemo {

    public static void main(String[] args) {
        ThreadRunnable runnable = new ThreadRunnable();
        Thread thread1 = new Thread(runnable, "窗口1");
        Thread thread2 = new Thread(runnable, "窗口2");
        thread1.start();
        thread2.start();
    }

}

class ThreadRunnable implements Runnable {

    private int count = 30;
    //声明这个锁
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {

        for (int i = 0; i < 100; i++) {
            //获得锁
            lock.lock();
            if (count > 0) {
                --count;
                System.out.println(String.format("你还剩%d张票", count));
            }
            //释放锁
            lock.unlock();
        }
    }

}

复制代码

线程的死锁问题

在Java中使用多线程,就会有可能导致死锁问题。死锁会让对应产生死锁的线程卡住,不再程序往下执行。我们只能通过中止并重启的方式来让程序重新执行。 造成死锁的原因可以概括成三句话:

  • 当前线程拥有其他线程需要的资源
  • 当前线程等待其他线程已拥有的资源
  • 都不放弃自己拥有的资源

死锁例子

举个例子:在沙漠上有两个人,一个人有饼,一个人有食物,他们希望自己都有水和食物,但是谁也不肯给出自己的资源。造成死锁。

  • 代码示例

package main.com.domain.java.basics.threadprocess;

public class DeadLockDemo {

    //水对象锁
    public static Object water = new Object();
    //食物对象锁
    public static Object food = new Object();

    public static void main(String[] args) {

        Runnaable1 runnaable1=new Runnaable1();
        Runnaable2 runnaable2=new Runnaable2();
        Thread t1=new Thread(runnaable1,"第一个人");
        Thread t2=new Thread(runnaable2,"第二个人");
        t1.start();
        t2.start();
    }

}
class Runnaable1 implements Runnable{
    @Override
    public void run() {
        synchronized (DeadLockDemo.water) {
            try {
                System.out.println(Thread.currentThread().getName() + " 拿到水");
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "等待拿到食物");
            synchronized (DeadLockDemo.food) {
                System.out.println(Thread.currentThread().getName() + "拿到食物");
            }
        }
    }
}


class Runnaable2 implements Runnable{
    @Override
    public void run() {
        synchronized (DeadLockDemo.food) {
            try {
                System.out.println(Thread.currentThread().getName() + "拿到食物");
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "等待拿到水");
            synchronized (DeadLockDemo.water) {
                System.out.println(Thread.currentThread().getName() + "拿到水");
            }
        }
    }
}

复制代码

关于死锁问题,我们在代码设计及编写的时候的要特别注意。

多线程使用实现生产者消费者模型

什么是生产者消费者模型

一种重要的模型,基于等待/通知机制。生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点:

  • 生产者生产的时候消费者不能消费
  • 消费者消费的时候生产者不能生产
  • 缓冲区空时消费者不能消费
  • 缓冲区满时生产者不能生产

Object对象的wait,notify,notifyall方法

  • wait
    如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态,会处于该对象的等待池中,等待池中的线程不会去竞争该对象的。
  • notify
    如果对象调用了notify方法就只随机唤醒一个wait 线程可以继续运行。
  • notifyall
    如果对象调用了notifyAll方法就会唤醒所有wait 线程继续运行。

注意

  • 在调用wait(), notify()或notifyAll()的时候,必须先获得锁,且状态变量须由该锁保护,而固有锁对象与固有条件队列对象又是同一个对象。也就是说,要在某个对象上执行wait,notify,先必须锁定该对象,而对应的状态变量也是由该对象锁保护的,所以一般这些方法都写在synchronized中。

代码示例

package main.com.domain.java.basics.threadprocess;

/**
 * 生产者和消费者,wait()和notify()的实现
 *
 * @author ZGJ
 * @date 2017年6月22日
 */
public class Test1 {
    private static Integer count = 0;
    private static final Integer FULL = 10;
    private static String LOCK = "lock";

    public static void main(String[] args) {
        Test1 test1 = new Test1();
        new Thread(test1.new Producer(),"厨师1号").start();
        new Thread(test1.new Producer(),"厨师2号").start();
        new Thread(test1.new Producer(),"厨师3号").start();
        new Thread(test1.new Producer(),"厨师4号").start();
        new Thread(test1.new Producer(),"厨师5号").start();
        new Thread(test1.new Consumer(),"客人1号").start();
        new Thread(test1.new Consumer(),"客人2号").start();
        new Thread(test1.new Consumer(),"客人3号").start();
        new Thread(test1.new Consumer(),"客人4号").start();
        new Thread(test1.new Consumer(),"客人5号").start();

    }

    class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(300);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (LOCK) {
                    while (count == FULL) {
                        try {
                            LOCK.wait();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    count++;
                    System.out.println(Thread.currentThread().getName() + "生产一个完毕,目前总共有" + count);
                    LOCK.notifyAll();

                }
            }
        }
    }

    class Consumer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK) {
                    while (count == 0) {
                        try {
                            LOCK.wait();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + "消费者消费一个完毕,目前总共有" + count);
                    LOCK.notifyAll();
                }
            }
        }
    }
}

复制代码

线程池的使用

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:

  如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

  那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务,使用线程池就可以达到这个效果?

使用线程池的好处:

  • 重用存在的线程,减少对象创建、消亡的开销,性能佳。
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  • 提供定时执行、定期执行、单线程、并发数控制等功能。

四种创建线程池的方式

newSingleThreadExecutor
  • 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newFixedThreadPool
  • 创建一个指定数量线程的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newCachedThreadPool
  • 创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60s不执行任务)的线程,当任务增加时,此线程池有可以智能的添加新线程来处理任务,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM),能到创建的最大线程数量。
newScheduledThreadPool
  • 创建一个大小无限制的支持定时及周期性任务执行的线程池。

备注: corePoolSize:核心线程数量,当有新任务在execute()方法提交时,会执行以下判断: 如果运行的线程少于 corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;如果线程池中的线程数量大于等于 corePoolSize 且小于 maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务;如果设置的corePoolSize 和 maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取任务并处理;如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务;

代码示例

package main.com.domain.java.basics.threadprocess;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ExecutorsDemo {
    public static void main(String[] args) {


        ExecurtorsRunnable execurtorsRunnable = new ExecurtorsRunnable();

        //创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
//        ExecutorService executorService = Executors.newSingleThreadExecutor();
        // 创建一个指定数量线程的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
//        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60s不执行任务)的线程,当任务增加时,此线程池有可以智能的添加新线程来处理任务,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM),能到创建的最大线程数量
//        ExecutorService executorService = Executors.newCachedThreadPool();

        //        executorService.execute(execurtorsRunnable);
//        executorService.execute(execurtorsRunnable);

        //创建一个大小无限制的支持定时及周期性任务执行的线程池
        //corePoolSize:核心线程数量,当有新任务在execute()方法提交时,会执行以下判断:
        //如果运行的线程少于 corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;
        //如果线程池中的线程数量大于等于 corePoolSize 且小于 maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务;
        //如果设置的corePoolSize 和 maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取任务并处理;
        //如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务;
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
        executorService.scheduleAtFixedRate(execurtorsRunnable, 1, 3, TimeUnit.SECONDS);


    }


}

class ExecurtorsRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-" + i);
            try {
                Thread.sleep(1 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


复制代码
关注下面的标签,发现更多相似文章
评论