Java-Se-多线程

进程 & 线程

进程:是系统进行资源分配的和调度的一个独立单位。举例:将公司的一个部门看做一个进程,公司分配项目奖金的时候就是以部门为基本单位分配1万块钱,部门下面的十几我的就分这1万块钱,这时就能够将部门中的每一个人看做是一个线程。这就是进程与线程之间的关系。java

线程:线程是进程的执行单元,一个进程能够有多个线程。编程

进程间能够并发,线程间也能够并发。数组

什么是并发,什么是并行?????缓存

并发(concurrency)和并行(parallellism)是: 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。 解释二:并行是在不一样实体上的多个事件,并发是在同一实体上的多个事件。 解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群 因此并发编程的目标是充分的利用处理器的每个核,以达到最高的处理性能。-- 并发编程网安全

所以,多线程编程解决的是并发问题,并行的问题能够经过分布式部署来解决。???多线程

建立线程的3种方式

继承Thread类

步骤:
一、 定义Thread类的子类,并重写run()方法。
二、 建立Thread子类的实现
三、 调用线程对象的start()方法启动线程并发

代码片断:dom

// 经过继承Thread类来建立线程类
public class FirstThread extends Thread
{
    // 重写run方法,run方法的方法体就是线程执行体
    public void run()
    {

    }
    public static void main(String[] args)
    {
        // 建立、并启动第一条线程
        new FirstThread().start();
        // 建立、并启动第二条线程
        new FirstThread().start();
    }
}

实现Runnable接口

步骤:
一、定义Runnable接口的实现类,并重写该接口的run()方法
二、建立该实现类的实例,并以此实例做为Thread的target来建立Thread对象
三、调用该线程对象的start()方法来启动线程分布式

代码片断:ide

// 经过实现Runnable接口来建立线程类
public class SecondThread implements Runnable
{
    // run方法一样是线程执行体
    public void run()
    {
       
    }

    public static void main(String[] args)
    {
       SecondThread st = new SecondThread();     // ①
        // 经过new Thread(target , name)方法建立新线程
        new Thread(st , "新线程1").start();
        new Thread(st , "新线程2").start();
    }
}

Callable和Future

步骤:
一、建立Callable接口实现类,并实现call()方法
二、使用FutureTask类来包装Callable对象
三、使用FutureTask对象做为Thread对象的target建立并启动线程
四、经过FutureTask对象的get()方法获取线程的返回值

代码片断:

@Test
    public void test() throws Exception {
        FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {

            public Integer call() throws Exception {
                return 1;
            }
        });

        new Thread(task, "新的线程1").start();

        Integer i = task.get();
        System.out.println("i:" + i);
    }

线程生命周期

线程的生命周期有:新建、就绪、运行、阻塞、死亡。

线程对象调用start()方法后就处于就绪状态。

线程生命周期的状态转换图以下: 线程生命周期状态转换图

总结:

  • wait(),进入阻塞状态,释放同步监视器
  • sleep(),进入阻塞状态,给其它进程(无论线程优先级)执行的机会,不会释放同步监视器
  • join(),调用线程进入阻塞状态,直到加入的线程执行完成为止
  • yield(),不进入阻塞状态,给其它线程(只给优先级高)执行的机会,不会释放同步监视器

控制线程

join

join:让一个线程等待另外一个线程完成。

setDaemon(true)

设置为后台线程,调用线程实例自己的setDaemon()方法:

MyThread myThread = new MyThread();
        myThread.setDaemon(true);

sleep

线程睡眠,并进入阻塞状态。

调用线程Thread类的静态sleep()方法,而不是某个线程的实例方法:

Thread.sleep(1000);

yield

线程暂停,但不进入阻塞状态。

调用线程Thread类的静态yield()方法,而不是某个线程的实例方法:

Thread.yield();

sleep()与yield()区别

  • sleep()与yield()都会让当前线程暂停,sleep()会给其它线程执行的机会,不理会其它线程的优先级;但yield()只给优先级相同或更高的线程执行的机会。
  • sleep()会让线程进入阻塞状态,yield()则不会进入阻塞状态。

线程优先级

priority,1-10,数值越大优先级越高:

  • MAX_PRIORITY:10
  • MIN_PRIORITY:1
  • NORM_PRIORITY:5
myThread.setPriority(Thread.MAX_PRIORITY);

注:servlet中的 load-on-startup 参数,值越大优先级越低

线程同步

传统线程同步

同步方法

同步一个易发生线程安全的方法,这里的锁就是this,即当前类对象

// 提供一个线程安全draw()方法来完成取钱操做
    public synchronized void draw(double drawAmount)
    {
        
    }

同步代码块

同步一个易发生线程安全的字段,这里的锁就是此可能发生线程安全的字段

public class DrawThread extends Thread
{
    // 模拟用户帐户
    private Account account;

    // 当多条线程修改同一个共享数据时,将涉及数据安全问题。
    public void run()
    {
        // 使用account做为同步监视器,任何线程进入下面同步代码块以前,
        // 必须先得到对account帐户的锁定——其余线程没法得到锁,也就没法修改它
        // 这种作法符合:“加锁 → 修改 → 释放锁”的逻辑
        synchronized (account)
        {
           
        }
        // 同步代码块结束,该线程释放同步锁
    }
}

什么时候释放同步监视器

  • wait()方法会释放同步监视器
  • Thread.sleep(),Thread.yield()不会释放同步监视器

同步锁(Lock)

java5开始提供了同步锁,经常使用的同步锁是ReentrantLock。

同步锁继承关系以下:

- Lock
    |-ReentrantLock
- ReadWriteLock
    |-ReentrantReadWriteLock

同步锁使用代码片断:

import java.util.concurrent.locks.*;

public class Account
{
    // 定义锁对象
    private final ReentrantLock lock = new ReentrantLock();

    // 提供一个线程安全draw()方法来完成取钱操做
    public void draw(double drawAmount)
    {
        // 加锁
        lock.lock();
        try
        {

        }
        finally
        {
            // 修改完成,释放锁
            lock.unlock();
        }
    }
}

ThreadLocal

原理:为每个使用该变量的线程都提供一个变量值的副本,使每个线程均可以独立改变本身的副本,而不会和其余线程的副本冲突,就好像每个线程都彻底拥有该变量同样。代码内部使用ThreadLocal.ThreadLocalMap来存储变量的键和值,也就是内部使用一个相似Map的结构来存储name和name对应的值。

ThreadLocal只提供三个public方法:

  • T get():返回此线程局部变量中当前线程副本中的值。
  • void remove():删除此线程局部变量中当前线程副本中的值。
  • void set(T value):设置此线程局部变量中当前线程副本中的值。

示例:

class Acount
{
    private ThreadLocal<String> name = new ThreadLocal<>();
    public Acount(String str){
        this.name.set(str);
    }
    public String getName(){
        return name.get();
    }
    public void setName(String str){
        this.name.set(str);
    }
    //......
}

线程间通讯

传统线程间通讯

适用条件:若是程序中使用的是同步代码块、同步方法来控制线程安全,则线程间的通讯是使用Object类的 wait(),notify(),notifyAll()控制线程间通讯。

public class Account
{
    // 封装帐户编号、帐户余额的两个成员变量
    private String accountNo;
    private double balance;
    // 标识帐户中是否已有存款的旗标
    private boolean flag = false;

    public Account(){}
    // 构造器
    public Account(String accountNo , double balance)
    {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    // accountNo的setter和getter方法
    public void setAccountNo(String accountNo)
    {
        this.accountNo = accountNo;
    }
    public String getAccountNo()
    {
        return this.accountNo;
    }
    // 所以帐户余额不容许随便修改,因此只为balance提供getter方法,
    public double getBalance()
    {
        return this.balance;
    }

    public synchronized void draw(double drawAmount)
    {
        try
        {
            // 若是flag为假,代表帐户中尚未人存钱进去,取钱方法阻塞
            if (!flag)
            {
                wait();
            }
            else
            {
                // 执行取钱
                System.out.println(Thread.currentThread().getName()
                    + " 取钱:" +  drawAmount);
                balance -= drawAmount;
                System.out.println("帐户余额为:" + balance);
                // 将标识帐户是否已有存款的旗标设为false。
                flag = false;
                // 唤醒其余线程
                notifyAll();
            }
        }
        catch (InterruptedException ex)
        {
            ex.printStackTrace();
        }
    }
    public synchronized void deposit(double depositAmount)
    {
        try
        {
            // 若是flag为真,代表帐户中已有人存钱进去,则存钱方法阻塞
            if (flag)             //①
            {
                wait();
            }
            else
            {
                // 执行存款
                System.out.println(Thread.currentThread().getName()
                    + " 存款:" +  depositAmount);
                balance += depositAmount;
                System.out.println("帐户余额为:" + balance);
                // 将表示帐户是否已有存款的旗标设为true
                flag = true;
                // 唤醒其余线程
                notifyAll();
            }
        }
        catch (InterruptedException ex)
        {
            ex.printStackTrace();
        }
    }
}

使用Condition控制线程间通讯

适用条件:若是程序中使用的是同步锁(Lock)来控制线程安全,则线程间通讯是使用Condition来控制线程间通讯。

获取Condiion实例代码片断:经过锁去获取

private final Lock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();

Condition类提供以下方法以实现线程间通讯:

  • await()
  • signal(),唤醒单个线程
  • signalAll(),唤醒此Lock上的全部线程

示例:

import java.util.concurrent.*;
import java.util.concurrent.locks.*;

public class Account
{
    // 显式定义Lock对象
    private final Lock lock = new ReentrantLock();
    // 得到指定Lock对象对应的Condition
    private final Condition cond  = lock.newCondition();

    public void draw(double drawAmount)
    {
        // 加锁
        lock.lock();
        try
        {
            if ()
            {
                
            }
            else
            {
                // 唤醒其余线程
                cond.signalAll();
            }
        }
        catch (InterruptedException ex)
        {
            ex.printStackTrace();
        }
        // 使用finally块来释放锁
        finally
        {
            lock.unlock();
        }
    }
}

使用阻塞队列(BockingQueue)控制线程间通讯

todo...

线程组与异常处理

todo...

线程池

普通线程池

java 5 新增了一个 Executors 工厂类来产生线程池,经常使用如下几个静态方法:

  • ExecutorService newCachedThreadPool():系统根据须要建立一个具备缓存功能的线程池。
  • ExecutorService newFixedThreadPool(int nThreads):建立一个可重用的、固定线程数的线程池。
  • ExecutorService newSingleThreadExector():建立一个只有单个线程的线程池。
  • ScheduledExecutorService newScheduledThreadPool(int corePoolSize):建立指定线程数的线程池,它能够指定延迟后执行线程任务。
  • ScheduledExecutorService newSingleThreadScheduledExecutor():建立一个只有一个线程和线程池,它能够在指定延迟后执行线程任务。
  • ExecutorService newWorkStealingPool(int parallelism):建立持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减小竞争。(java 8)
  • ExecutorService newWorkStealingPool():该方法是前一个方法的简化版本。若是当前机器有4个CPU,则目标并行级别被设置为4,也就是至关于前一个方法传入4做为参数。(java 8)

ExecutorService 表明尽快执行线程的线程池,程序只需把一个 Runnable 对象或 Callable 对象提交给指定的线程池便可。

ExecutorService 经常使用如下方法:

  • Future<?> submit(Runnable task):将一个线程任务提交给线程池,Future表明返回值。
  • <T> Future<T> submit(Runnable task,T result):同上,其中result显式指定线程执行执行结束后返回的值。
  • <T> Future<T> submit(Callable<T> task):将一个Calable对象提交给指定的线程池,Future表明返回值。

线程池使用步骤:
一、调用Executors类的静态工厂方法建立一个ExecutorService对象。
二、建立Runnable实现类或Callable实现类的实例。
三、调用ExecutorService对象的submit()方法提交Runnable或Callable实例。
四、调用ExecutorService对象的shutdown()关闭线程池。

示例代码片断:

import java.util.concurrent.*;

public class ThreadPoolTest
{
    public static void main(String[] args)
        throws Exception
    {
        // 建立足够的线程来支持4个CPU并行的线程池
        // 建立一个具备固定线程数(6)的线程池
        ExecutorService pool = Executors.newFixedThreadPool(6);
        // 使用Lambda表达式建立Runnable对象
        Runnable target = () -> {
            for (int i = 0; i < 100 ; i++ )
            {
                System.out.println(Thread.currentThread().getName()
                    + "的i值为:" + i);
            }
        };
        // 向线程池中提交两个线程
        pool.submit(target);
        pool.submit(target);
        // 关闭线程池
        pool.shutdown();
    }
}

ForkJoinPool

jdk 7 提供了ForkJoinPool来支持将一个任务拆分红多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。

java.util.concurrent.Executor
                    |-ExecutorService
                        |-AbstractExecutorService
                            |-ForkJoinPool

建立ForkJoinPool实例后,就能够调用ForkJoinPool的submit(ForkJoinTask task)或invoke(ForkJoinTask task)方法来提交任务。

其中ForkJoinTask有两个抽象子类:

java.util.concurrent.Future
                    |-ForkJoinTask          表示一个可执行、合并的任务
                        |-RecursiveAction   表示没有返回值的任务
                        |-RecursiveTask     表示有返回值的任务

完整示例:

import java.util.concurrent.*;

// 继承RecursiveAction来实现"可分解"的任务
class PrintTask extends RecursiveAction
{
    // 每一个“小任务”只最多只打印50个数
    private static final int THRESHOLD = 50;
    private int start;
    private int end;
    // 打印从start到end的任务
    public PrintTask(int start, int end)
    {
        this.start = start;
        this.end = end;
    }
    @Override
    protected void compute()
    {
        // 当end与start之间的差小于THRESHOLD时,开始打印
        if(end - start < THRESHOLD)
        {
            for (int i = start ; i < end ; i++ )
            {
                System.out.println(Thread.currentThread().getName()
                    + "的i值:" + i);
            }
        }
        else
        {
            // 若是当end与start之间的差大于THRESHOLD时,即要打印的数超过50个
            // 将大任务分解成两个小任务。
            int middle = (start + end) / 2;
            PrintTask left = new PrintTask(start, middle);
            PrintTask right = new PrintTask(middle, end);
            // 并行执行两个“小任务”
            left.fork();
            right.fork();
        }
    }
}
public class ForkJoinPoolTest
{
    public static void main(String[] args)
        throws Exception
    {
        ForkJoinPool pool = new ForkJoinPool();
        // 提交可分解的PrintTask任务
        pool.submit(new PrintTask(0 , 300));
        pool.awaitTermination(2, TimeUnit.SECONDS);
        // 关闭线程池
        pool.shutdown();
    }
}
import java.util.concurrent.*;
import java.util.*;

// 继承RecursiveTask来实现"可分解"的任务
class CalTask extends RecursiveTask<Integer>
{
    // 每一个“小任务”只最多只累加20个数
    private static final int THRESHOLD = 20;
    private int arr[];
    private int start;
    private int end;
    // 累加从start到end的数组元素
    public CalTask(int[] arr , int start, int end)
    {
        this.arr = arr;
        this.start = start;
        this.end = end;
    }
    @Override
    protected Integer compute()
    {
        int sum = 0;
        // 当end与start之间的差小于THRESHOLD时,开始进行实际累加
        if(end - start < THRESHOLD)
        {
            for (int i = start ; i < end ; i++ )
            {
                sum += arr[i];
            }
            return sum;
        }
        else
        {
            // 若是当end与start之间的差大于THRESHOLD时,即要累加的数超过20个时
            // 将大任务分解成两个小任务。
            int middle = (start + end) / 2;
            CalTask left = new CalTask(arr , start, middle);
            CalTask right = new CalTask(arr , middle, end);
            // 并行执行两个“小任务”
            left.fork();
            right.fork();
            // 把两个“小任务”累加的结果合并起来
            return left.join() + right.join();    // ①
        }
    }
}
public class Sum
{
    public static void main(String[] args)
        throws Exception
    {
        int[] arr = new int[100];
        Random rand = new Random();
        int total = 0;
        // 初始化100个数字元素
        for (int i = 0 , len = arr.length; i < len ; i++ )
        {
            int tmp = rand.nextInt(20);
            // 对数组元素赋值,并将数组元素的值添加到sum总和中。
            total += (arr[i] = tmp);
        }
        System.out.println(total);
        // 建立一个通用池
        ForkJoinPool pool = ForkJoinPool.commonPool();
        // 提交可分解的CalTask任务
        Future<Integer> future = pool.submit(new CalTask(arr , 0 , arr.length));
        System.out.println(future.get());
        // 关闭线程池
        pool.shutdown();
    }
}
相关文章
相关标签/搜索