多线程之深入浅出

为什么要使用多线程

网站的访问量非常的巨大,常常会出现多个用户端访问客户端的情况,多线程可以使我们更好去应对这种高并发的情况,最大可能去充分利用cpu,加快访问速度,提升用户使用的舒适度

分清进程线程

进程:
1. 进程是程序的一次动态执行过程, 占用特定的地址空间。
2. 每个进程由3部分组成:cpu、data、code。每个进程都是独立的,保有自己的cpu时间,代码和数据,即便用同一份程序产生好几个进程,它们之间还是拥有自己的这3样东西,这样的缺点是:浪费内存,cpu的负担较重。
3. 多任务(Multitasking)操作系统将CPU时间动态地划分给每个进程,操作系统同时执行多个进程,每个进程独立运行。以进程的观点来看,它会以为自己独占CPU的使用权。
4. 进程的查看
Windows系统: Ctrl+Alt+Del,启动任务管理器即可查看所有进程。
Unix系统: ps or top。
多线程之深入浅出_第1张图片
线程:
一个进程可以产生多个线程。同多个进程可以共享操作系统的某些资源一样,同一进程的多个线程也可以共享此进程的某些资源(比如:代码、数据),所以线程又被称为轻量级进程(lightweight process)。
1. 一个进程内部的一个执行单元,它是程序中的一个单一的顺序控制流程。
2. 一个进程可拥有多个并行的(concurrent)线程。
3. 一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且它们从同一堆中分配对象并进行通信、数据交换和同步操作。
4. 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。
5. 线程的启动、中断、消亡,消耗的资源非常少。
多线程之深入浅出_第2张图片
线程和进程的区别:
1. 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销
2. 线程可以看成是轻量级的进程,属于同一进程的线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
3. 线程和进程最根本的区别在于:进程是资源分配的单位,线程是调度和执行的单位。
4. 多进程: 在操作系统中能同时运行多个任务(程序)。
5. 多线程: 在同一应用程序中有多个顺序流同时执行。
6. 线程是进程的一部分,所以线程有的时候被称为轻量级进程。
7. . 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
8. 系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,除了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。
进程与程序的区别:
程序是一组指令的集合,它是静态的实体,没有执行的含义。而进程是一个动态的实体,有自己的生命周期。一般说来,一个进程肯定与一个程序相对应,并且只有一个,但是一个程序可以有多个进程,或者一个进程都没有。除此之外,进程还有并发性和交往性。简单地说,进程是程序的一部分,程序运行的时候会产生进程。

创建多线程的四大方式

一.继承Thread类(这种不推荐,类单继承,后期不易于代码维护)


/**
 * 创建线程的方式一:
 * 创建:继承Thread+重写run方法
 * 启动:.start()
 * 不推荐,因为java中类单继承,不易于后期代码维护
 * @author Zrd
 */
public class StartThread extends Thread{
     
    /**
     * run方法是线程入口点
     */
    @Override
    public void run () {
     
       for(int i=0;i<20;i++){
     
           System.out.println("听歌");
       }
    }
    public static void main(String[] args){
     
        new StartThread().start();
        for(int i=0;i<100;i++){
     
            System.out.println("敲代码");
        }
    }
}

二.实现Runnable接口(推荐,避免单继承的局限性)

/**
 * 创建线程的方式二:
 * 创建:实现Runnable+重写run
 * 启动:创建实现类对象+Thread对象+start
 * 推荐:避免了单继承的局限性,优先使用接口,便于后期代码维护
 * 方便资源共享
 * @author Zrd
 */
public class StartRunnable implements Runnable{
     
    /**
     * run方法不能抛出异常,只能try catch
     */
    @Override
    public void run () {
     
        for(int i=0;i<20;i++){
     
            System.out.println("听歌");
        }
    }

    public static void main (String[] args) {
     
        //创建代理对象
        new Thread(new StartRunnable()).start();
        for(int i=0;i<100;i++){
     
            System.out.println("敲代码");
        }

    }
}

三.实现Callable接口(推荐,juc中使用,属于高级并发编程,笔者暂时能力有限,先临摹,等深入了解后再来做分析)

/**
 * 创建线程的方式三:
 * Callable+重写run;创建执行服务;提交执行;获取结果;关闭服务
 * 高级并发编程中使用
 * @author Zrd
 */
public class StartCallable implements Callable<Boolean> {
     
    @Override
    public Boolean call () throws Exception {
     
        for(int i=0;i<20;i++){
     
            System.out.println("听歌"+i);
        }
        return true;
    }

    public static void main (String[] args) throws Exception{
     
        StartCallable c1 = new StartCallable();
        StartCallable c2 = new StartCallable();
        StartCallable c3 = new StartCallable();
        //创建执行服务
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        //提交执行
        Future< Boolean > s1 = executorService.submit(c1);
        Future< Boolean > s2 = executorService.submit(c2);
        Future< Boolean > s3 = executorService.submit(c3);
        //获取结果
        s1.get();
        s2.get();
        s3.get();
        //关闭服务
        executorService.shutdownNow();

    }
}

四:线程池(笔者暂时还未涉略,后期深入理解后再做补充)

线程状态

一个线程对象在它的生命周期内,需要经历5个状态:
多线程之深入浅出_第3张图片
操控线程状态的几种方式:
一.sleep(睡眠)
使用sleep,让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。(不会释放锁)

public class TestThreadState {
     
    public static void main(String[] args) {
     
        StateThread thread1 = new StateThread();
        thread1.start();
        StateThread thread2 = new StateThread();
        thread2.start();
    }
}
//使用继承方式实现多线程
class StateThread extends Thread {
     
    public void run() {
     
        for (int i = 0; i < 100; i++) {
     
            System.out.println(this.getName() + ":" + i);
            try {
     
                Thread.sleep(2000);//调用线程的sleep()方法;
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        }
    }
}

二:yield (礼让)
使用yield,让正在运行的线程直接进入就绪状态,让出CPU的使用权。(线程重新竞争cpu的使用权,所以礼让不一定成功

public class TestThreadState {
     
    public static void main(String[] args) {
     
        StateThread thread1 = new StateThread();
        thread1.start();
        StateThread thread2 = new StateThread();
        thread2.start();
    }
}
//使用继承方式实现多线程
class StateThread extends Thread {
     
    public void run() {
     
        for (int i = 0; i < 100; i++) {
     
            System.out.println(this.getName() + ":" + i);
            Thread.yield();//调用线程的yield()方法;
        }
    }
}

三.线程的联合join()
线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。如下面示例中,“爸爸线程”要抽烟,于是联合了“儿子线程”去买烟,必须等待“儿子线程”买烟完毕,“爸爸线程”才能继续抽烟。

public class TestThreadState {
     
    public static void main(String[] args) {
     
        System.out.println("爸爸和儿子买烟故事");
        Thread father = new Thread(new FatherThread());
        father.start();
    }
}
 
class FatherThread implements Runnable {
     
    public void run() {
     
        System.out.println("爸爸想抽烟,发现烟抽完了");
        System.out.println("爸爸让儿子去买包红塔山");
        Thread son = new Thread(new SonThread());
        son.start();
        System.out.println("爸爸等儿子买烟回来");
        try {
     
            son.join();
        } catch (InterruptedException e) {
     
            e.printStackTrace();
            System.out.println("爸爸出门去找儿子跑哪去了");
            // 结束JVM。如果是0则表示正常结束;如果是非0则表示非正常结束
            System.exit(1);
        }
        System.out.println("爸爸高兴的接过烟开始抽,并把零钱给了儿子");
    }
}
 
class SonThread implements Runnable {
     
    public void run() {
     
        System.out.println("儿子出门去买烟");
        System.out.println("儿子买烟需要10分钟");
        try {
     
            for (int i = 1; i <= 10; i++) {
     
                System.out.println("第" + i + "分钟");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
        System.out.println("儿子买烟回来了");
    }
}

多线程之深入浅出_第4张图片

获取线程基本信息的方法

多线程之深入浅出_第5张图片
线程的常用方法一

public class TestThread {
     
    public static void main(String[] argc) throws Exception {
     
        Runnable r = new MyThread();
        Thread t = new Thread(r, "Name test");//定义线程对象,并传入参数;
        t.start();//启动线程;
        System.out.println("name is: " + t.getName());//输出线程名称;
        Thread.currentThread().sleep(5000);//线程暂停5分钟;
        System.out.println(t.isAlive());//判断线程还在运行吗?
        System.out.println("over!");
    }
}
class MyThread implements Runnable {
     
    //线程体;
    public void run() {
     
        for (int i = 0; i < 10; i++)
            System.out.println(i);
    }
}

线程的优先级

一.处于就绪状态的线程,会进入“就绪队列”等待JVM来挑选。
二.线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。
三.使用下列方法获得或设置线程对象的优先级
int getPriority();
void setPriority(int newPriority);
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。

public class TestThread {
     
    public static void main(String[] args) {
     
        Thread t1 = new Thread(new MyThread(), "t1");
        Thread t2 = new Thread(new MyThread(), "t2");
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}
class MyThread extends Thread {
     
    public void run() {
     
        for (int i = 0; i < 10; i++) {
     
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

线程同步

一.什么是线程同步?
线程同步的本质是一种等待机制,在处理多线程问题中,多个线程同时访问一个对象,有些甚至想修改这个对象,假如我们不做人很处理,就会出现数据不正确(线程不安全)的问题,解决思路:多个需要同时访问这个对象的线程进入这个对象的等待池形成队列,等前面的线程处理完毕后,下一个线程再使用

二. 实现线程同步
使用synchronized关键字;它包括两种用法:synchronized 方法synchronized 块
1.synchronized 方法
通过在方法声明中加入 synchronized关键字来声明,语法如下:

public  synchronized  void accessVal(int newVal);

synchronized 方法控制对“对象的类成员变量”的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
2.synchronized块
相比起synchronized 方法的优势:精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。

synchronized(syncObject)
   {
      
   //允许访问控制的代码 
   }

自定义一个类测试synchronized 方法,代码如下

/**
 * 线程安全:在并发的时候保证数据的正确性,效率尽可能的高
 * 1。采用synchronized 同步方法 (锁的是this)
 * @author Zrd
 */
public class SynchronizeTest01 {
     
    public static void main (String[] args) {
     
        SafeWeb12306 safeWeb12306 = new SafeWeb12306();
        new Thread(safeWeb12306,"线程一").start();
        new Thread(safeWeb12306,"线程二").start();
    }
}

/**
 * 定义一个抢票类,体会同步方法
 */
class SafeWeb12306 implements Runnable{
     
    /**
     * 票数
     */
    private int votes=10;
    /**
     * 标识 判断是否还有票
     */
    private boolean flag=true;
    @Override
    public void run () {
     
        while (flag){
     
            test();
        }
    }
    public synchronized  void test(){
     
        /**
         * 当票数<=0 时候 直接跳出方法
         */
        if(votes<=0){
     
            flag=false;
            return;
        }
        //模拟延时
        try {
     
            Thread.sleep(200);
        }catch (InterruptedException e){
     //阻塞异常
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+votes--);
    }
}

自定义一个synchronized块案例,代码如下:

/**
 * 线程安全
 * 2.采用synchronized(obj){} 同步块直接锁资源,提高性能
 * @author Zrd
 */
public class SynchronizeTest02 {
     
    public static void main (String[] args) {
     
        Account account = new Account(150, "总账户");
        Withdrawal w1 = new Withdrawal(account, "一号操作员", 30);
        Withdrawal w2 = new Withdrawal(account, "二号操作员", 30);
        Withdrawal w3 = new Withdrawal(account, "三号操作员", 90);
        Withdrawal w4 = new Withdrawal(account, "四号操作员", 90);
        new Thread(w1).start();
        new Thread(w2).start();
        new Thread(w3).start();
        new Thread(w4).start();

    }
}
/**
 * 存款账户类
 */
class Account{
     
    /**
     * 余额
     */
     int balance;
    /**
     * 账户姓名
     */
     String accountName;

    public Account () {
     
    }

    public Account (int balance, String accountName) {
     
        this.balance = balance;
        this.accountName = accountName;
    }
}
/**
 * 模拟取款类
 */
class Withdrawal implements Runnable{
     
    /**
     * 账户对象
     */
    private Account account;
    /**
     * 操作员
     */
    private String operator;
    /**
     * 取走的金额
     */
    private int fundsWithdrawn;

    public Withdrawal (Account account, String operator, int fundsWithdrawn) {
     
        this.account = account;
        this.operator = operator;
        this.fundsWithdrawn = fundsWithdrawn;
    }

    public Withdrawal () {
     
    }

    /**
     * 使用synchronized块锁账户资源 account
     */
    @Override
    public void run () {
     
        //采取优化,当余额<0直接跳出方法, 不需要去锁资源 ,提高性能
        if(account.balance-fundsWithdrawn<0){
     
            return;
        }
        /**
         * 注意synchronized只能锁住一个对象
         * 当需要同时锁多个对象,将对象进行包装,锁住包装类
         */
        synchronized (account){
     
            if(account.balance-fundsWithdrawn<0){
     
                return;
            }else {
     
                System.out.println(operator+"取走了"+account.accountName+" "+fundsWithdrawn+"元,剩下"+(account.balance-fundsWithdrawn));
                //总账户的钱减去取走的钱
                account.balance=account.balance-fundsWithdrawn;
            }

        }
    }
}

死锁的出现和解决方案

一.什么时候会出现死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步块需要同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题

死锁的解决方案
出现死锁一般是锁中嵌套锁,解决的方案是:将锁从另外一个锁中解套出来

下面编写一个案例体会死锁的出现,和解决方案

/**
 * 死锁:过多的同步可能造成相互不释放资源
 * 从而相互等待,一般发生与同步块中持有多个对象的锁
 * 解决方案:不要在锁中嵌套锁,解套
 * @author Zrd
 */
public class DeadLock {
     
    public static void main (String[] args) {
     
        for(int i=0;i<100;i++){
     
            MakeUp g1 = new MakeUp(true, "一号女孩");
            MakeUp g2 = new MakeUp(false, "二号女孩");
            new Thread(g1).start();
            new Thread(g2).start();
        }

    }
}

/**
 * 口红类
 */
class Lipstick{
     

}

/**
 * 镜子类
 */
class Mirror{
     

}
/**
 * 化妆
 */
class MakeUp implements Runnable{
     
    /**
     * 口红对象,采用static 保证只有一只口红
     * 对static对象初始化
     */
    static Lipstick lipstick=new Lipstick();
    /**
     * 镜子对象,采用static 保证只有一面镜子
     */
    static Mirror mirror=new Mirror();
    /**
     * true->选择口红,false->选择镜子
     */
    boolean choice;
    /**
     * 化妆女孩对象
     */
    String girl;

    public MakeUp () {
     
    }

    public MakeUp (boolean choice, String girl) {
     
        this.choice = choice;
        this.girl = girl;
    }

    /**
     *包含两个案例一个会出现死锁,一个是基于对死锁的解决
     */
    @Override
    public void run () {
     
        /**
         * 死锁案例
         */
        try {
     
            makeUpDeadLock();
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }


        /**
         * 解决后的案例
         */
        try {
     
            makeUpDeadLockSolved();
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
    }
    /**
     * 死锁案例
     * 化妆过程 锁中嵌套了锁,容易出现死锁
     */
    private void makeUpDeadLock() throws InterruptedException {
     
        if (choice){
     
            //获得口红锁
            synchronized (lipstick){
     
                System.out.println(this.girl+"获得口红");
                //1秒后 ,获取镜子
                Thread.sleep(1000);
                //获得镜子锁
                synchronized (mirror){
     
                    System.out.println(this.girl+"获得镜子");
                }
            }
        }else {
     
            //获得镜子锁
            synchronized (mirror){
     
                System.out.println(this.girl+"获得镜子");
                //1秒后 ,获取口红
                Thread.sleep(1200);
                //获得口红锁
                synchronized (lipstick){
     
                    System.out.println(this.girl+"获得口红");
                }
            }

        }
    }
    /**
     * 解决死锁案例
     * 思路,解套
     */
    private void makeUpDeadLockSolved() throws InterruptedException {
     
        if (choice){
     
            //获得口红锁
            synchronized (lipstick){
     
                System.out.println(this.girl+"获得口红");
            }
            //1秒后 ,获取镜子
            Thread.sleep(1000);
            //获得镜子锁
            synchronized (mirror){
     
                System.out.println(this.girl+"获得镜子");
            }
        }else {
     
            //获得镜子锁
            synchronized (mirror){
     
                System.out.println(this.girl+"获得镜子");
            }
            //1秒后 ,获取口红
            Thread.sleep(1000);
            //获得口红锁
            synchronized (lipstick){
     
                System.out.println(this.girl+"获得口红");
            }
        }
    }
}

解决并发的几种模式(笔者还没开始学juc 暂时写一种 后续学到了再做补充)

一 生产者于消费者模式
两种方式实现
1.管程法:通过定义一个缓冲容器判断缓冲容器中空间存放情况,有空间生产者就生产数据,有数据消费者就可以进行消费,不能的话就线程等待Thread.wait(),生产和消费之间可以相互通知Thread.notiflyAll()
下面编写一个案例体会管程法:

package 多线程.并发.几种解决并发的模式;

/**
 * 生产者与消费者实现方式一:管程法
 * 解决的问题:1.什么时候能生产,2什么时候能消费
 * 借助缓冲容器
 * @author Zrd
 */
public class ManagementMethod {
     
    public static void main (String[] args) {
     
        BufferContainer bufferContainer = new BufferContainer();
        new Thread(new Producer(bufferContainer)).start();
        new Thread(new Consumer(bufferContainer)).start();
    }
}

/**
 * 生产者
 */
class Producer implements Runnable{
     
    BufferContainer bufferContainer;

    public Producer (BufferContainer bufferContainer) {
     
        this.bufferContainer = bufferContainer;
    }

    @Override
    public void run () {
     
        //生产数据
        for(int i=0;i<100;i++){
     
            bufferContainer.push(new Data(i));
            System.out.println("生产了"+i+"数据");
        }

    }
}

/**
 * 消费者
 */
class Consumer implements Runnable{
     
    BufferContainer bufferContainer;

    public Consumer (BufferContainer bufferContainer) {
     
        this.bufferContainer = bufferContainer;
    }
    @Override
    public void run () {
     
        //消费数据
        for(int i=0;i<20;i++){
     
            bufferContainer.pop();
            System.out.println("消费了"+i+"数据");
        }
    }
}

/**
 * 数据缓冲区
 * 本质上是定义一个缓冲容器
 */
class BufferContainer{
     
    /**
     * 定义一个大小为10的Data数组,用作数据缓存容器
     */
    Data[] datas=new Data[10];
    /**
     * 计数器
     */
    int count=0;

    /**
     * 存数据
     */
    public synchronized void push(Data data)  {
     
        /**
         * 什么时候能生产数据,容器中存在空间
         * 当容器中不存在空间,只能等待
         */
        if(count==datas.length){
     
            //this.wait() 当前线程处于阻塞状态,等待消费者的通知
            try {
     
                this.wait();
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        }
        //容器有空间可以消费
        datas[count]=data;
        count++;
        //通知消费者消费
        this.notifyAll();
    }

    /**
     *获取数据
     */
    public synchronized Data pop()  {
     
        /**
         * 何时能消费?
         * 判断容器中是否存在数据
         * 容器中没有数据,只能等待
         */
        if(count==0){
     
            //this.wait() 当前线程处于阻塞状态,等待生产者的通知
            try {
     
                this.wait();
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        }
        //存在数据可以消费
        count--;
        //通知生产者生产
        this.notifyAll();
        return datas[count];
    }

}
/**
 * 数据
 */
class Data{
     
    /**
     * 加入一个成员 好区别
     */
    int i;

    public Data (int i) {
     
        this.i = i;
    }
    public Data () {
     

    }
}

2.信号灯法:通过定义一个标识位*,控制生产和消费的实现
下面编写一个案例体会信号灯法:

package 多线程.并发.几种解决并发的模式;

import java.lang.reflect.TypeVariable;

/**
 * 生产者与消费者实现方式二:信号灯法
 * 解决的问题:1.什么时候能生产,2什么时候能消费
 * 借助标识位
 * @author Zrd
 */
public class SemaphoreMethod {
     
    public static void main (String[] args) {
     
        Tv tv = new Tv();
        new Thread(new Actor(tv)).start();
        new Thread(new Audience(tv)).start();
    }
}

/**
 * 电视类
 * 演员表演 和 观众观看
 */
class Tv{
     
    /**
     * 选择做的事情
     */
    String voice;
    /**
     * true->演员表演,false->观众观看
     */
    boolean flag;

    /**
     * 演员表演(生产)
     */
    public synchronized void play(){
     
        /**
         * 借助标识位,控制能否表演
         * 等待
         */
        if(!flag){
     
            try {
     
                this.wait();
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        }
        //开始表演(生产)
        System.out.println(this.voice);
        //改变标识位
        flag=false;
        //通知观看(消费) 唤醒等待的线程
        this.notifyAll();
    }
    /**
     * 观众观看(消费)
     */
    public synchronized void watch(){
     
        /**
         * 借助标识位,控制能否观看
         * 等待
         */
        if(flag){
     
            try {
     
                this.wait();
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        }
        //开始观看(消费)
        System.out.println(this.voice);
        //改变标识位
        flag=true;
        //通知生产(生产) 唤醒等待的线程
        this.notifyAll();
    }
}
class Actor implements Runnable{
     
    /**
     * 操作tv对象
     */
    Tv tv;

    public Actor (Tv tv) {
     
        this.tv = tv;
    }

    @Override
    public void run () {
     
        for(int i=0;i<100;i++){
     
            tv.voice="生产(演员)"+i;
            tv.play();
        }
    }
}
class Audience implements Runnable{
     
    /**
     * 操作tv对象
     */
    Tv tv;

    public Audience (Tv tv) {
     
        this.tv = tv;
    }

    @Override
    public void run () {
     
        for(int i=0;i<20;i++){
     
            tv.voice="消费(观众)"+i;
            tv.watch();
        }
    }
}

你可能感兴趣的:(JAVA基础篇,多线程,java,并发编程)