---------该文主要是自己的读书笔记,都是一些比较基础的东西。-------------
1、基本的线程机制
2、使用Executor
可以通过Executor帮忙管理Thread对象,从而简化并发编程。
主要的执行器有两种:
- CachedThreadPool:在程序执行的过程中,通常会创建与所需数量相同的线程,然后在它回收旧线程的时候停止创建新线程。优先考虑
- FixedThreadPool:一次性执行代价高昂的线程分配。
下面来看一下两者的线程分配情况:
第一种采用-CachedThreadPool创建线程:
/** * 定义一个任务 并且主动继承Runnable接口 * @author Xia */ public class LiftOff implements Runnable{ protected int countDown = 10; private static int taskCount = 0; private final int id = taskCount++; //定义一个空的构造函数 public LiftOff(){}; //用构造函数来初始化countDown public LiftOff(int countDown){ this.countDown = countDown; } //定义一个函数用于返回倒计时状态 public String status(){ return "#" + id +"("+(countDown > 0 ? countDown:"LiftOff!")+"),"; } @Override public void run() { while(countDown-->0){ System.out.println(status()); Thread.yield(); } } }
具体的创建线程:
import java.util.concurrent.*; public class CachedThreadPool { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); //创建线程池 for(int i = 0; i < 5; i++) exec.execute(new LiftOff()); exec.shutdown(); } }
采用第二种提前分配线程个数的情况:
import java.util.concurrent.*; public class FixedThreadPool { public static void main(String[] args) { // 默认创建指定的线程个数 ExecutorService exec = Executors.newFixedThreadPool(5); for(int i = 0; i < 5; i++) exec.execute(new LiftOff()); exec.shutdown(); } }
3、线程的休眠——sleep()
下面看一下休眠的例子:
package com.hone.concurrent01basica; /** * 测试sleep()方法 */ import java.util.concurrent.*; public class SleepingTask extends LiftOff { public void run() { try { while(countDown-- > 0) { System.out.print(status()); //线程休眠1s- TimeUnit.MILLISECONDS.sleep(1000); } } catch(InterruptedException e) { System.err.println("Interrupted"); } } public static void main(String[] args) { //创建随机的线程池 ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; i++) exec.execute(new SleepingTask()); exec.shutdown(); } }
4、线程的优先级别——setPriority()
优先级别后,调度器倾向于让优先级最高的线程执行。
下面看一下下面的设置优先级别的例子:
package com.hone.concurrent01basica; /** * 显示优先级别的利用 */ import java.util.concurrent.*; public class SimplePriorities implements Runnable { private int countDown = 5; private volatile double d; // No optimization private int priority; public SimplePriorities(int priority) { this.priority = priority; } //重写toString()方法,用于打印线程的方法名称 public String toString() { return Thread.currentThread() + ": " + countDown; } public void run() { Thread.currentThread().setPriority(priority); while(true) { // An expensive, interruptable operation: for(int i = 1; i < 100000; i++) { d += (Math.PI + Math.E) / (double)i; if(i % 1000 == 0) Thread.yield(); } System.out.println(this); if(--countDown == 0) return; } } public static void main(String[] args) { //创建线程池 ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; i++) //优先级别最低 exec.execute( new SimplePriorities(Thread.MIN_PRIORITY)); //优先级别最高 exec.execute( new SimplePriorities(Thread.MAX_PRIORITY)); exec.shutdown(); } }
打印结果:可以看到,优先级别高的线程先输出
Thread[pool-1-thread-6,10,main]: 5 Thread[pool-1-thread-5,1,main]: 5 Thread[pool-1-thread-4,1,main]: 5 Thread[pool-1-thread-3,1,main]: 5 Thread[pool-1-thread-6,10,main]: 4 Thread[pool-1-thread-2,1,main]: 5 Thread[pool-1-thread-1,1,main]: 5 Thread[pool-1-thread-6,10,main]: 3 Thread[pool-1-thread-5,1,main]: 4 Thread[pool-1-thread-4,1,main]: 4 Thread[pool-1-thread-3,1,main]: 4 Thread[pool-1-thread-2,1,main]: 4 Thread[pool-1-thread-6,10,main]: 2 Thread[pool-1-thread-1,1,main]: 4 Thread[pool-1-thread-5,1,main]: 3 Thread[pool-1-thread-6,10,main]: 1 Thread[pool-1-thread-4,1,main]: 3 Thread[pool-1-thread-3,1,main]: 3 Thread[pool-1-thread-2,1,main]: 3 Thread[pool-1-thread-1,1,main]: 3 Thread[pool-1-thread-4,1,main]: 2 Thread[pool-1-thread-5,1,main]: 2 Thread[pool-1-thread-2,1,main]: 2 Thread[pool-1-thread-3,1,main]: 2 Thread[pool-1-thread-1,1,main]: 2 Thread[pool-1-thread-5,1,main]: 1 Thread[pool-1-thread-4,1,main]: 1 Thread[pool-1-thread-2,1,main]: 1 Thread[pool-1-thread-3,1,main]: 1 Thread[pool-1-thread-1,1,main]: 1
5、让步——yield()
作用:建议相同优先级别的其他线程可以开始运行了。
6、加入一个线程——join(xxx)
作用:如果p线程在另一个t线程上调用t.join(),则p线程会被挂起,直到目标线程t结束才恢复。
package com.hone.concurrent01basica; /** * 理解join() */ //线程1 class Sleeper extends Thread { private int duration; public Sleeper(String name, int sleepTime) { super(name); duration = sleepTime; start(); } public void run() { try { sleep(duration); //线程休眠 } catch(InterruptedException e) { System.out.println(getName() + " was interrupted. " + "isInterrupted(): " + isInterrupted()); return; } System.out.println(getName() + " has awakened"); } } //线程1 class Joiner extends Thread { private Sleeper sleeper; public Joiner(String name, Sleeper sleeper) { super(name); this.sleeper = sleeper; start(); } public void run() { try { sleeper.join(); } catch(InterruptedException e) { System.out.println("Interrupted"); } //打印线程名称 System.out.println(getName() + " join completed"); } } public class Joining { public static void main(String[] args) { Sleeper sleepy = new Sleeper("Sleepy", 1500), grumpy = new Sleeper("Grumpy", 1500); Joiner dopey = new Joiner("Dopey", sleepy), doc = new Joiner("Doc", grumpy); grumpy.interrupt(); } }
//打印输出 Grumpy was interrupted. isInterrupted(): false Doc join completed Sleepy has awakened Dopey join completed
7、捕获异常——Executor()
2、共享受限资源
1、解决共享资源的竞争——synchronized
为什么要解决资源的竞争,防止两个任务访问相同的资源,会造成冲突,因此需要在资源被访问的时候加上锁,这种又称为同步。
其中java提供synchronized关键字,为防止资源冲突提供了内置支持,当对象上调用其任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能调用。
那么什么时候应该写同步呢?
如果你正在写一个变量,他可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么必须适应同步,并且,读写线程都必须使用相同的监控器锁同步。
也就是说每次访问临界共享资源的方法都必须被同步,否则他们不会正确的工作。
2、使用显示的Lock对象
Lock也可以显示的产生互斥机制,Lock对象必须被显示的创建、锁定、释放。但是它与synchronized关键字造成的内置关键字相比,代码缺乏有雅兴。
import java.util.concurrent.locks.*; public class MutexEvenGenerator extends IntGenerator { private int currentEvenValue = 0; private Lock lock = new ReentrantLock(); public int next() { lock.lock(); try { ++currentEvenValue; Thread.yield(); // Cause failure faster ++currentEvenValue; return currentEvenValue; } finally { lock.unlock(); } } public static void main(String[] args) { EvenChecker.test(new MutexEvenGenerator()); } }
public abstract class IntGenerator { private volatile boolean canceled = false; public abstract int next(); // Allow this to be canceled: public void cancel() { canceled = true; } public boolean isCanceled() { return canceled; } }
两者区别:
当使用synchronized关键字的时候,需要写的代码量更少,并且用户错误出现的可能性会降低,因此通常只有在解决特殊问的时候才会使用显示的Lock对象,例如,在使用synchronized关键字时,某些事物失败了,那么就会抛出一个异常。但是你没有机会去做任何的清理工作,以维护系统使其处于良好的状态,那么lock对象就可以进行维护了。
3、原子性和易变性(待续)
4、临界区
有时候,只是希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法,通过这个方式分离的代码段称为临界区,也成为同步控制块。
这种方法有时候可以提供线程的性能。
5、在其他对象上同步
比如:两个任务 可以同时进入同一个对象,只要这个对象上的方法是 在不同的锁上同步即可:
//: concurrency/SyncObject.java //演示在其他对象上上锁 import static net.mindview.util.Print.*; class DualSynch { private Object syncObject = new Object(); //锁上该方法 public synchronized void f() { for(int i = 0; i < 5; i++) { print("f()"); Thread.yield(); } } public void g() { //利用synchronized(this),对于该方法中的某个字段进行同步。 synchronized(syncObject) { for(int i = 0; i < 5; i++) { print("g()"); Thread.yield(); } } } } public class SyncObject { public static void main(String[] args) { final DualSynch ds = new DualSynch(); new Thread() { public void run() { ds.f(); } }.start(); ds.g(); } } /* Output: (Sample) g() f() g() f() g() f() g() f() g() f() *///:~
6、线程本地存储——ThreadLocal
原因:防止任务在共享资源上产生冲突的第二种方式就是根除对变量的共享。
线程本地存储的一种自动化机制,可以为使用相同对象的每个不同的线程都创建不同的存储。
代码演示:
package com.hone.concurrent01basica; // ThreadLocal可以自动给每一个线程分配静态的存储内存 import java.util.concurrent.*; import java.util.*; class Accessor implements Runnable { private final int id; public Accessor(int idn) { id = idn; } public void run() { while(!Thread.currentThread().isInterrupted()) { ThreadLocalVariableHolder.increment(); System.out.println(this); Thread.yield(); } } //重写toString()方法,打印线程存储的本地变量数 public String toString() { return "#" + id + ": " + ThreadLocalVariableHolder.get(); } } public class ThreadLocalVariableHolder { private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() { private Random rand = new Random(47); protected synchronized Integer initialValue() { return rand.nextInt(10000); } }; public static void increment() { value.set(value.get() + 1); } public static int get() { return value.get(); } public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; i++) exec.execute(new Accessor(i)); TimeUnit.SECONDS.sleep(3); // Run for a while exec.shutdownNow(); // All Accessors will quit } } /* Output: (Sample) #0: 9259 #1: 556 #2: 6694 #3: 1862 #4: 962 #0: 9260 #1: 557 #2: 6695 #3: 1863 #4: 963 ... *///:~
每个线程都分配了自己的存储,因为它们每个都需要跟踪自己的计数值,即便只有一个ThreadLocalVariableHolder对象。
3、终结任务
下面先来说一下线程的状态:
- 新建(new):当线程被创建的时候,它只会暂时处于这个状态。
- 就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。
- 阻塞(Blocked):线程能够运行,但是某个条件阻止它的运行。
- 死亡(dead):处于死亡的线程或者即将处于死亡的线程将不再可以调度。
一个线程如何进入阻塞状态呢?
- 通过调用sleep(milliseconds)使任务进入休眠状态
- 通过调用wai()使线程挂起。
- 任务在等待某个输入或者输出
- 任务视图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获得了这个锁。
2、线程中断
在Thread类中可以通过interrupt()方法终止阻塞的任务,但是为了调用Interrupt()必须持有Thread对象。
3、被互斥所阻塞
如果尝试在一个对象上调用其synchronized方法,而这个对象的锁已经被其他任务获得,那么调用的任务将被挂起,直到这个锁可以获得。
import static net.mindview.util.Print.*; public class MultiLock { public synchronized void f1(int count) { if(count-- > 0) { print("f1() calling f2() with count " + count); f2(count); } } //在f2()中调用f1()直到count = 0 public synchronized void f2(int count) { if(count-- > 0) { print("f2() calling f1() with count " + count); f1(count); } } public static void main(String[] args) throws Exception { final MultiLock multiLock = new MultiLock(); new Thread() { public void run() { multiLock.f1(10); } }.start(); } } /* Output: f1() calling f2() with count 9 f2() calling f1() with count 8 f1() calling f2() with count 7 f2() calling f1() with count 6 f1() calling f2() with count 5 f2() calling f1() with count 4 f1() calling f2() with count 3 f2() calling f1() with count 2 f1() calling f2() with count 1 f2() calling f1() with count 0 *///:~
4、线程之间的协作
上面的介绍中知道,如果两个任务在交替着步入某项共享资源,你可以使用互斥来使得任何时刻只有一个任务可以访问这项资源。
那么下面就该解决如何是任务之间彼此的合作了?
在任务的协作时,关键是这些任务之间的握手,为了实现这种握手,我们采用了相同的的基础特征:互斥。
4.1 、wait()和notifAll()
wait():等待外部世界产生变化的时候将任务挂起,并且只有在notify()或者notifyAll()时候,这个任务才会被唤醒。
**********wait()和Sleep()区别:***************
wait():线程将被挂起,对象锁被释放,这样别的任务会获得这个锁。因此在该对象中的其他synchronized方法可以在wait()期间被调用。
sleep():线程被挂起,但是对象锁不会被释放,也就是说别的任务不能获得这个锁。
*****************************************************************************************
两种形式的wait()
- wait(毫秒数),可以通过notify()或者notifyAll(),或者时间到期的时候唤醒。
- 第二种是wait()不接受任何形式的参数,这种wait()会无限等待下去,直到线程收到notify()或者notifyAll()
演示实例:
下面演示类似于汽车的抛光和打蜡的过程,打蜡——抛光——打蜡
package com.hone.concurrent01basica; import java.util.concurrent.*; class Car { //用于打蜡和抛光的状态 private boolean waxOn = false; public synchronized void waxed() { waxOn = true; // Ready to buff notifyAll(); } public synchronized void buffed() { waxOn = false; // Ready for another coat of wax notifyAll(); } //如果抛光或者打蜡的状态为false,这该任务将被挂起,并且会释放锁 public synchronized void waitForWaxing() throws InterruptedException { while(waxOn == false) wait(); } public synchronized void waitForBuffing() throws InterruptedException { while(waxOn == true) wait(); } } class WaxOn implements Runnable { private Car car; public WaxOn(Car c) { car = c; } public void run() { try { while(!Thread.interrupted()) { System.out.println("Wax On! "); TimeUnit.MILLISECONDS.sleep(200); car.waxed(); car.waitForBuffing(); } } catch(InterruptedException e) { System.out.println("Exiting via interrupt"); } System.out.println("Ending Wax On task"); } } class WaxOff implements Runnable { private Car car; public WaxOff(Car c) { car = c; } public void run() { try { while(!Thread.interrupted()) { car.waitForWaxing(); System.out.println("Wax Off! "); TimeUnit.MILLISECONDS.sleep(200); car.buffed(); } } catch(InterruptedException e) { System.out.println("Exiting via interrupt"); } System.out.println("Ending Wax Off task"); } } public class WaxOMatic { public static void main(String[] args) throws Exception { Car car = new Car(); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new WaxOff(car)); exec.execute(new WaxOn(car)); TimeUnit.SECONDS.sleep(5); // Run for a while... exec.shutdownNow(); // Interrupt all tasks } }
4.2 notify()和notifyAll()
区别:
- notify():在众多等待的同一个锁的任务中只有一个会被唤醒,当条件发生变化的时候,必须只有一个任务从中受益。
- notifyAll():并非是唤醒所有正在等待的任务,而是因某个特定的锁调用notifyAll()的时候,只有等待这个锁的任务才会被唤醒。
4.4、生产者和消费者问题
先假设这样一个场景:对于一个酒店而言,需要chef不断的生产食物,服务员不断的呈递食物给顾客,实际上chef就类似于一个生产者,server类似于一个消费者。
我们需要模拟一个这样的过程:
在这个实例中分别有如下对象:Restaurant Chef Server Meal
meal实体类:
/** * 定义meal实体类,用于表示食物 * @author Xia */ public class Meal { private final int orderNum; public Meal(int orderNum) { this.orderNum = orderNum; } // 重写toString()方法 public String toString() { return "Meal " + orderNum; } }
Server对象:
/** * 定义一个Server类表示服务生,并且实现Runnable接口 * * @author Xia * */ public class Server implements Runnable { private Restaurant restaurant; // 构造方法,里面出入Restaurant对象 public Server(Restaurant r) { restaurant = r; } @Override public void run() { try { while (!Thread.interrupted()) { // 当meal位空的时候让服务员等待,同时chef在此刻用于生产食物 synchronized (this) { while (restaurant.meal == null) { wait(); } } // 如果meal不为空,则服务员开始送菜 System.out.println("Server send: " + restaurant.meal); // 同时这里面先锁住chef,如果meal为空,则通知chef开始生产菜 synchronized (restaurant.chef) { restaurant.meal = null; restaurant.chef.notifyAll(); // 这个地方有点问题 } } } catch (InterruptedException e) { System.out.println("Server interrupted"); } } }
Chef对象:
import java.util.concurrent.TimeUnit; /** * 定义一个chef类,用于表示生产者对象 * * @author Xia * */ public class Chef implements Runnable { private Restaurant restaurant; private int count = 0; public Chef(Restaurant r) { restaurant = r; } @Override public void run() { try { while (!Thread.interrupted()) { // 对下面的一段代码加上锁,并且判断如果meal不为空,则通知chef等待 synchronized (this) { while (restaurant.meal != null) { wait(); } } // 不断的生产食物,如果食物超过了10,则立刻停止 if (++count == 10) { System.out.println("out of food , closing "); restaurant.exec.shutdownNow(); } System.out.print("order up ! "); // 这里面需要先锁住server对象,同时开始生产meal synchronized (restaurant.server) { restaurant.meal = new Meal(count); restaurant.server.notifyAll(); } TimeUnit.MILLISECONDS.sleep(100); } } catch (InterruptedException e) { System.out.println("Chef interrupted "); } } }
Restaurant对象:并且也是一个主线程:
/** * 写一个生产者和消费者问题 假设在一个厨房中:厨师炒菜和服务员取菜,一个专门负责生产,一个专门负责消费。 里面包含的对象:菜 meal 、 服务员 * server、 厨师 Chef 餐馆 Restaurant * * @author Xia * */ public class Restaurant { Meal meal; // 创建线程池 ExecutorService exec = Executors.newCachedThreadPool(); // new 两个对象 Server server = new Server(this); Chef chef = new Chef(this); // 构造方法初始化Restaurant,并且启动两个线程 public Restaurant() { exec.execute(chef); exec.execute(server); } // 再main方法中调用构造方法间接地启动两个线程 public static void main(String[] args) { new Restaurant(); } }
试着运行的结果:
order up ! Server send: Meal 1 order up ! Server send: Meal 2 order up ! Server send: Meal 3 order up ! Server send: Meal 4 order up ! Server send: Meal 5 order up ! Server send: Meal 6 order up ! Server send: Meal 7 order up ! Server send: Meal 8 order up ! Server send: Meal 9 out of food , closing Server interrupted order up ! Chef interrupted