多线程

进程vs线程:

进程:每个程序被运行加载到内存之后,都会被操作系统作为一个进程,进程是处于运行过程中的程序,是具有独立功能,被操作系统进行资源分配和调度的独立单元。

线程:一个进程里面可以拥有多个线程,线程拥有自己的堆栈,程序计数器和自己的局部变量,但是不拥有系统资源,多个线程共享进程的系统资源。

创建线程的三种方式:

1.继承Thread类创建线程类

继承Thread类,重写run()方法,该run()方法就代表程序需要完成的任务。创建Thread子类的实例,即创建线程对象。然后通过start()方法启动线程。

 1 public class MyThread extends Thread {
 2     private int count;
 3 
 4     @Override
 5     public void run() {
 6 
 7         for (; count < 100; count ++) {
 8             System.out.println(getName() + "---" + count);
 9         }
10     }
11 
12     public static void main(String[] args) {
13         for (int i = 0; i < 100; i++) {
14             System.out.println(Thread.currentThread().getName() + i);
15             if (i == 20) {
16                 new MyThread().start();
17                 new MyThread().start();
18             }
19         }
20     }
21 }

2.实现Runnable接口来创建并启动多线程

继承Runnable接口,并重写该接口的run()方法。创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread类对象,该Thread类对象才是真正的多线程对象。然后调用start()方法来执行该Thread实例线程。

示例:

 1 public class MyRunableThread implements Runnable {
 2 
 3     private int count = 0;
 4 
 5     @Override
 6     public void run() {
 7         for (; count < 20; count ++){
 8             System.out.println(Thread.currentThread().getName() + "--" + count);
 9         }
10     }
11 
12     public static void main(String[] args){
13         for (int i=0; i<30; i++){
14             System.out.println(Thread.currentThread().getName() + "--" + i);
15             if (i == 20){
16                 MyRunableThread myRunableThread = new MyRunableThread();
17                 new Thread(myRunableThread, "子线程1").start();
18                 new Thread(myRunableThread, "子线程2").start();
19             }
20         }
21     }
22 }

  3.使用Callable和Future创建线程

Java无法将任意方法包装成线程执行体,在Java5之后提供了一个Callable接口,该接口提供了call()方法作为线程执行体,类似于Runnable接口,call()方法类似于run()方法。但是call()方法比run()方法作为执行体,就更加强大。

1.call()可以有返回值。

2.call()可以声明抛出异常。

public class MyCallableThread {

    public static void main(String[] args) {
        //FutureTask包装Callable实例对象,此处使用了Lambda表达式来创建一个Callable对象
        FutureTask<Integer> task = new FutureTask<>(() -> {
            int count = 0;
            for (; count < 10; count++) {
                System.out.println(Thread.currentThread().getName() + "--" + count);
            }
            return count;
        });

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--" + i);
            if (i == 20) {
                new Thread(task, "callAbleThread").start();
            }
        }
        try {
            //Callable线程获取返回值
            System.out.println("线程的返回值为:" + task.get());
        } catch (Exception e) {
            e.getStackTrace();
        }
    }
}

 Future接口代表Callable接口里call()方法的返回值,FutureTask是Future接口的实现类,FutureTask实现类实现了Future接口和Runnable接口,可以做为Thread类的Target。Future接口里提供了如下几个方法来控制实现Callable接口的任务:

boolean cancel(Boolean mayInterruptIfRunning) 试图取消该Future里关联的Callable任务。

V get()返回Callable任务里的返回值。该方法会阻塞程序,必须等子线程结束后,才会得到返回值。

V get(long timeOut, TimeUnit unit)返回Callable任务里的返回值。该方法让程序最多阻塞的最长时间,由timeOut和unit来指定,如果超时,Callable接口依然没有返回值,则会抛出TimeOutException异常。

boolean isCancelled()如果在Callable任务正常执行前被取消,则返回true

boolean isDone()如果Callable任务已经完成,则返回true。

Callable接口创建线程的步骤:

1)创建Callable接口的实现类,并实现Call()方法,该方法作为线程的执行体,且该Call()方法有返回值,在创建Callable实现类的实例。也可以使用Lambda表达式来创建Callable接口的实例。

2)使用FutureTask对象来包装Callable接口的实现类实例,FutureTask包装了Callable实现类的返回值。

3)使用FutureTask对象作为Thread对象的target创建并启动多线程。

4)调用FutureTask对象的get()方法来获取子线程执行结束后的返回值。

 

控制线程

(1)join线程

join()方法时Thread提供一个线程等待被join()线程完成的方法。

join()等待被join线程执行完成。

join(long millis)等待被join线程的最长时间,单位为millis毫秒。

(2)后台进程

后台进程就是为了其他进程提供服务,一旦前台进程死亡之后,后台进程就自动死亡。

setDaemon(true);设置后台进程

(3)线程睡眠sleep()

如果需要当前线程暂停一段时间,并进入阻塞状态,可以使用Thread类的sleep()方法。

static void sleep(long millis) 让当前正在执行的线程暂停millis毫秒,并进入阻塞状态。

static void sleep(long millis, int nanos) 让当前正在执行的线程暂停millis毫秒,加上nanos毫微秒。

(4)线程让步:yield()

让当前正在执行的线程暂停,但是它不会阻塞该线程,只是将线程转入阻塞状态。只是让当前线程暂停一下,处于就绪状态,让线程调度器将其调度出来重新执行。

线程同步

 sychronized可以用来修饰方法和代码块,来实现加锁。 

线程通信

(1)Object类提供的wait(),notify(),notifyAll()三个方法可以实现线程通信。一般用于sychronized关键字保证的同步通信,这三个方法可以由同步监视器对象来调用,可以分为如下两种情况:

1)对于sychronized修饰的同步方法,该类默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这三个方法。

2)对于使用sychronized修饰的同步代码块,同步监视器就是sychronized括号里面的对象,所以必须使用该对象调用上述三个方法。

await():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或者notifyAll()方法来唤醒该线程。wait()方法有三种形式,无时间参数表示一直等待,直到其他线程唤醒,另外两种形式是等待一段时间后自动唤醒。

notify()唤醒在此同步监视器上等待的单个线程。如果所有线程都在此监视器上等待,则会唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定之后,才可以执行被唤醒的线程。

notifyAll()唤醒在此同步监视器上等待的所有线程。

(2)使用Condition控制线程通信

使用Lock对象来保证同步时,java提供了一个Condition类来保持协调。使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象。

Condition将同步监视器方法(wait(),notify(),notifyAll())分解成不同的对象,以便通过将这些对象与Lock对象组合使用。Lock替代了同步方法或者同步代码块,Condition替代了同步监视器的功能。

Condition实例被绑定在一个Lock对象上。获取特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可。Condition类提供的三个方法如下:

await()类似于隐式同步监视器上的await()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程。

signal()唤醒此Lock对象上等待的单线程。如果所有线程在该Lock对象上等待,则会选择性唤醒其中一个线程。只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程。

signalAll()唤醒此Lock对象上等待的所有线程。只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程。

(三)使用阻塞队列(BlockingQueue)控制线程通信

BlockingQueue的主要用途并不是作为容器,而是作为线程同步的工具。BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则线程被阻塞,当消费者线程试图从BlockingQueue中取出元素时,如果队列已空,则线程被阻塞。

程序的两个线程通过交替向BlockingQueue中放入元素,取出元素,既可以很好的控制线程的通信。

BlockingQueue提供的方法:

put(E e) 尝试把E元素放入BlockingQueue中,如果该队列元素已满,则阻塞该线程。

take()尝试从BlockingQueue的头部取出元素,如果该队列中的元素已空,则阻塞该线程。

BlockingQueue继承Queue接口。同样也可以使用Queue接口中提供的方法。Queue接口中提供的方法归纳起来分为三组:

1)在队列尾部插入元素。包括:add(E e), offer(E e)和put(E e)方法,当该队列已满时,这三个方法分别会抛出异常,返回false,阻塞队列。

2)在队列头部删除并返回删除的元素。包括remove(),poll()和take()方法。当该队列已空时,这三个方法分别会抛出异常,返回false,阻塞队列。

3)在队列头部取出但并不删除元素。包括element()和peek(),当队列已空时,这两个方法分别抛出异常,返回false。

线程池

优点:实现资源的复用,有效控制并发线程的数量,有效的管理线程。

使用Executors工厂类生产线程池,该工厂类包含如下几个静态工厂方法来创建线程池。

 newCachedThreadPool()创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。

newFixedThreadPool(int nThread)创建一个可用的具有固定线程数的线程池。

newSingleThreadExecutor()创建一个具有单线程的线程池,相当于newFixedThreadPool()方法传入参数为1.

newScheduledThreadPool(int corePoolSize)创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池里。

newSingleScheduledExecutor()创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。

ExecutorService newWorkStealingPool(int parallelism)创建持有足够的线程的线程池来支持给定的并行级别,该方法会使用多个队列来减少竞争。

ExecutorService newWorkStealingPool()该方法是上一个方法的简化版本。如果当前机器有4个CPU,则目标并行级别被设置成为4,也就是相当于为前一个方法传入4作为参数。

上面7个方法的前三个方法返回一个ExecutorService对象,该对象代表一个线程池,它可以执行Runnable对象和Callable对象所代表的线程;而中间的两个方法返回一个ScheduleExecutorService线程池,它是ExecutorService的子类,它是在指定延迟后执行线程任务。后两个方法。生成的work stealing池,相当于后台线程池,如果所有前台线程都死亡了,work stealing池的线程会自动死亡。

ExecutorService代表线程池,程序只需要将Runnable对象或者Callable对象(代表线程任务)提交给指定的线程池,该线程池就会尽快执行该任务。

ExecutorService提供如下方法:

Future<?> submit(Runnable task)将一个Runnable对象提交给线程池,线程池将在空闲的时候执行Runnable对象所代表的任务。Future对象代表Runnable任务的返回值,然后run()方法并没有返回值,所以Future对象将在run()方法执行结束返回null。但可以调用Future的isDone(),isCanceled()方法来获取Runnable对象状态。

<T> Future<T> submit(Runnable task, T result) 将一个Runnable对象提交给指定的线程池,线程池将在有空闲线程时执行Runnable对象所代表的任务。result显式得指定Future对象在run()方法执行结束后返回result。

<T> Future<T> submit(Callable<T> task) 将一个Callable对象提交给指定的线程池,线程池将在有空闲时执行Callable对象所代表的任务提交给线程池,线程池将在空闲的时候执行Callable代表的任务。Future代表Callable对象里Call()方法的返回值。

用完一个线程池后,应该调用线程池的shutdown()方法,该方法将启动线程池的关闭序列,调用shutdown()方法后的线程池将不再接受新任务,但会将以前所有已提交任务执行完。

线程池来执行任务的步骤:

1)调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。

2)创建Runnable实现类或Callable实现类的实例,作为线程执行任务。

3)调用ExecutorService对象的submit()方法来提交Runnable实例或者Callable实例

4)当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池。

 1 public class ThreadPoolTest {
 2     public static void main(String[] args) throws ExecutionException, InterruptedException {
 3         ExecutorService executorService = Executors.newFixedThreadPool(2);
 4         AtomicInteger counts = new AtomicInteger();
 5         FutureTask<Integer> task = new FutureTask<>(() -> {
 6             for (int i = 0; i < 100; i++) {
 7                 counts.getAndIncrement();
 8                 System.out.println(Thread.currentThread().getName() + "--" + i);
 9                 System.out.println(counts);
10             }
11             return counts.get();
12         });
13         executorService.submit(task);
14         System.out.println(task.get());
15         if (task.isDone()){
16             executorService.submit(task);
17             System.out.println(task.isDone());
18         }
19         executorService.shutdown();
20     }
21 }

 

posted @ 2020-04-05 10:28  seedss  阅读(185)  评论(0编辑  收藏  举报