定义任务
- Thread.yeild()的作用是对线程调度器的一种建议(可以将CPU从一个线程切换到另一个线程),它在声明“我已经执行完声明周期,此刻正是切换给其他任务的大好机会”。 例如:
public class LiftOff implements Runnable{
protected int countDown = 10;
private static int taskCount = 8;
private final int id = taskCount ++;
public LiftOff(){
}
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.print(status());
Thread.yield();
}
}
}
Thread类
将Runnable对象转变成工作任务的传统方式是把它交给一个Thread构造器。例如:
Thread t = new Thread(new LiftOff());
t.start();
线程间的切换是由线程调度器负责的。
使用Executor
使用Executor.newCachedThreadPool()可以创建ExecutorService(具有生命周期的Executor)。
newCachedTrheadPool将为每个任务都创建一个线程。
使用ExecutorService.shutdown()方法可以防止新任务被提交给这个Executor。
从任务中获取返回值
Runnable是执行工作的独立任务,它不需要返回值。如果希望任务在结束后有返回值,应该使用Callable接口。
public class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id){
this.id = id;
}
@Override
public String call() throws Exception {
return "result of TaskResult" + id;
}
}
测试代码:
@Test
public void testCallable(){
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>>results = new ArrayList<>();
for ( int i = 0; i < 10 ; i++){
results.add(exec.submit(new TaskWithResult(i)));
}
for (Future<String> fs : results){
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
exec.shutdown();
}
}
}
测试结果:
result of TaskResult0
result of TaskResult1
result of TaskResult2
result of TaskResult3
result of TaskResult4
result of TaskResult5
result of TaskResult6
result of TaskResult7
result of TaskResult8
result of TaskResult9
可以使用isDone查询Future是否完成;你也可以不用调用isDone,直接调用get()方法,此时线程将阻塞,直到结果准备就绪。
休眠
影响任务行为的一种简单方法是调用sleep()方法。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
使用sleep将抛出异常。
优先级
优先级不会导致死锁,优先级较低的线程仅仅是执行的频率较低。
采用setPriority方法改变优先级。
public class SimplePriorities implements Runnable {
private int countDown = 5;
private volatile double d;
private int priority;
public SimplePriorities(int priority){
this.priority = priority;
}
@Override
public String toString(){
return Thread.currentThread() + ": " + countDown;
}
@Override
public void run() {
Thread.currentThread().setPriority(priority);
while (true){
//耗时操作
for (int i = 1; i <10000; i++){
d += (Math.PI + Math.E) / (double)i ;
if (i % 1000 == 0){
Thread.yield();
System.out.println(this);
if (-- countDown == 0) return;
}
}
}
}
}
测试代码:
public void testPriority(){
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i ++) {
exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
}
exec.shutdown();
}
测试结果:
Thread[pool-1-thread-1,10,main]: 5
Thread[pool-1-thread-1,10,main]: 4
Thread[pool-1-thread-1,10,main]: 3
Thread[pool-1-thread-1,10,main]: 2
Thread[pool-1-thread-1,10,main]: 1
测试了很多次都是这个结果,没办法····
让步
Thread.yield()
后台线程
当所有的非后台线程结束时,程序也就终止了,同时会杀死所有的后台线程。main就是一种非后台线程。
必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。
Thread daemon = new Thread(new XXRunnable());
daemon.setDaemon(true);
daemon.start();
通过编写定制的ThreadFactory可以定制由Executor创建的线程的属性(后台,优先级,名称):
public class DeemonThreadFactory implements ThreadFactory {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
然后把它给Executor:
ExecutorService exec = Executors.newCachedThreadPool(new DeemonThreadFactory());
后台进程会在不执行finally语句的情况下终止run方法。
比如:
public class ADemon implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("finally.....");
}
}
}
测试代码:
@Test
public void testThread(){
Thread thread = new Thread(new ADemon());
thread.setDaemon(true);
thread.run();
}
结果是:
finally.....
执行了。。。。不知道为什么?书上说不会执行,除非去掉thread.setDaemon(true);
编码的变体
Thread和Runnable的区别: Runnable是接口,可以允许你继承另外一个类,而Thread不行。
优选Executor而不是创建Thread的原因:
- 在构造器中启动线程是很有问题的,因为另一个线程可能在构造器结束之前就开始了任务,这意味着该任务可以访问不确定状态下的对象。
- 可以控制所有任务同时被关闭。
加入一个线程
join,其效果是:等待一段时间直到第二个线程结束才继续执行。
如果一个线程在另一个线程t上调用t.join(),此线程将被挂起,知道目标线程t结束才恢复。
可以在调用join加上一个超时参数。
join可以被打断,调用interrupt()方法。
捕获异常
Thread.UncaughtExceptionHandler()方法是Java SE5的新接口,它允许你在每个Thread对象上都附着一个异常处理器。
为了使用它,我们创建一个新类型的ThreadFactory,它将在每个新线程创建时附着一个handler。
public class MyUncaughtHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
}
}
public class HandlerThreadFactory implements ThreadFactory {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new MyUncaughtHandler());
return t;
}
}
共享受限资源
不正确访问资源
考虑以下这种生产者消费者模型。
//生产者
public abstract class IntGenerator {
private volatile boolean canceled = false;
public abstract int next();
public void cancel(){
canceled = true;
}
public boolean isCanceled(){
return canceled;
}
}
//消费者
public class EvenChecker implements Runnable {
private IntGenerator generator;
private final int id;
public EvenChecker(IntGenerator generator , int ident){
this.generator = generator;
this.id = ident;
}
@Override
public void run() {
while (!generator.isCanceled()){
int val = generator.next();
if (val % 2 != 0){
System.out.println(val +"is not even!!!");
generator.cancel();
}else{
System.out.println(val +"is even!!!");
}
}
System.out.println("cancel !!!");
}
public static void test(IntGenerator intGenerator , int count){
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < count; i ++){
exec.execute(new EvenChecker(intGenerator,count));
}
exec.shutdown();
}
public static void test(IntGenerator intGenerator){
test(intGenerator,10);
}
}
//真正的生产者
public class EvenGenator extends IntGenerator {
private int currentEvenValue = 0;
@Override
public int next() {
++ currentEvenValue; //danger point,这里不是原子性的,可能有同时有其他线程执行了next()方法
++ currentEvenValue;
return currentEvenValue;
}
}
测试代码:
@Test
public void testThread2(){
EvenChecker.test(new EvenGenator());
}
测试结果:
2is even!!!
4is even!!!
6is even!!!
8is even!!!
10is even!!!
12is even!!!
14is even!!!
16is even!!!
18is even!!!
20is even!!!
22is even!!!
24is even!!!
26is even!!!
28is even!!!
30is even!!!
32is even!!!
34is even!!!
36is even!!!
38is even!!!
40is even!!!
42is even!!!
44is even!!!
46is even!!!
48is even!!!
50is even!!!
52is even!!!
54is even!!!
56is even!!!
58is even!!!
60is even!!!
62is even!!!
64is even!!!
66is even!!!
68is even!!!
70is even!!!
72is even!!!
74is even!!!
78is even!!!
76is even!!!
82is even!!!
80is even!!!
86is even!!!
84is even!!!
Process finished with exit code 0
90is even!!!
到90就退出了。。。为啥??
解决共享资源竞争
解决方法:序列化访问共享资源。
做法:加锁,在一段时间内只有一个任务可以运行这段代码。 在使用并发时,将域设置为private是非常重要的。否则,synchronized关键字就不能放置其他任务直接访问域,这样就会产生冲突。
针对每个类,也有一个锁(作为类的Class对象的一部分),所以synchronized static方法可以在类的范围内防止对static数据的并发访问。
上面的生产者代码可以改成以下这种更安全的方式:
//真正的生产者
public class EvenGenator extends IntGenerator {
private int currentEvenValue = 0;
@Override
public synchronized int next() {
++ currentEvenValue;
Thread.yield();
++ currentEvenValue;
return currentEvenValue;
}
}
加入yield的原因加入yield是为了增加上下文切换的可能性,但是由于加入互斥,所以加入这句话也不会导致失败。如果不加入synchronized,加入这句话会导致更快失败.
使用显式的Lock对象
与内建的锁相比,缺乏代码优雅性。 创建锁的代码如下:
//真正的生产者
public class MutextEvenGenator extends IntGenerator {
private Lock lock = new ReentrantLock();
private int currentEvenValue = 0;
@Override
public int next() {
lock.lock();
try{
++currentEvenValue; //danger point,这里不是原子性的,可能有同时有其他线程执行了next()方法
Thread.yield();
++ currentEvenValue;
return currentEvenValue; //return必须放在unlock之前,确保不会过早将数据暴露给其他线程
}finally {
lock.unlock();
}
}
}
测试代码:
@Test
public void testLock(){
EvenChecker.test(new MutextEvenGenator());
}
Lock相对于synchronized的优点:
- 使用synchronized关键字时,如果失败了,就会抛出一个异常。但是你没有机会去做任何清理工作。有了显式Lock,可以使用finally子句去做清理工作。
- Lock允许你尝试获取锁,还可以尝试获取锁一段时间后,如果失败去做其他时间。方法就是
tryLock()以及tryLock(time);
所以,当解决特殊问题时,才使用显式的Lock对象。
原子性和易变性
不正确的知识:原子操作不需要进行同步控制。
volatile关键字的作用:
- 保证原子性
字撕裂:JVM将64位(long和double)变量的读取和写入当作两个分离的32位操作来执行,这就产生了一个在读取和写入操作中间发生上下文切换,从而导致任务可以看到不正确结果的可能性。如果加入volaitle官架子,就会获取原子性。
- 保证了可见性。
如果你将一个域声明为volatile的,那么只要对这个域产生了写操作,那么所有的读操作都可以看到这个修改。
如果一个域完全由synchronized方法或语句来防护,那就不必将其设置成volatile的。
使用volatile场景:类中只有一个可变的域,否则你的第一选择应该是synchronized。
如果一个域可以被多个任务同时访问,或者这些任务中至少有一个是写入任务,那么你就应该将这个域设置为volatile。这样造成,对这个域的写入和读取都是针对内存的,而却没有被缓存。但是,volatile并没有改变是否是原子性操作的事实。
Brian的同步规则:
如果你正在写一个变量,他可能接下来将被另一个线程读取,或则正在读取一个上一次已经被另一个线程写过的变量,那么必须使用同步,并且,读写线程都必须用相同的监视器锁同步。
原子类
AtomicInteger、AtomicLong、AtomicReference。 下面是通过AtomicInteger重写的MutexEvenGenerator:
public class MutextEvenGenator extends IntGenerator {
private Lock lock = new ReentrantLock();
private AtomicInteger atomicInteger = new AtomicInteger(0);
@Override
public int next() {
return atomicInteger.addAndGet(2);
}
}
临界区
定义:你只是希望防止多个线程同时访问内部的部分代码而不是防止访问整个方法。通过这个方式分离出来的代码片段就成为临界区。也成为同步控制块。
线程本地存储
防止任务在共享资源上产生冲突的第二个方式是根除对变量的共享。线程本地化存储是一种自动化机制,可以为使用相同变量的每个线程都创建不同的存储。 实现类:ThreadLocal
public class Accessor implements Runnable {
private final int id;
public Accessor(int id) {
this.id = id;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
ThreadLocalVariableHolder.increment();
System.out.println(this);
Thread.yield();
}
}
@Override
public String toString() {
return "#" + id + ThreadLocalVariableHolder.get();
}
}
public class ThreadLocalVariableHolder {
private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){
private Random rand = new Random(47);
@Override
protected synchronized Integer initialValue(){
return rand.nextInt(1000);
}
};
public static void increment(){
value.set(value.get()+ 1);
}
public static int get(){
return value.get();
}
}
测试代码:
@Test
public void testThreadLocal() throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool(new DeemonThreadFactory());
for (int i = 0; i < 5; i ++) {
exec.execute(new Accessor(i));
}
TimeUnit.SECONDS.sleep(3);
exec.shutdown();
}
increment()和get()方法不用加synchronized,因为ThreadLocal保证不会出现竞争条件。
任务终结
下面参考一个共享资源的任务终结示例。背景是要统计进入花园的参观人数。花园有多个入口。
public class Count {
private int count = 0;
private Random rand = new Random(47);
public synchronized int increment(){ //如果去掉synchronized,会使得程序奔溃
int temp = count;
if (rand.nextBoolean()){
Thread.yield();//可以增加失败的可能性
}
return (count = ++temp);
}
public synchronized int value(){
return count;
}
}
//花园入口
public class Entrance implements Runnable{
private static Count count = new Count();
private static List<Entrance> entrances = new ArrayList<>();
private int number = 0; //通过本入口进入的人数
private final int id;
public static volatile boolean canceled = false; //所有线程可见
public static void cancel(){canceled = true; }
public Entrance(int id) {
this.id = id;
entrances.add(this);
}
@Override
public void run() {
while (!canceled){
synchronized (this){
++number;
}
System.out.println(this + "total :" + count.increment());
try{
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("sleep interrupt");
}
}
System.out.println("stop !");
}
public synchronized int getValue(){
return number;
}
@Override
public String toString() {
return "entrance" + id + ":" + getValue();
}
public static int getCount() {
return count.value();
}
public static int sumEntrance(){
int sum = 0;
for (Entrance entrance : entrances){
sum += entrance.getValue();
}
return sum;
}
}
测试代码:
public void testEntrance() throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool(new DeemonThreadFactory());
for (int i = 0; i < 5; i ++) {
exec.execute(new Entrance(i));
}
TimeUnit.SECONDS.sleep(4);
exec.shutdown();
//等待每个任务完成,如果在规定时间内,没有所有任务完成,则返回false
if (!exec.awaitTermination(250,TimeUnit.MILLISECONDS)){
System.out.println("some tasks are not terminated");
}
System.out.println("totle" + Entrance.getCount());
System.out.println("sum" + Entrance.sumEntrance());
}
测试结果:
entrance2:1total :2
entrance1:1total :1
entrance0:1total :3
entrance3:1total :4
entrance4:1total :5
entrance0:2total :6
entrance3:2total :8
entrance1:2total :7
entrance4:2total :9
entrance2:2total :10
entrance0:3total :11
entrance1:3total :12
entrance3:3total :13
entrance2:3total :14
entrance4:3total :15
some tasks are not terminated
totle15
sum15
可以看到结果没错,但是有些任务没终结掉。。。。。跟书上的结果又不一样,桑心。。。
在阻塞时终结
线程状态:
- 新建
- 就绪
- 阻塞
- 死亡
进入阻塞状态原因:
- 通过
sleep进入休眠状态 - 通过
wait()使线程挂起,直到notify()或者notifyAll() - 任务在等待某个输入/输出完成
- 某个任务在试图获取锁的时候,锁不可用,因为另一个任务已经获得了锁
注:suspend()和resume()来唤醒线程的时候可能导致死锁,所以他们被废止。stop()方法也被废止,因为它不释放获得的锁。
中断
Thread.interrupt()可以终止被阻塞的任务,这个方法将设置线程的阻塞状态。如果一个线程已经被阻塞,或者试图执行一个阻塞操作,那么设置这个线程的终端状态将抛出InterruptedException。当抛出该异常或者该任务调用Thread.interrput()方法时,中断状态被复位。
Thread.interrupt是离开run方法而不抛出异常的第二种方式。(第一种是前面提到的检测cancel)
下面的示例`展示了集中基本的中断情况:
//io阻塞不可中断
public class IOBlock implements Runnable {
private InputStream in;
public IOBlock(InputStream is){
in =is;
}
@Override
public void run() {
try{
System.out.println("wait for read");
in.read();
}catch (IOException e){
if (!Thread.currentThread().isInterrupted()){
System.out.println("interrupt from io");//这句不会执行
}else{
throw new RuntimeException(e);
}
}
System.out.println("exec for run");
}
}
//sleetp可以中断
public class SleepBlock implements Runnable {
@Override
public void run() {
try{
TimeUnit.SECONDS.sleep(100);
}catch (InterruptedException e){
System.out.println("interrupt exception"); //会执行
}
System.out.println("exec for run");
}
}
//synchronize不可中断
public class SynchrnizedBlock implements Runnable {
public SynchrnizedBlock(){
new Thread(){
@Override
public void run() {
f(); //这个线程获取了锁
}
}.start();
}
public synchronized void f(){
while (true){
Thread.yield();//用于不会释放锁
}
}
@Override
public void run() {
System.out.println("call f()");
f();
System.out.println("exit SynchrnizedBlock run()");
}
}
测试:
public class Interrupting {
private static ExecutorService executorService = new ThreadPoolExecutor(4,
10, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10),
r -> {
Thread thread = new Thread(r);
thread.setName("current" + r.getClass().getName());
return thread;
}, (r, executor) -> {
});
public static void test(Runnable r) throws InterruptedException {
Future<?> f = executorService.submit(r);
TimeUnit.MILLISECONDS.sleep(100);
System.out.println("interrupting " + r.getClass().getName());
f.cancel(true);
System.out.println("interrupt send to " + r.getClass().getName());
}
}
@Test
public void testInterrupt() throws InterruptedException {
Interrupting.test(new IOBlock(System.in));
Interrupting.test(new SleepBlock());
Interrupting.test(new SynchrnizedBlock());
TimeUnit.SECONDS.sleep(3);
System.out.println("out with system.exit");
System.exit(0);
}
测试结果:
wait for read
interrupting com.alexjlockwood.example.java.IOBlock
interrupt send to com.alexjlockwood.example.java.IOBlock
interrupting com.alexjlockwood.example.java.SleepBlock
interrupt send to com.alexjlockwood.example.java.SleepBlock
interrupt exception
exec for run
call f()
interrupting com.alexjlockwood.example.java.SynchrnizedBlock
interrupt send to com.alexjlockwood.example.java.SynchrnizedBlock
out with system.exit
总结:
SleepBlock是可以中断阻塞,IOBlock和SynchronizedBlock是不可中断阻塞,解决IO阻塞的解决办法是关闭资源,比如in.close。或者采用NIO,NIO会自动的响应中断。
互斥阻塞
ReentrantLock上阻塞的任务具有可以被中断的能力。
检查中断
~ 今晚继续~
其他:
在敲代码的时候,由于AS装了阿里的代码规范检查插件,结果一使用Executors.newCachedThreadPool就提示我最好采用ThreadPoolExecutor的方式,特意上网谷歌了下原因。下面是文章:
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
线程之间协作
下面看一个给车涂蜡,然后抛光的过程。抛光任务在涂蜡任务完成之前,是不能执行工作的。 代码如下:
public class Car {
private boolean waxOn = false;
public synchronized void waxed(){
waxOn = true;
notifyAll();
}
public synchronized void buffed(){
waxOn = false;
notifyAll();
}
public synchronized void waitForWaxing() throws InterruptedException{
while(waxOn == false){
wait();
}
}
public synchronized void waitForBuffing() throws InterruptedException{
while(waxOn == true){
wait();
}
}
}
public class WaxOn implements Runnable {
private Car mCar;
public WaxOn(Car car) {
mCar = car;
}
@Override
public void run() {
try{
while (!Thread.interrupted()){
System.out.println("Wax On");
TimeUnit.MILLISECONDS.sleep(200);
mCar.waxed();
mCar.waitForBuffing();
}
}catch (InterruptedException e){
System.out.println("exiting via interrupt");
}
System.out.println("ending wax on task");
}
}
public class WaxOff implements Runnable {
private Car mCar;
public WaxOff(Car car) {
mCar = car;
}
@Override
public void run() {
try{
while (!Thread.interrupted()){
System.out.println("Wax off");
TimeUnit.MILLISECONDS.sleep(200);
mCar.waitForWaxing();
mCar.buffed();
}
}catch (InterruptedException e){
System.out.println("exiting via interrupt");
}
System.out.println("ending wax on task");
}
}
测试代码:
@Test
public void testWax() throws InterruptedException {
Car car = new Car();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new WaxOff(car));
executorService.execute(new WaxOn(car));
TimeUnit.SECONDS.sleep(5);
executorService.shutdownNow();//注意,这里不能是shutdown,使用shutdown不会导致InterruptException,也就无法使用异常退出
}
测试结果:
Wax On
Wax off
Wax On
Wax off
Wax On
Wax off
Wax On
.....
exiting via interrupt
ending wax on task
exiting via interrupt
ending wax on task
上面的例子除了可以使用notify()和wait()之外,还可以使用Lock和Condition。
可以通过Condition的await()方法来挂起线程,然后通过调用signal()或者signalAll()来唤醒线程。
signalAll是用来唤醒所有在这个Condition上被其自身挂起的任务。相比notifyAll,它更安全。
只需要把Car的同步代码改一下,其他类不变就可以实现同样的效果:
public class Car {
private boolean waxOn = false;
ReentrantLock lock = new ReentrantLock();
Condition mCondition = lock.newCondition();
public void waxed() {
lock.lock();
try {
waxOn = true;
mCondition.signalAll();
} finally {
lock.unlock();
}
}
public void buffed() {
lock.lock();
try {
waxOn = false;
mCondition.signalAll();
} finally {
lock.unlock();
}
}
public void waitForWaxing() throws InterruptedException {
lock.lock();
try {
while (waxOn == false) {
mCondition.await();
}
} finally {
lock.unlock();
}
}
public void waitForBuffing() throws InterruptedException {
lock.lock();
try {
while (waxOn == true) {
mCondition.await();
}
} finally {
lock.unlock();
}
}
}
注意,使用Lock类时要在finally中进行释放锁!!!
生产者-消费者与队列
采用notify,notifyAll是一种非常低级的方式,即每次交互时都握手。在许多情况下,可以使用更高级的抽象——同步队列来解决任务协作问题。BlockingQueue接口提供这个队列,这个接口有大量标准实现,比如:LinkedBlockingQueue,ArrayBlockQueue;
同步队列可以在队列为空时挂起消费者对象,当有更多元素时恢复消费者对象。
下面是一个简单的测试,它简单地串起LiftOff对象,然后推出-> 执行。
public class LiftOffRunner implements Runnable {
private BlockingQueue<LiftOff> rockets;
public LiftOffRunner(BlockingQueue<LiftOff> rockets) {
this.rockets = rockets;
}
public void add(LiftOff lo) {
try {
//当有可用位置时才进行插入
rockets.put(lo);
} catch (InterruptedException inter) {
inter.printStackTrace();
}
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
LiftOff liftOff = rockets.take();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}