JAVA多线程的学习

1、Thread.yield方法声明把CPU让给其他具有相同优先级的线程去执行,不过这只是一个暗示,并没有保障机制。

2、Executor

      执行器,管理Thread对象。

      语法demo:     

      ExecutorService exec=Executors.newCacheThreadPool();

      for(int i=0;i<5;i++){
        exec.execute(new XXX()); //XXX为实现Runnable接口的类
        exec.shutdown();
       }

    三种类型及其区别:

      CacheThreadPool:在程序执行过程中创建与所需数量相同的线程,在回收旧线程时停止创建新线程,是Executor首选。

       FixedThreadPool:一次性预先执行代价高昂的线程分配,可以限制线程的数量。

       SingleThreadExcutor:如果向其提交多个任务,那么这些任务排队,每个任务都在下个任务开始前结束,所有的任务使用相同的线程。

3、如何创建有返回值的线程

      实现Callable接口,重写call()方法

      exec.submit(new XXX())会返回Future对象,用future.get()获取值 这个值是泛型的,取决于实现接口的声明,

      如:implements Callable<String>

      关于get:可以先调用Future的isDone()方法来查询是否已经完成。任务完成时会具有一个结果,可以通过get来获取。

      如果不适用isDone()直接get,get会阻塞知道结果准备就绪。

4、通过编写定制的ThreadFactory可以定制由Excutor创建的线程属性(是否是后台,优先级,名称)

      比如:

class MyThreadFactory implements ThreadFactory{
    public Thread newThread(Runnable r){
        Thread t=new Thread(r);
        t.setDaemon(true); //设置为后台线程
        return ..; 
    }
}

5、守护线程中派生的子类默认是守护线程,当所有非守护线程结束时,后台线程终止,一旦main()退出,JVM会关闭所有的守护线程。

      当所有的非后台线程结束时,程序就终止了。同时会杀死进程中所有的后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会终止。

6、若在A线程中调用B.join方法,则A会被挂起,知道B结束。在调用时也可以带上一个超时参数。对join方法的调用可以被打断(通过在调用线程上调用interrupted()方法),这时被打断的B需要用到try-catch字句。(在run方法中,catch InterruptedException)

7、线程抛出的异常不能被正常try-catch到,可以用Executor解决这个问题:

      如4中所示自定义一个myfactory类,在该类的factory的newThread方法中t.setUncaughtExceptionHandler(new XXX) ;    

      XXX是实现了Thread.UncaughtExceptionHandler接口的类。

      然后ExecutorService exec=Executors.newCachedThreadPool(new myfactory);

8、synchronized和lock对象

      synchronized:如果某个任务处于一个对标记为synchronized的调用中,那么在这个线程从该方法返回之前,其他所有需要调用类中任何标记为synchronized方法的线程都会被阻塞。不过如果是synchronized(obj)方法块,只要obj是不同的对象,两个方法不会因为另一个方法的同步而被阻塞。

      用synchronized时代码量更小,且不会出现忘记unlock这种情况,用Lock需要lock();try{} fianlly{unlock},避免在lock后代码出现异常导致死锁。显式使用lock对象可以解决更特殊的问题,比如尝试获取锁一段时间然后放弃、实现自己的调度算法等等。且在加锁释放锁方面有更细粒度的控制力。

9、volatile

      一个定义为volatile的变量是说这个变量可能会意想不到的被改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心的重新读取这个变量的值,而不是使用保存在寄存器里的备份。

      如果多个任务在同时访问某个域,这个域就应该是volatile的,否则只能用同步来访问。同步也会导致从主存中刷新。因此一个域完全由synchronized方法或语句块来保护,就不必为其设置volatile了。

      long和double的写入可能不是原子的,因为long和double是64位的,在32位的机子上的读写操作会被当做两个32位操作执行。如果使用volatile就会获得原子性。此关键字还确保了应用中的可视性。如果把一个域声明为volatile的,只要对这个域产生写操作,所有的读操作都可以看到这个修改。即使使用了缓存。volatile域会被直接写到主存中,而读取操作发生在主存。如果域由synchronized方法或语句块保护,不必设置为volatile。使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变域。第一选择应该是synchronized。

10、同步控制块

        亦称为临界区,在方法内部:

         synchronized(syncObject){

         }

11、ThreadLocal

       当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

       从线程的角度看,目标变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。

12、线程的暂停、继续、终止

       wait、notify、exec.shutdownNow()

       在Executor上调用shutdownNow(),将发送一个interrupt()调用给他所有的线程。(相当于xx.interrupt())

       如果想中断某个单一任务,使用Executor.submit()而不是execute()来启动任务,返回一个Future<f> f,可以通过调用f.cancel(true)来中断。

       可以中断对sleep的调用,(或者任何要求抛出InterruptedException的调用),不能中断正在试图获取资源的synchronized锁或者试图执行I/O操作的线程(办法是关闭任务在其上发生阻塞的底层资源)。

13、wait 和 notify

       sleep和notify不会释放锁,wait会释放锁

       两种形式的wait:毫秒数作为参数或者不加参数

       把wait、notify放在Object类中是因为这些方法操作的锁也是所有对象的一部分,所以可以把wait放进任何控制同步方法里,不需要考虑这个类是继承Thread还是Runnable接口。实际上,只能在同步控制方法或同步控制块里调用wait、notify。否则在运行的时候将得到illegalmonitorStateException异常。调用这两个方法的任务在调用这些方法前必须用有对象的锁。

       notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。

       void notify():唤醒一个正在等待该对象的线程

       void notifyAll():唤醒所有正在等待该对象的线程。

       两者的最大区别在于:

              notifyAll使所有原来在该对象上等待被notify的线程统统退出wait状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。

              notify他只是选择一个wait状态的线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在在等待被该对象notify的线程们,当第一个线程运行完毕后释放对象上的锁,此时如果该对象没有再次使用notify语句,即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。

        可以用lock+condition来代替wait和notify,但是更复杂,只有在更加困难的多线程中才必须。

ReentrantLock

        java.util.concurrent.lock中的Lock框架是锁定的一个抽象,它允许把锁定的实现作为Java类,而不是作为编程语言的特性来实现。

这就为Lock多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。(synchronized是java底层语言,是定义的关键字,是语言层面的)

       ReentrantLock实现了Lock,它拥有与synchronized相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,他还提供了在激烈竞争下的更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM可以花更少的时间来调度线程,把更多时间用在线程的执行上。)      

class printDemo{
    private Lock lock=new ReentrantLock();
    
    public void printStr(String str){
        lock.lock();//获取锁
        
        try{
            for(int i=0;i<str.length;i++){
                System.out.print(str.charAt(i)+" ");
            }
        }finally{
            lock.unlock();//释放锁
        }
    }
        
}

区别:

        需要注意的是,用synchronized修饰的方法或者语句块在代码执行完之后锁自动释放,而Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),需要把互斥区放在try内,释放锁放在finally内!!

读写锁ReadWriteLock

        上例中展示的是和synchronized相同的功能,那么Lock的优势在哪儿呢?

        例如一个类对其内部共享数据data提供了get()和set方法,如果用synchronized,则代码如下:


package ThreadStudy;

public class SynchronizedDemo {
	public static void main(String[] args) {
		final syncData data = new syncData();

		// 写入
		for (int i = 1; i <= 3; i++) {
			Thread t = new Thread(new Runnable() {
				public void run() {
					for (int j = 0; j < 5; j++) {
						data.set((int) (Math.random() * 30));
					}
				}
			});
			t.setName("Thread-write:" + i);
			t.start();
		}

		// 读取
		for (int i = 1; i <= 3; i++) {
			Thread t = new Thread(new Runnable() {

				public void run() {
					for (int j = 0; j < 5; j++) {
						data.get();
					}

				}
			});
			t.setName("Thread-read:" + i);
			t.start();
		}
	}
}

class syncData {
	private int data;// 共享数据

	public synchronized void set(int data) {
		System.out.println(Thread.currentThread().getName() + "准备写入数据。。。");
		try {
			Thread.sleep(20);
		} catch (Exception e) {
			e.printStackTrace();
		}
		this.data = data;
		System.out.println(Thread.currentThread().getName() + "写入" + this.data);
	}

	public synchronized void get() {
		System.out.println(Thread.currentThread().getName() + "准备读取数据");
		try {
			Thread.sleep(20);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "读取" + this.data);
	}
}

    其中读取可以同时进行,不应该互斥

   下面是读写锁ReadWriteLock实现:

package ThreadStudy;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
	public static void main(String[] args) {
		final rwlData data = new rwlData();

		// 写入
		for (int i = 1; i <= 3; i++) {
			Thread t = new Thread(new Runnable() {
				public void run() {
					for (int j = 0; j < 5; j++) {
						data.set((int) (Math.random() * 30));
					}
				}
			});
			t.setName("Thread-write:" + i);
			t.start();
		}

		// 读取
		for (int i = 1; i <= 3; i++) {
			Thread t = new Thread(new Runnable() {

				public void run() {
					for (int j = 0; j < 5; j++) {
						data.get();
					}

				}
			});
			t.setName("Thread-read:" + i);
			t.start();
		}
	}
}

class rwlData {
	private int data;// 共享数据
	private ReadWriteLock rwl = new ReentrantReadWriteLock();

	public synchronized void set(int data) {
		rwl.writeLock().lock();// 获取写锁
		try {
			System.out.println(Thread.currentThread().getName() + "准备写入数据。。。");
			try {
				Thread.sleep(20);
			} catch (Exception e) {
				e.printStackTrace();
			}
			this.data = data;
			System.out.println(Thread.currentThread().getName() + "写入" + this.data);
		} finally {
			rwl.writeLock().unlock();// 释放写锁
		}
	}

	public synchronized void get() {
		rwl.readLock().lock();//获取读锁
		try {
			System.out.println(Thread.currentThread().getName() + "准备读取数据");
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "读取" + this.data);
		} finally {
			rwl.readLock().unlock();//释放读锁
		}
	}
}

       与互斥锁定相比,读写锁定允许对共享数据进行跟高级别的并发放访问。虽然一次只有一个线程(writer线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader线程)

       从理论上讲,与互斥锁定相比,使用读写锁定所允许的并发性增强将带来更大的性能提高。

       在实践中,只有再多处理器上并且只在访问模式使用与共享数据时,才能完全实现并发性增强。---例如,某个最初用数据填充并且之后不经常对其修改的collection,因为经常对其进行搜索(比如搜索某种目录),所以这样的collection是使用读写锁的理想候选者。

线程间通信Condition

        condition可以代替传统的线程间通信,用await()替换wait(),用signal()替换 notify() ,用signalAll() 替换notifyAll()。

        ---因为wait()、notify()、notifyAll()在Object中是final的,不可重写。

        传统线程的通信方式,Condition都可以实现。

        注意,Condition是被绑定到Lock上的,要创建一个Lock的condition必须用newConditon()方法。

        Condition的强大之处在于它可以为多个线程建立不同的Condition。

        看JDK文档中的一个例子:假定有一个绑定的缓冲区,它支持put和take方法。如果试图在空的缓冲区上执行take操作,则在某一个项变得可用之前,线程将一直被阻塞;如果试图在缓冲区上执行put操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待set中保存put线程和take线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition实例来做到这一点。

        ---其实就是java.util.concurrent.ArrayBlockingQueue的功能

package ThreadStudy;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockConditionDemo {
	public static void main(String[] args) {
		BoundedBuffer bb = new BoundedBuffer();
		// 写入
		for (int i = 1; i <= 3; i++) {
			Thread t = new Thread(new Runnable() {
				public void run() {
					for (int j = 0; j < 60; j++) {
						try {
							bb.put(j);
							System.out.println(Thread.currentThread().getName()+"写入:"+j);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			});
			t.setName("Thread-write:" + i);
			t.start();
		}
		// 读取
		for (int i = 1; i <= 3; i++) {
			Thread t = new Thread(new Runnable() {

				public void run() {
					for (int j = 0; j < 60; j++) {
						try {
							System.out.println(Thread.currentThread().getName()+"读取:"+bb.take());
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}

				}
			});
			t.setName("Thread-read:" + i);
			t.start();
		}
	}

}

class BoundedBuffer {
	final Lock lock = new ReentrantLock(); // 锁对象
	final Condition notFull = lock.newCondition(); // 写线程锁
	final Condition notEmpty = lock.newCondition(); // 读线程锁

	final Object[] items = new Object[100];// 缓存队列
	int putptr; // 写索引
	int takeptr; // 读索引
	int count; // 队列中数据数目

	// 写
	public void put(Object x) throws InterruptedException {
		lock.lock(); // 锁定
		try {
			// 如果队列满,则阻塞<写线程>
			while (count == items.length) {
				notFull.await();
			}
			// 写入队列,并更新写索引
			items[putptr] = x;
			if (++putptr == items.length)
				putptr = 0;
			++count;

			// 唤醒<读线程>
			notEmpty.signal();
		} finally {
			lock.unlock();// 解除锁定
		}
	}

	// 读
	public Object take() throws InterruptedException {
		lock.lock(); // 锁定
		try {
			// 如果队列空,则阻塞<读线程>
			while (count == 0) {
				notEmpty.await();
			}

			// 读取队列,并更新读索引
			Object x = items[takeptr];
			if (++takeptr == items.length)
				takeptr = 0;
			--count;

			// 唤醒<写线程>
			notFull.signal();
			return x;
		} finally {
			lock.unlock();// 解除锁定
		}
	}
}

 优点:

         假设缓存队列里面已经存满,那么阻塞的肯定是写线程,唤醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒写线程。

         那么假设只有一个Condition会有什么效果呢?缓冲队列中已经存满,这个lock不知道唤醒的是读线程还是写线程了,如果唤醒的是读线程,那么皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这时又去唤醒,这样就浪费了很多时间。

14、同步队列

        首先我们需要了解同步队列和等待队列的概念。简单的理解是同步队列存放着竞争同步资源的线程的引用(不是存放线程),而等待队列存放着待唤醒的线程的引用。

        同步队列中存放着一个个节点,当线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点并将其加入同步队列,首节点表示的获取同步状态成功的线程节点。 

        

 

15、管道

       两个线程之间,一个拥有pepedwriter,一个拥有pipedreader(需要writer做参数)。

16、CountDownLatch和CyclicBarrier

        CountDownLatch:

                被用来同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成。new CountDownLatch(int size),被等待的线程在执行完操作后latch.countdown(),等待线程调用latch.await();计数值为0时结束等待;

        CyclicBarrier:

                 字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。适用于:创建一组任务,并行执行,然后在下一个步骤之前等待。用于一组或几组线程,比如一组线程需要在一个时间点上达成一致,例如同时开始一个工作。

        //当await的数量到达了设定的数量后,首先执行该Runnable对象。

        CyclicBarrier(int,Runnable):

        //通知barrier已完成线程

         await():

import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
 
 
/**  
 * 各省数据独立,分库存偖。为了提高计算性能,统计时采用每个省开一个线程先计算单省结果,最后汇总。  
 *   
 */  
public class CyclicBarrierDemo{   
   
    public static void main(String[] args) {   
        TotalService totalService = new TotalServiceImpl(); //计算汇总信息  
        CyclicBarrier barrier = new CyclicBarrier(5,   
                new TotalTask(totalService));   //totaltask由最后一个进入的线程启动
  
        // 实际系统是查出所有省编码code的列表,然后循环,每个code生成一个线程。   
        new BillTask(new BillServiceImpl(), barrier, "北京").start();   
        new BillTask(new BillServiceImpl(), barrier, "上海").start();   
        new BillTask(new BillServiceImpl(), barrier, "广西").start()	;   
        new BillTask(new BillServiceImpl(), barrier, "四川").start();   
        new BillTask(new BillServiceImpl(), barrier, "黑龙江").start();   
  
    }   
}   
  
/**  
 * 主任务:汇总任务  
 */  
class TotalTask implements Runnable {   
    private TotalService totalService;   
    TotalTask(){}
    TotalTask(TotalService totalService) {   
        this.totalService = totalService;   
    }   
  
    public void run() {   
        // 读取内存中各省的数据汇总。   
        System.out.println("=======================================");   
        System.out.println("开始全国汇总");   
        totalService.count();   
    }   
}   
  
interface TotalService{
	public void count();
}
 
class TotalServiceImpl implements TotalService{
	public void count(){
		Integer totalSum=new Integer(0);
		ConcurrentHashMap<String, Integer> conmap=BillServiceImpl.conmap;
		for(Entry<String, Integer> entry:conmap.entrySet()){
			System.out.println(entry.getKey()+"="+entry.getValue());
			totalSum+=(Integer)entry.getValue();
		}
		System.out.println("全国数据总额为:"+totalSum.intValue());
	}
}
 
/**  
 * 子任务:计费任务  
 */  
class BillTask extends Thread {   
    // 计费服务   
    private BillService billService;   
    private CyclicBarrier barrier;  
    // 代码,按省代码分类,各省数据库独立。   
    private String code;   
    BillTask(){}
    BillTask(BillService billService, CyclicBarrier barrier, String code) {   
        this.billService = billService;   
        this.barrier = barrier;   
        this.code = code;   
    }   
  
    public void run() { 
    		billService.bill(code);  
        // 把bill方法结果存入内存,如ConcurrentHashMap,vector等,代码略   
//        System.out.println(code + "省已经计算完成,并通知汇总Service!");   
        try {   
            // 通知barrier已经完成   
            barrier.await();   
        } catch (InterruptedException e) {   
            e.printStackTrace();   
        } catch (Exception e) {   
            e.printStackTrace();   
        }  
    }   
  
}  
interface BillService{
	public void bill(String code);
}
class BillServiceImpl implements BillService{
	public static ConcurrentHashMap<String, Integer> conmap=new ConcurrentHashMap<String, Integer>();
	public void  bill(String code){
		conmap.put(code, new Random().nextInt(1000));
		System.out.println("开始计算--" + code + "省--数据!");
		System.err.println(code+"--数据--计算中---");
		System.err.println(code + "省已经计算完成,并通知汇总Service!");
	}
}

 

 

Semaphore

       Semaphore翻译成字面意思是信号量,Semaphore可以控制同时访问的线程个数,通过acquire()获取一个许可,如果没有就等待,而release()释放一个许可。

        假如一个工厂有5台机器,但是有8个工人,一台机器只能同时被一个人使用,只有使用完了,其他工人才能继续使用。那么我们通过Semaphore来实现:

    

package ThreadStudy;

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {
	
	public static void main(String[] args) {
		int n=8;
		Semaphore semaphore=new Semaphore(5);
		for(int i=1;i<=n;i++){
			new Worker(i, semaphore).start();
		}
	}
	
	static class Worker extends Thread{
		private int num;
		private Semaphore semaphore;
		public Worker(int num, Semaphore semaphore) {
			this.num = num;
			this.semaphore = semaphore;
		}
		
		public void run(){
			try{
				semaphore.acquire();
				System.out.println("工人"+this.num+"占用一个机器在生产。。。。");
				Thread.sleep(2000);
				System.out.println("工人"+this.num+"释放出机器。");
				semaphore.release();
			}catch(Exception e){
				e.printStackTrace();
			}
		}
		
	}
}

下面对上面说的三个辅助类进行一个总结:

        (1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们的侧重点不同:

                  CountDownLatch一般用于某个线程A等待若干个其他线程执行完成任务之后,他才执行;

                  而CyclicBarrier一般用于一组线程相互等待至某个状态,然后这一组线程在同时执行;

                  另外,CountDownLatch是不能够重用的,而CyclicBarrier可以。 

        (2)Semaphore其实和锁有点类似,他一般用于控制对某族资源的访问权限。

17、concurrentHashmap(http://blog.csdn.net/liuzhengkang/article/details/2916620

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值