首页 > 代码库 > Java编程思想(十七) —— 并发之基本概念和方法
Java编程思想(十七) —— 并发之基本概念和方法
先提前写并发这一块,前面的数组,IO,泛型,注解是知识点很多很小的东西,放到后面写。
确实如书上所说,学到这里都是顺序编程,程序所有事物任意时刻只能执行一个步骤。这远远不够,想想双11淘宝技术如果并发不牛叉的话根本处理不了这么 庞大的数据量。——《淘宝技术这十年》。技术的积累真的是很深。
作者介绍的时候最后很有趣说,学会这章之后很容易变得过分自信,如果要学好,还需要继续深入。
1)基本的线程机制
进程有3种状态,运行,就绪,阻塞。
线程有4种状态,运行,就绪,阻塞和终止。网上还有其他分法。
所谓进程和线程的联系区别,是经常问的问题,回答倒是难答。
简单说的话,一个程序至少有一个进程,而一个进程里面至少有一个线程,线程间共享进程内存空间,而进程与进程间独立。
一、线程驱动任务:
class MyThread implements Runnable{ //Runnable 源码 /*public interface Runnable { public abstract void run(); }*/ int number = 10; private static int count = 0; //之前用过,就是希望id初始化之后不再改变 private final int id = count++; public MyThread(){} public MyThread(int number){ number = number; } public String status(){ return id+" "+ (number > 0? number :"zero"); } public void run() { while(number-->0){ System.out.println(status()); Thread.yield(); } } } public class TestThread { public static void main(String[] args) { MyThread m = new MyThread(); m.run(); } }
Thread.yield()是线程调度器,其实就是说我跑过了,我把cpu让给你。
一run,马上就跑了起来。
二、用Thread驱动:
Thread(Runnable target)
Thread
object.public class TestThread { public static void main(String[] args) { Thread t = new Thread(new MyThread()); t.start(); System.out.println("late"); } }
start会为线程先做初始化操作,然后调用run方法。此时的run方法是其他线程调用,main线程继续执行,所以先打印出late。当然,如果改成t.run(),那么late就会最后打印了。
Tread的run和start有什么区别呢——java中thread的start()和run()的区别 。
按照里面的英文说法,假如我们再:
public static void main(String[] args) { Thread t = new Thread(new MyThread()); Thread t1 = new Thread(new MyThread()); t.start(); t1.start(); // t.run(); // t1.run(); System.out.println("late"); } result: start方法: 1 9 0 9 1 8 0 8 1 7 0 7 1 6 0 6 1 5 0 5 1 4 0 4 1 3 0 3 1 2 0 2 1 1 0 1 1 zero 0 zero run方法: 0 9 0 8 0 7 0 6 0 5 0 4 0 3 0 2 0 1 0 zero 1 9 1 8 1 7 1 6 1 5 1 4 1 3 1 2 1 1 1 zerorun和start是不同的,两个线程使用start方法的时候,两个是同时运行的,结果是交错的。
两个线程使用run方法的时候,运行是顺序的,哪个在前,先运行哪个。所以看到的是t的输出先。
start方法会让线程运行是异步的(asynchronously),在启动的时候,其实start调用的是MyThread的run方法,但是此时执行的是其他线程,而执行main方法的线程继续,所以先打印了df,其余两个线程在交错运行中。
run方法是同步的(synchronously),只有等它运行完才进行下一步的操作,所以df在最后打印。
2)用Executor管理Thread
java.util.concurrentInterface Executor
An object that executes submitted Runnable
tasks. An Executor is normally used instead of explicitly creating threads. For example, rather than invokingnew Thread(new(RunnableTask())).start() for each of a set of tasks, you might use:
Executor executor = anExecutor; executor.execute(new RunnableTask1()); executor.execute(new RunnableTask2()); ...
一个执行提交的Runnable任务的类。代替显式地创建线程。
ExecutorService es = Executors.newCachedThreadPool(); es.execute(new MyThread()); es.execute(new MyThread()); es.shutdown(); //es.execute(new MyThread()); shutdown之后不能提交新任务
newCachedThreadPool为每个任务都创建一个线程,
ExecutorService es = Executors.newFixedThreadPool(2); es.execute(new MyThread());newFixedThreadPool(int n)不会为每个任务都创建一个线程,而是限制了线程,需要线程的事件会到这个池里拿到线程。
newSingleThreadExecutor()的作用就和FixedThreadPool中n的值为1一样。
ExecutorService ess = Executors.newFixedThreadPool(1); ess.execute(new MyThread()); ess.execute(new MyThread()); ess.execute(new MyThread()); ess.execute(new MyThread()); ess.execute(new MyThread()); 0 9 0 8 0 7 0 6 0 5 0 4 0 3 0 2 0 1 0 zero 1 9 1 8 1 7 1 6 1 5 1 4 1 3 1 2 1 1 1 zero 2 9 2 8 2 7 2 6 2 5 2 4 2 3 2 2 2 1 2 zero 3 9 3 8 3 7 3 6 3 5 3 4 3 3 3 2 3 1 3 zero 4 9 4 8 4 7 4 6 4 5 4 4 4 3 4 2 4 1 4 zero
3)从任务中返回值
Runnable虽然执行自己的工作,但是不返回值,实现Callable接口可以返回值。
java.util.concurrent
Interface Callable<V>
Type Parameters:
V - the result type of method call
Modifier and Type | Method and Description |
---|---|
V | call() Computes a result, or throws an exception if unable to do so. |
class MyCall implements Callable<String>{ private int id; public MyCall(int id){ this.id = id; } public String call() throws Exception { return "call"+id; } } public class ExecuteCall { public static void main(String[] args) { ExecutorService es = Executors.newCachedThreadPool(); ArrayList<Future<String>> a = new ArrayList<Future<String>>(); for (int i = 0; i < 5; i++) { a.add( es.submit(new MyCall(i))); } for(Future f : a){ try { System.out.println(f.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } }
这个例子很多新东西,因为我们是要返回值,所以用了MyCall实现了Call接口,并且必须使用ExecutorService.submit方法。
<T> Future<T> | submit(Callable<T> task) Submits a value-returning task for execution and returns a Future representing the pending results of the task. |
public interface Future<V>
然后我们用了ArrayList来存储Future,通过foreach语法遍历。
V get() throws InterruptedException, ExecutionException
4)休眠
记得以前看过一个排序算法是用休眠写的,很有趣,至今还记得,就是让大的数睡久一点才输出。
Mythread的修改:
public void run() { while(number-->0){ System.out.println(status()); try { //Thread.sleep(100); TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }
Thread.sleep是老版本的写法,TimeUnit.MILLISECONDS.sleep是SE5版本后的写法。其实后者也是用了Thread.sleep。
public void sleep(long timeout) throws InterruptedException { if (timeout > 0) { long ms = toMillis(timeout); int ns = excessNanos(timeout, ms); Thread.sleep(ms, ns); } }
翻到火龙果的论坛帖子,其实后者和前者的区别就是可以变换单位,
TimeUnit.MICROSECONDS.sleep的话就是微秒,如果1000微秒的话那就是一毫秒,直接可以用TimeUnit.MILLISECONDS.sleep(1)。
这样就提供的更好的阅读性,不是有些题目会问你Thread.sleep方法里面的单位是多少吗?——毫秒。
5)优先级调度
public class TestPriority { public static void main(String[] args) { Thread t1 = new Thread(new MyThread()); Thread t2 = new Thread(new MyThread()); Thread t3 = new Thread(new MyThread()); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY); t1.start(); t2.start(); t3.start(); } }
其实调度的映射做得不是很好,setPriority有10级优先级,1到10,数字越大优先级别越大。映射不好是指4不一定比5慢。
6)让步
让步的话就是前面写到的Thread.yield,给线程调度机制的信息就是我自己已经运行的差不多了,你们谁需要就谁去用吧。
这些是线程的基本概念,优先级,睡眠,之前没用过的就是Executor和TimeUnit.MILLISECONDS.sleep。
Java编程思想(十七) —— 并发之基本概念和方法