基于上一篇文章介绍了一些关于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
由上面输出结果我们总结一下结论:
- 主线程和线程2的线程ID相同,说明直接调用run()方法不会创建新的线程,而是在主线程中直接调用run()方法,普通的方法调用
- 线程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接口的方式,都可以创建一个线程,其结果都是一样的。区别在于:
- 实现Runnable的方式需要将实现Runnable接口的类作为参数传递给Thread,然后通过Thread类调用Start()方法来创建线程
- 直接继承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
由上面输出结果我们总结一下结论:
- Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。
- 通常在使用Callable的时候,也会涉及到Future,一般配合一起使用,一个产生结果,一个拿到结果。
- 假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到最终计算结果。