线程技术讨论
一:线程的概念
多线程是个有意思的概念,一个应用程序,比如酷我音乐或者某个游戏,是一个进程,然后cpu给该进程
分配内存空间,如果电脑内存空间有限,而且运行的程序比较多时就会比较卡,一个进程可能有多个线程
执行,比如游戏中,多个任务,建筑物等都是线程在操作,在单核处理器的情况下,是并发执行的,cpu给
每个线程随机分配时间片,得到时间片的那个任务执行,由于切换的频率比较快,所以用户看起来是一起
执行的。在多核处理器的情况下,是并行的,确实可以一起执行,所以多线程的好处就是不必考虑电脑是否多核,
确实可以提高执行效率。
下面看一个简单的例子如下:
//实现倒计时功能 class CountDown implements Runnable{ private int countDown = 10; //用来计数 public static int taskCount = 0; //用来标记实例,从零开始 public final int id = taskCount++; //定义为final类型,不可修改 public CountDown(){} public CountDown(int countDown){ this.countDown = countDown; } public String getStatus(){ return "#"+id+ "("+(countDown>0?countDown:"liftOff!")+")"; } @Override public void run() { while(countDown-->0){ System.out.println(getStatus()); } } }
实现一个简单的倒计时功能,countDown用来计数,id用来标记访问实例,在main方法中调用run:
public class TestThread2 { public static void main(String[] args) { CountDown countDown = new CountDown(); countDown.run(); } }
执行结果:
#0(9) #0(8) #0(7) #0(6) #0(5) #0(4) #0(3) #0(2) #0(1) #0(liftOff!)
顺序执行,实际上上面的执行并没有涉及到线程,计数的执行都是在main线程中执行的,如果想实现多线程执行,就必须
把上面的任务依附到某个线程上。
二:创建线程的两种方式
1:继承Thread
// 创建线程方式一 class MyThread extends Thread { public void run() { for (int index = 0; index < 100; index++) { System.out.println(Thread.currentThread().getName() + ":::" + index); } } }
public class TestThread1 { public static void main(String[] args) { Thread thread = new MyThread(); thread.start(); for (int index = 0; index < 10; index++) { System.out.println(Thread.currentThread().getName() + ":::" + index); } } }
Thread-0:::0 Thread-0:::1 Thread-0:::2 main:::0 Thread-0:::3 Thread-0:::4 Thread-0:::5 Thread-0:::6 Thread-0:::7 Thread-0:::8 Thread-0:::9 main:::1 main:::2 main:::3 main:::4 main:::5 main:::6 main:::7 main:::8 main:::9
从运行结果可以看出,main线程和Thread0线程随机获取cpu时间片执行任务!
//创建线程方式二 class MyRun implements Runnable{ @Override public void run() { for (int index = 0; index < 100; index++) { System.out.println(Thread.currentThread().getName() + ":::" + index); } } }
public class TestThread1 { public static void main(String[] args) { Runnable run = new MyRun(); Thread thread = new Thread(run); thread.start(); for (int index = 0; index < 10; index++) { System.out.println(Thread.currentThread().getName() + ":::" + index); } } }
main:::0 Thread-0:::0 Thread-0:::1 main:::1 Thread-0:::2 Thread-0:::3 main:::2 Thread-0:::4 Thread-0:::5 main:::3 Thread-0:::6 main:::4 main:::5 main:::6 main:::7 Thread-0:::7 Thread-0:::8 Thread-0:::9 main:::8 main:::9
运行结果类似上面,所以也是多线程执行
三:线程池的使用
如果需要执行主线程之外的线程时,可以用上面的方式创建线程,但是如果并发的任务比较多时,频繁的创建和销毁线程会消耗过多的资源
所以最好的选择是使用线程池,在使用的时候由线程池分配,使用完成由线程池回收掉
1:没有返回值的线程池
newCachedThreadPool 、newFixedThreadPool、newSingleThreadPool这是几种常见的线程池,第一种是线程池是无界的,但是内存
的资源是有限的,一般会有限制,第二种是固定长度线程池,在创建线程池的时候就指定线程的长度,第三种只会创建一个线程,如果第二种
的参数为1就是第三种
public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); for(int i=0;i<5;i++){ service.execute(new CountDown()); } service.shutdown(); }
#0(9) #0(8) #0(7) #0(6) #0(5) #0(4) #0(3) #0(2) #0(1) #0(liftOff!) #2(9) #2(8) #1(9) #1(8) #1(7) #1(6) #1(5) #1(4) #1(3) #1(2) #1(1) #1(liftOff!) #4(9) #4(8) #4(7) #4(6) #4(5) #4(4) #4(3) #4(2) #4(1) #4(liftOff!) #2(7) #2(6) #2(5) #2(4) #2(3) #2(2) #2(1) #2(liftOff!) #3(9) #3(8) #3(7) #3(6) #3(5) #3(4) #3(3) #3(2) #3(1) #3(liftOff!)
从运行结果可以看出,随机放入5个执行任务到线程池中,然后这5个任务随机抢夺cpu的执行权
newFixedThreadPool和newCacheThreadPool的用法比较类似,cache是第一选择,如果有问题可以选择fixed,对于第三种
newSingleThreadPool,只有一个线程,如果对给它太多的任务,它会阻塞,然后顺序执行每一个任务:
public static void main(String[] args) { ExecutorService service = Executors.newSingleThreadExecutor(); for(int i=0;i<5;i++){ service.execute(new CountDown()); } service.shutdown(); }
#0(9) #0(8) #0(7) #0(6) #0(5) #0(4) #0(3) #0(2) #0(1) #0(liftOff!) #1(9) #1(8) #1(7) #1(6) #1(5) #1(4) #1(3) #1(2) #1(1) #1(liftOff!) #2(9) #2(8) #2(7) #2(6) #2(5) #2(4) #2(3) #2(2) #2(1) #2(liftOff!) #3(9) #3(8) #3(7) #3(6) #3(5) #3(4) #3(3) #3(2) #3(1) #3(liftOff!) #4(9) #4(8) #4(7) #4(6) #4(5) #4(4) #4(3) #4(2) #4(1) #4(liftOff!)
这是没有返回值的线程池的用法,这种方式在需要异步调用的时候使用,同步调用的时候需要用到有返回值的线程池
2:有返回值的线程池
如果想让多线程执行完毕后,将结果返回,则需要实现Callable而不是Runnable,重写call()方法,而不是run()方法
//创建有返回值的线程池 class MyCall implements Callable<Integer>{ private int id; public MyCall(int id){ this.id = id; } @Override public Integer call() throws Exception { return id; } }
public class TestThread3 { public static void main(String[] args) { ExecutorService exec = Executors.newFixedThreadPool(5); List<Future<Integer>> result = new ArrayList<Future<Integer>>(); for(int i=0;i<5;i++){ Future<Integer> future = exec.submit(new MyCall(i)); result.add(future); } for(Future<Integer> future:result){ Integer i; try { i = future.get(); System.out.println(i); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } finally{ exec.shutdown(); } } } }
0 1 2 3 4
多线程执行完任务后,最后将结果返回,其中get()会阻塞线程,起到计数器的作用,如果有一个线程没有执行完毕,那么就会一直阻塞
不会结束。
模拟如下:
修改call()方法代码如下:
public Integer call() throws Exception { if(3==id){ Thread.sleep(3000); } return id; }
那么在运行的时候,就会在id=3处阻塞,3后面的任务都不会执行,直到3执行结束。
今天多线程的知识就总结到这里了,后面继续......