再次梳理多线程
说起多线程,java开发人员都会觉得这是必须的知识,但是在我们(绝大多数的应用开发)平时的时候,写的多线程代码并不多。就比如我而言,在生产环境下写的多线程也就是一个批量上传百万条数据时候开了5个线程进行批量上传,带来的提高也就提高了3点多倍的速度。所以很多开发人员在提到多线程的时候就会感觉很熟悉,但是却讲不清楚。
那为什么需要多线程?因为现在是多cpu的时代。多个cpu一起干活(一起干一件事情还是多件事情)。其实并发提高的效率是指单个cpu上程序运行多个任务的总体性能。正常情况下,一个任务在单个cpu上顺序进行是最高效率的,但是为什么会出现多线程呢?就是因为一个任务有可能不能顺序进行下去,就是指任务阻塞。当一个任务卡在那里的时候,cpu就是处于浪费状态,所以才会有多线程,当一个任务阻塞的时候,切换到另一个任务。
基本的线程机制
1.创建线程可以使用new Thread(new Runnable())或者new Runnable()或者ExecutorService.excute(new Runnable()),推荐使用后者。线程启动后是为了执行一些任务的,就是我们需要实现的具体功能,这些通常写在继承的run()方法内。
2.当我们的任务需要有返回值的时候,需要继承Callable()String方法。然后调用ExcutorService.submit()方法去执行这个任务
<T> Future<T> submit(Callable<T> task);
返回的是Future<T>,T就是我们要的返回值。当然这个任务可能很快就能完成,也会一直完不成,所以在通过future拿返回值时,最后先调用isDone()方法查看是否任务完成
3.线程的休眠和优先级和让步
一般现在都用TimeUnit.*.sleep()
4.后台线程
后台线程派生的线程同样为后台线程,并且如果后台线程的run方法里面有try cathc finally,其实finally并不会执行。因为后台线程会在所有非后台线程终止时。后台线程就会马上被关掉。
5.线程的join
一个线程可以在另一个线程上调用join,比如a线程里面进行b.join(),则a线程就停止干活,等到b线程结束后才会进行a,同时也可以设置超时时间。如果b上时间不返回,则可以调用b。interrupt()进行强制中断
6.线程的异常捕获
如果我们在任务中(即run方法中)抛出了异常,使用try{}catch{]是没用的。
例如
1 try { 2 ExecutorService exec =Executors.newCachedThreadPool(); 3 4 exec.execute(new ExceptionThread());//ExceptionThread的run方法直接抛出runtimeException 5 } catch(RuntimeException ue) { 6 // This statement will NOT execute! 7 System.out.println("Exception has been handled!"); 8 }
解决办法是继承UncaughtExceptionHandler()接口,并将一个线程对象和他绑定,则异常就会被捕获。
并且我们一般new一个Thread,应该是从jdk的DefaultThreadFactory返回的一个runable对象。如果想改变new的thread对象,可以Executors.newCachedThreadPool()时候指定自定义ThreadFactory。//TODO
线程竞争
1.当多个线程共同干一件事情,比如说将一个数+2
public int next() { ++currentEvenValue; ++currentEvenValue; return currentEvenValue; //To change body of implemented methods use File | Settings | File Templates. }
如果这样做的话,就会有问题,因为看似一个方法能够顺序执行,这篇文章讲述了为什么http://www.atatech.org/article/detail/14014/196
如何让这个next方法来保证完成我们期望他完成的任务呢,可以加锁--synchronized
一般我们把这个例子代码中的currentEvenValue叫做共享资源,一般以对象的形式在内存中,但也可能是文件,io端口等等。如果想解决共享资源的问题,一般会在访问这个资源的方法中加上synchronized。
如果一个类有两个synchronized方法 synchronized a() 和synchronized b().那么当你调用一个对象的a方法时,是不能同时进行b方法。
同样,也有另外一种办法,在next()开始的时候new ReenTrantLock(); 有lock()和unlock(),tryLock()方法。 这种方法可以让你在尝试获得锁。如果当前不能,则可以干其他的事情。而synchronized则会一直等待锁资源的释放。
synchronized除了可以锁一个方法,也可以一个代码块,
2.原子性,可视性
原子性就是不会被线程调度机制打断。比如基本类型的赋值(long和dobule除外,64位的读取和写入当作两个32位的,不清楚怎么复现)
volatile来声明一个字段,可以保证他的原子性和可视性,即1个线程对这个字段进行了修改,那么它会刷新到主存。中间不会被打断
3.THreadLocal
Threadlocal不会涉及多个线程的同步,因为他们不会涉及多个线程竞争资源。 这个类经常使用
4.线程的中断
一般我们会通过ExecutorService 的execute()方法来启动线程。如果掉用ExecutorService.shutdownNow()则通过这个ExecutorService启动的所有线程都会被中断
,所以如果我们需要关闭一部分线程而保持其他线程状态,可以通过submit()方法来启动。submit()会返回Future<T>,通过f。cancel(true)方法,则会中断这个线程。不过如果当前线程阻塞在io,网络,或者在synchronized锁的时候,是关闭不掉的。通过关闭底层资源来中断线程。