Java多线程学习(基础篇) - Go语言中文社区

Java多线程学习(基础篇)


1. java对线程的支持

java对线程的支持主要体现在Thread类以及Runable接口上,他们都位于java.lang包下,无论是Thread类还是Runable接口,它们都有public void run()方法,这个run方法为我们提供了线程实际工作时的代码,换句话说,我们的逻辑代码就可以写在run方法体中。

那么什么时候该用Thread,什么时候该用Runable呢
继承Thread实现的模式是 定义多个线程,各自完成各自的任务. 实现Runnable实现的模式是 定义多个线程,实现一个任务.其实在实现一个任务用多个线程来做也可以用继承Thread类来实现只是比较麻烦,一般我们用实现Runnable接口来实现,简洁明了。大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。

2. 线程的常用方法
这里写图片描述
3. 如何正确地停止线程
不要调用线程的stop()方法来停止线程,调用stop方法停止线程会有一种戛然而止的感觉,你并不知道它是执行到哪一步停止的,也不知道还有什么逻辑没执行完。应该设置一个结束标志来结束线程。也不要调用interrupt()方法来停止线程,因为interrupt()方法的初衷并不是用于停止线程的,而是用于中断线程。
4 interrupt
① interrupt的作用就是中断线程,调用interput()方法会设置线程的中断状态。
② 然后调用线程的interrupted()【这是一个静态方法】方法或isInterrupted()【这是一个实例方法】方法将会返回一个boolean值(线程的中断状态)。
③ 当一个线程,调用join,sleep方法而被阻塞时,会使线程的中断状态被清除,然后后面调用interrupted()或isInterrupted()方法都不能得到一个正确的结果,并且当前线程会收到一个InterruptedException异常,这也就是为什么我们需要在代码sleep方法和join方法中进行try catch的原因。

    package com.glassbox.thread.demo;

/**
 * @auther xiehuaxin
 * @create 2018-08-08 10:16
 * @todo
 */
public class InterruptTest extends Thread {
    public static void main(String[] args) {
        System.out.println("begin>>>>>>>>>>>>>>>");
        Thread interruptTest = new InterruptTest();
        interruptTest.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("interrupt Thread>>>>>>>");
        interruptTest.interrupt();
        System.out.println("stop>>>>>>>>>>>>>>>>");
    }

    @Override
    public void run() {
        while (true) {
            System.out.println("Thread is running...");
            /**
             * 这里用这种方式代替sleep是因为调用sleep的时候会改变当前线程的“中断状态”,从而抛出一个InterreptException异常
             */
            long time = System.currentTimeMillis();
            while (System.currentTimeMillis() - time < 1000) {

            }
        }
    }
}

输出: 可以看到,当调用interrupt方法后,线程仍在输出

begin>>>>>>>>>>>>>>>
Thread is running...
Thread is running...
Thread is running...
Thread is running...
interrupt Thread>>>>>>>
stop>>>>>>>>>>>>>>>>
Thread is running...
Thread is running...
Thread is running...
Thread is running...

可以把isInterrupted()的返回结果当做结束标志

   @Override
    public void run() {
        while (!this.isInterrupted()) {
            System.out.println("Thread is running...");
            /**
             * 这里用这种方式代替sleep是因为调用sleep的时候会改变当前线程的“中断状态”,从而抛出一个InterreptException异常
             */
            long time = System.currentTimeMillis();
            while (System.currentTimeMillis() - time < 1000) {

            }
        }
    }

5. 线程中的能量不守恒
“争用条件”:当多个线程同时共享访问同一数据(内存区域)时,每个线程都尝试操作该数据,从而导致数据被破坏(corrupted),这种现象称为争用条件。

package com.glassbox.thread.demo2;

/**
 * @auther xiehuaxin
 * @create 2018-08-08 10:52
 * @todo
 */

/**
 * 宇宙的能量系统
 * 遵循能量守恒定律
 * 能量不会凭空产生也不会凭空消失,只会从一处转移到另一处
 */
public class EnergySystem {

    //能量盒子,能存储能量的地方
    private final double[] energyBox;

    /**
     *
     * @param n 能量盒子的数量
     * @param initalEnergy 每个能量盒子初始含有的能量值
     */
    public EnergySystem(int n, double initalEnergy) {
        this.energyBox = new double[n];
        for (int i = 0; i < energyBox.length; i++) {
            energyBox[i] = initalEnergy;

        }
    }

    /**
     * 能量的转移,从一个盒子转移到另外一个盒子
     * @param from 能量源
     * @param to 能量终点
     * @param amount 要转移的能量值
     */
    public void transfer(int from, int to, double amount) {

        if(energyBox[from] < amount) {
            return;
        }
        System.out.println(Thread.currentThread().getName());
        energyBox[from] -= amount;
        System.out.printf("从%d转移%10.2f单位能量到%d", from, amount, to);
        energyBox[to] += amount;
        System.out.printf("能量总和:%10.2f%n", getTotalEnergies());
    }

    /**
     * 获取能量世界的能量总和
     * @return
     */
    private double getTotalEnergies() {
        double sum = 0;
        for (double amount : energyBox) {
            sum += amount;
        }
        return sum;
    }

    /**
     * 返回能量盒子的长度
     * @return
     */
    public int getBoxAmount() {
        return energyBox.length;
    }
}
package com.glassbox.thread.demo2;

/**
 * @auther xiehuaxin
 * @create 2018-08-08 11:48
 * @todo
 */
public class EnergyTransferTask implements Runnable {

    //共享的能量世界
    private EnergySystem energySystem;
    //能量转移的能源盒子下班
    private int fromBoxIndex;
    //单次能量转移最大单元
    private double maxAmount;
    //最大休眠时间(毫秒)
    private int DELAY = 10;

    public EnergyTransferTask(EnergySystem energySystem, int fromBoxIndex, double max) {
        this.energySystem = energySystem;
        this.fromBoxIndex = fromBoxIndex;
        this.maxAmount = max;
    }

    @Override
    public void run() {
        try {
            while (true) {
                int toBox = (int) (energySystem.getBoxAmount() * Math.random());
                double amount = maxAmount * Math.random();
                energySystem.transfer(fromBoxIndex, toBox, amount);
                Thread.sleep((int) (DELAY * Math.random()));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.glassbox.thread.demo2;

/**
 * @auther xiehuaxin
 * @create 2018-08-08 11:59
 * @todo
 */
public class EnergySystemTest {
    //要构建的能量世界的盒子的数量
    public static final int BOX_AMOUNT = 100;
    //每个盒子的初始能量
    public static final double INITIAL_ENERGY = 1000;

    public static void main(String[] args) {
        EnergySystem energySystem = new EnergySystem(BOX_AMOUNT,INITIAL_ENERGY);
        for (int i = 0; i < BOX_AMOUNT; i++) {
            EnergyTransferTask task = new EnergyTransferTask(energySystem,i,INITIAL_ENERGY);
            Thread thread = new Thread(task,"TransferThread:" + i);
            thread.start();
        }
    }
}

输出

38转移    174.83单位能量到0能量总和: 100000.00
TransferThread:3838转移     27.28单位能量到9能量总和: 100000.00
TransferThread:52
TransferThread:9898转移    584.27单位能量到49能量总和:  99390.63
TransferThread:9898转移    126.47单位能量到20能量总和:  99390.63
TransferThread:9898转移    264.87单位能量到98能量总和:  99390.63
TransferThread:7474转移    873.16单位能量到42能量总和:  99390.63
TransferThread:5050转移    439.71单位能量到47能量总和:  99390.63
TransferThread:7070转移     29.80单位能量到34能量总和:  99390.63
能量总和: 100000.00
TransferThread:7575转移    377.40单位能量到78能量总和:  99390.63
TransferThread:9595转移     74.24单位能量到76能量总和:  99390.63
能量总和: 100000.00
TransferThread:7171转移    285.92单位能量到10能量总和:  99390.63
TransferThread:8383转移    778.90单位能量到66能量总和:  99390.63
TransferThread:4646转移    515.10单位能量到63能量总和:  99390.6352转移    609.37单位能量到40能量总和: 100000.00

6. 那么如何保持能量守恒呢
互斥与同步
互斥:字面意思是互相排斥,实际含义是,同一时间只能有一个线程对临界区或关键数据进行操作(通过锁)。
同步:线程间的一种通信机制。一个线程它可以做一件事情,然后它可以用某种方式告诉别的线程它已经做完了(notify,wait,notifyAll)。

package com.glassbox.thread.demo2;

/**
 * @auther xiehuaxin
 * @create 2018-08-08 10:52
 * @todo
 */

/**
 * 宇宙的能量系统
 * 遵循能量守恒定律
 * 能量不会凭空产生也不会凭空消失,只会从一处转移到另一处
 */
public class EnergySystem {

    //能量盒子,能存储能量的地方
    private final double[] energyBox;

    //添加一个锁的对象
    private final Object lockObj = new Object();

    /**
     *
     * @param n 能量盒子的数量
     * @param initalEnergy 每个能量盒子初始含有的能量值
     */
    public EnergySystem(int n, double initalEnergy) {
        this.energyBox = new double[n];
        for (int i = 0; i < energyBox.length; i++) {
            energyBox[i] = initalEnergy;

        }
    }

    /**
     * 能量的转移,从一个盒子转移到另外一个盒子
     * @param from 能量源
     * @param to 能量终点
     * @param amount 要转移的能量值
     */
    public void transfer(int from, int to, double amount) {

        /**
         * 通过对lockObj对象加锁实现互斥
         */
        synchronized (lockObj) {
            //这里当能量不足的时候就退出,但是在退出之后,这条线程仍然有机会去获取cpu资源,从而再次要求加锁,但是加锁操作时有开销的,这样会降低系统的性能,所以当条件不满足的时候我们可以让这条线程等待,从而降低这条线程获取锁的开销
            /*if(energyBox[from] < amount) {
                return;
            }*/

            /**
             * while循环,保证条件不满足时任务都会被阻挡,而不是竞争cpu资源
             */
            while (energyBox[from] < amount) {
                try {
                    //当线程调用lockObj对象的wait()方法之后,当前的线程就会被置于lockObj对象的“等待集合wait set”中
                    lockObj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


            System.out.println(Thread.currentThread().getName());
            energyBox[from] -= amount;
            System.out.printf("从%d转移%10.2f单位能量到%d", from, amount, to);
            energyBox[to] += amount;
            System.out.printf("能量总和:%10.2f%n", getTotalEnergies());
            //唤醒所有在lockObj对象上等待的线程
            lockObj.notifyAll();
        }

    }

    /**
     * 获取能量世界的能量总和
     * @return
     */
    private double getTotalEnergies() {
        double sum = 0;
        for (double amount : energyBox) {
            sum += amount;
        }
        return sum;
    }

    /**
     * 返回能量盒子的长度
     * @return
     */
    public int getBoxAmount() {
        return energyBox.length;
    }
}

输出

68转移    765.10单位能量到80能量总和: 100000.00
TransferThread:6969转移    564.22单位能量到33能量总和: 100000.00
TransferThread:6464转移    941.32单位能量到96能量总和: 100000.00
TransferThread:9696转移    511.90单位能量到74能量总和: 100000.00
TransferThread:44转移    424.99单位能量到70能量总和: 100000.00
TransferThread:77转移    429.73单位能量到33能量总和: 100000.00

7. 如何扩展java并发编程的知识
① 了解Java Memory Model
JMM描述了java线程如何通过内存进行交互的(通过这部分学习可以了解什么是happens-before原则)
happens-before
synchronized,volatile&final(java是如何通过这几个关键字来实现happens-before原则的)

② Locks对象和Condition对象(通过这两个对象可以了解如何对程序进行加锁以及同步的通信)
java锁机制和等待条件的高层实现
java.util.concurrent.locks

③ 线程安全性
原子性、可见性
java.util.concurrent.atomic(如何通过这个包来避免原子性编程的问题)
synchronized & volatile(当一个原子操作由多个语句构成时,又是如何通过synchronized实现原子性操作的)
DeadLocks

④ 多线程常用的交互模型(在java并发实现当中,有哪些类是实现了这些模型,可以供我们直接调用的)
Producer-Consumer模型
Read-Write Lock模型
Future模型
Woker Thread模型

⑤ Java5引入的并发编程工具
java.util.concurrent
线程池ExecutorService
Callable & Future
BlockingQueue

最后推荐两边书
这里写图片描述

练习巩固
题目:创建两个线程,一个线程输出1、3、5、…、99,另一个线程输出2、4、6、…、100。最后的效果是两个线程相互交替输出,达到输出1、2、3、4、…、99、100这样顺序输出的效果。
以下是我自己的实现:

package com.glassbox.thread.test;

/**
 * @auther xiehuaxin
 * @create 2018-08-08 16:08
 * @todo
 */
public class PrintNum implements Runnable {

    int i = 1;
    @Override
    public void run() {
        while (true) {
            //如果不是使用实现Runnable这种方式创建线程,这里不要用this(自己创建一个锁对象)
            synchronized (this) {
                //这里先调用锁对象的notify方法再调用wait方法是巧妙之处,大家细细体会,如果还不明白的话可以把notify方法放到wait方法后,然后运行输出结果就一目了然了
                notify();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(i <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                    i++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
package com.glassbox.thread.test;

import java.util.concurrent.*;

/**
 * @auther xiehuaxin
 * @create 2018-08-08 15:41
 * @todo
 */
public class ThreadExercise {
    /**
     * 任务队列
     */
    private static ArrayBlockingQueue workQueue = new ArrayBlockingQueue(10);

    public static void main(String[] args) {

        /*
      线程池最大数量
     */
        int maximumPoolSizeSize = 100;
        /*
      线程活动保持时间
     */
        long keepAliveTime = 1;
        /*
      线程池的基本大小
     */
        int corePoolSize = 10;
        //建议手动创建线程池并且以线程池的形式创建线程
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSizeSize, keepAliveTime, TimeUnit.SECONDS, workQueue, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("hahaThread");
                return thread;
            }
        });
        PrintNum num = new PrintNum();
        executor.execute(num);
        executor.execute(num);
    }
}
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/aimashi620/article/details/81448281
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-02-02 19:26:24
  • 阅读 ( 924 )
  • 分类:Linux

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢