JAVA多线程demo

基于上一篇文章介绍了一些关于JAVA多线程基础方面的理论知识,这一篇开始实际动手操作一番看看具体效果。

1、通过集成java.lang.Thread线程类来创建一个线程

package com.feizi.java.concurrency.thread;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestThread {
    public static void main(String[] args) {
        /**
         * 控制台输出结果:
         * 主线程ID是: 1
         * 名称线程2的线程ID是:1
         * 名称线程1的线程ID是:11
         * 结论:
         * 1、主线程和线程2的线程ID相同,说明直接调用run()方法不会创建新的线程,而是在主线程中直接调用run()方法,普通的方法调用
         * 2、线程1先调用start()方法,而后线程2调用run()方法,最终却线程2先于线程1输出,说明新建的线程并不会影响主线程的执行顺序
         */

        System.out.println("主线程ID是: " + Thread.currentThread().getId());

        Thread t1 = new MyThread("线程1");
        t1.start();

        Thread t2 = new MyThread("线程2");
        /*直接调用run()方法*/
        t2.run();
    }
}

/**
 * 自定义线程
 */
class MyThread extends Thread{
    /*线程名称*/
    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("名称" + name + "的线程ID是:" + Thread.currentThread().getId());
    }
}

控制台输出结果:

主线程ID是: 1
名称线程2的线程ID是:1
名称线程1的线程ID是:11

Process finished with exit code 0

由上面输出结果我们总结一下结论:

  1. 主线程和线程2的线程ID相同,说明直接调用run()方法不会创建新的线程,而是在主线程中直接调用run()方法,普通的方法调用
  2. 线程1先调用start()方法,而后线程2调用run()方法,最终却线程2先于线程1输出,说明新建的线程并不会影响主线程的执行顺序

2、通过实现java.lang.Runnable接口来创建一个线程

package com.feizi.java.concurrency.thread;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestRunnable {
    public static void main(String[] args) {
        System.out.println("主线程的ID是: " + Thread.currentThread().getId());
        MyRunnable r1 = new MyRunnable("线程1");
        Thread t1 = new Thread(r1);
        t1.start();

        MyRunnable r2 = new MyRunnable("线程2");
        Thread t2 = new Thread(r2);
        /*直接调用run()方法,并不会创建新线程*/
        t2.run();
    }
}

class MyRunnable implements Runnable{

    private String name;

    public MyRunnable(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("名字" + name + "的线程ID是: " + Thread.currentThread().getId());
    }
}

控制台输出结果:

主线程的ID是: 1
名字线程2的线程ID是: 1
名字线程1的线程ID是: 11

Process finished with exit code 0

由以上输出结果我们可以看出:

其实不管是通过继承Thread类,还是实现Runnable接口的方式,都可以创建一个线程,其结果都是一样的。区别在于:

  1. 实现Runnable的方式需要将实现Runnable接口的类作为参数传递给Thread,然后通过Thread类调用Start()方法来创建线程
  2. 直接继承Thread类的话代码更简洁,也更容易理解,但是由于JAVA被设计为只支持单继承,所以如果要继承其他类的同时需要实现线程那就只能实现Runnable接口了,这里更推荐实现Runnable接口,实现Runnable的方式更为灵活一些。

3、sleep()线程休眠

package com.feizi.java.concurrency.thread;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestSleep {
    private int i = 10;
    private Object object = new Object();

    public static void main(String[] args) {
        /**
         * 线程: Thread-0开始执行,i的值为:11
         * 线程: Thread-0准备进入休眠状态...
         * 线程: Thread-0休眠结束...
         * 线程: Thread-0继续执行,i的值为===========>:12
         * 线程: Thread-1开始执行,i的值为:13
         * 线程: Thread-1准备进入休眠状态...
         * 线程: Thread-1休眠结束...
         * 线程: Thread-1继续执行,i的值为===========>:14
         * 结论:
         * 1、当Thread-0进入休眠状态,Thread-1并没有马上执行,而是等待Thread-0休眠结束释放了对象锁才继续执行
         * 2、当调用sleep()方法时,必须捕获异常或者向上抛出异常,当线程休眠结束并不会马上执行,而是进入就绪状态,等待CPU的再次调度,调用Sleep()方法相当于是进入了阻塞状态
         */

        TestSleep testSleep = new TestSleep();
        Thread t1 = testSleep.new MyTestThread();
        t1.start();

        Thread t2 = testSleep.new MyTestThread();
        t2.start();
    }

    class MyTestThread extends Thread{
        @Override
        public void run() {
            synchronized (object){
                i++;
                System.out.println("线程: " + Thread.currentThread().getName() + "开始执行,i的值为:" + i);
                System.out.println("线程: " + Thread.currentThread().getName() + "准备进入休眠状态...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("线程: " + Thread.currentThread().getName() + "休眠结束...");
                i++;
                System.out.println("线程: " + Thread.currentThread().getName() + "继续执行,i的值为===========>:" + i);
            }
        }
    }
}

控制台输出结果:

线程: Thread-0开始执行,i的值为:11
线程: Thread-0准备进入休眠状态...
线程: Thread-0休眠结束...
线程: Thread-0继续执行,i的值为===========>:12
线程: Thread-1开始执行,i的值为:13
线程: Thread-1准备进入休眠状态...
线程: Thread-1休眠结束...
线程: Thread-1继续执行,i的值为===========>:14

Process finished with exit code 0

由上面输出结果我们总结一下结论:

1、当Thread-0进入休眠状态,Thread-1并没有马上执行,而是等待Thread-0休眠结束释放了对象锁才继续执行
2、当调用sleep()方法时,必须捕获异常或者向上抛出异常,当线程休眠结束并不会马上执行,而是进入就绪状态,等待CPU的再次调度,调用Sleep()方法相当于是进入了阻塞状态

4、join()加入线程

package com.feizi.java.concurrency.thread;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        /**
         * 执行结果:
         * main主线程: main ====> 0
         * main主线程: main ====> 1
         * 当前线程: 线程1 ===> 0
         * main主线程: main ====> 2
         * 当前线程: 线程1 ===> 1
         * 当前线程: 线程1 ===> 2
         * 当前线程: 线程1 ===> 3
         * 当前线程: 线程1 ===> 4
         * main主线程: main ====> 3
         * main主线程: main ====> 4
         * 当前线程: 线程2 ===> 0
         * 当前线程: 线程2 ===> 1
         * 当前线程: 线程2 ===> 2
         * 当前线程: 线程2 ===> 3
         * 当前线程: 线程2 ===> 4
         * main主线程: main ====> 5
         * main主线程: main ====> 6
         * main主线程: main ====> 7
         * main主线程: main ====> 8
         * main主线程: main ====> 9
         * 结论:
         * 1、使用了join()方法之后,主线程会等待子线程结束之后才会结束
         */
        Thread t1 = new MyJoinThread("线程1");
        t1.start();
//        t1.join();

        for (int i = 0; i < 10; i++){
            if(i == 5){
                Thread t2 = new MyJoinThread("线程2");
                t2.start();
                t2.join();
            }
            System.out.println("main主线程: " + Thread.currentThread().getName() + " ====> " + i);
        }
    }
}

class MyJoinThread extends Thread{
    public MyJoinThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++){
            System.out.println("当前线程: " + Thread.currentThread().getName() + " ===> " + i);
        }
    }
}

控制台输出结果:

main主线程: main ====> 0
当前线程: 线程1 ===> 0
main主线程: main ====> 1
当前线程: 线程1 ===> 1
main主线程: main ====> 2
当前线程: 线程1 ===> 2
main主线程: main ====> 3
当前线程: 线程1 ===> 3
main主线程: main ====> 4
当前线程: 线程1 ===> 4
当前线程: 线程2 ===> 0
当前线程: 线程2 ===> 1
当前线程: 线程2 ===> 2
当前线程: 线程2 ===> 3
当前线程: 线程2 ===> 4
main主线程: main ====> 5
main主线程: main ====> 6
main主线程: main ====> 7
main主线程: main ====> 8
main主线程: main ====> 9

Process finished with exit code 0

由上面输出结果我们总结一下结论:

使用了join()方法之后,主线程会等待子线程结束之后才会结束

5、yeild()让出CPU执行权限

package com.feizi.java.concurrency.thread;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestYield {
    public static void main(String[] args) {
        /**
         * 执行结果:
         * 名字: 线程1执行
         * 名字: 线程2执行
         * 线程2结果num: 1065788928计算耗时: 8毫秒
         * 线程1结果count: 1784293664计算耗时: 138毫秒
         * 结论:
         * 1、调用yield()方法是为了让当前线程让出CPU执行权限,从而可以让CPU去执行其他线程,它和sleep()方法类似同样是不会释放对象锁,
         * 但是yield()不会控制具体的交出CPU权限的时间,同时也只能让具有相同优先级的线程获得CPU执行时间的机会
         * 2、调用yield()方法并不会让当前线程进入阻塞状态,而只是进入就绪状态,只需要等待重新获取CPU的时间片,而Sleep()则会进入阻塞状态
         */
        MyYieldThread t = new MyYieldThread("线程1");
        t.start();

        MyYieldThread2 t2 = new MyYieldThread2("线程2");
        t2.start();
    }
}

class MyYieldThread extends Thread{
    private String name;

    public MyYieldThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("名字: " + name + "执行");
        long start = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 1000000; i++){
            count = count + (i + 1);
            Thread.yield();
        }

        long end = System.currentTimeMillis();
        System.out.println(name + "结果count: " + count + "计算耗时: " + (end - start) + "毫秒");
    }
}

class MyYieldThread2 extends Thread{
    private String name;

    public MyYieldThread2(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("名字: " + name + "执行");
        long start = System.currentTimeMillis();
        int num = 0;
        for (int i = 0; i < 10000000; i++){
            num += i * 8;
        }
        long end = System.currentTimeMillis();
        System.out.println(name + "结果num: " + num + "计算耗时: " + (end - start) + "毫秒");
    }
}

控制台输出结果:

名字: 线程1执行
名字: 线程2执行
线程2结果num: 1065788928计算耗时: 6毫秒
线程1结果count: 1784293664计算耗时: 133毫秒

Process finished with exit code 0

由上面输出结果我们总结一下结论:

1、调用yield()方法是为了让当前线程让出CPU执行权限,从而可以让CPU去执行其他线程,它和sleep()方法类似同样是不会释放对象锁,但是yield()不会控制具体的交出CPU权限的时间,同时也只能让具有相同优先级的线程获得CPU执行时间的机会
2、调用yield()方法并不会让当前线程进入阻塞状态,而只是进入就绪状态,只需要等待重新获取CPU的时间片,而Sleep()则会进入阻塞状态

6、带返回值的线程接口Callable

package com.feizi.java.concurrency.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestCallable {
    public static void main(String[] args) {
        //创建一个线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        //创建三个有返回值的任务
        MyCallable c1 = new MyCallable("线程1");
        MyCallable c2 = new MyCallable("线程2");
        MyCallable c3 = new MyCallable("线程3");

        Future f1 = threadPool.submit(c1);
        Future f2 = threadPool.submit(c2);
        Future f3 = threadPool.submit(c3);

        try {
            System.out.println(f1.get().toString());
            System.out.println(f2.get().toString());
            System.out.println(f3.get().toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }
    }
}

class MyCallable implements Callable{
    private String name;

    public MyCallable(String name) {
        this.name = name;
    }

    @Override
    public Object call() throws Exception {
        return name + "返回了东西...";
    }
}

控制台输出结果:

线程1返回了东西...
线程2返回了东西...
线程3返回了东西...

Process finished with exit code 0

由上面输出结果我们总结一下结论:

  1. Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。
  2. 通常在使用Callable的时候,也会涉及到Future,一般配合一起使用,一个产生结果,一个拿到结果。
  3. 假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到最终计算结果。

你可能感兴趣的:(JAVA多线程demo)