再次梳理多线程

     说起多线程,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锁的时候,是关闭不掉的。通过关闭底层资源来中断线程。

 

 

posted @ 2014-03-22 17:18  新密牛哥哥  阅读(1227)  评论(1编辑  收藏  举报