并发编程—1线程基础

1.线程的基础和线程的共享和协作

1.1 基础概念

线程 vs 进程

进程:程序运行时,资源分配的最小单位。进程内会有一个或者多个线程,线程间共享这个进程的资源。
线程:cpu调度的最小单位,依赖进程而存在。

并发 vs 并行

并发:单位时间内,可以处理事情的能力。
并行:同一时刻,可以同时处理事情的能力。

并发编程的好处和注意事项

好处:充分利用了cpu的资源。加快用户的响应时间,程序的模块化和异步化。
注意事项:

  • 并发会存在死锁的情况
  • 并发共享资源,会存在冲突的情况。
  • 过多的线程会导致机器宕机。

守护线程

随着主线程结束而结束的线程。和主线程共死。
例如GC线程。
特别地,如果一个线程被设置为守护线程,那么线程内容的finally代码不一定总被执行。

创建线程的几种方式

  • 继承Thread
  • 实现Runnalbe
  • 实现Callable + FutureTask
/*实现Callable接口,允许有返回值*/
	private static class UseCall implements Callable<String>{

		@Override
		public String call() throws Exception {
			System.out.println("I am implements Callable");
			return "CallResult";
		}
		
	}	
	
	public static void main(String[] args) 
			throws InterruptedException, ExecutionException {

		UseCall useCall = new UseCall();
		FutureTask<String> futureTask = new FutureTask<>(useCall);
		new Thread(futureTask).start();
		System.out.println(futureTask.get());
	}



停止线程的几种方式

线程正常执行结束

线程执行过程中抛出异常

stop resume

这2个方法是强制中断吸纳线程,已经被废弃的方法。

interrupt()、 isInterrupted() 、static interrupted()

  • interrupt()方法是告诉线程需要中断一些,但是是否会中断由线程自身决定。所以一般结合isInterrupted() 、static interrupted()2个方法一起使用。
  • isInterrupted() 返回当前线程的中断状态,不会重置线程的中断状态。
  • static interrupted() 返回当前线程的中断状态,并且会重置isInterrupted()为false
    在sleep(),wait()等方法都会抛出一个InterruptedException,这时候捕获这个异常的时候,要主动重新调用interrupt()方法。否则可能无法正确中断线程。

如何程序员需要安全的中断一个线程,需要通过while轮询的方式去判断一个线程的标志位。如下:

public class EndRunnable {
	
	private static class UseRunnable implements Runnable{
		
		@Override
		public void run() {

			String threadName = Thread.currentThread().getName();
			while(Thread.currentThread().isInterrupted()) {
				System.out.println(threadName+" is run!");
			}
			System.out.println(threadName+" interrput flag is "
					+Thread.currentThread().isInterrupted());
		}			
	}

	public static void main(String[] args) throws InterruptedException {
		UseRunnable useRunnable = new UseRunnable();
		Thread endThread = new Thread(useRunnable,"endThread");
		endThread.start();
		Thread.sleep(20);
		endThread.interrupt();
	}

}


/**
 *
 *类说明:抛出InterruptedException异常的时候,要注意中断标志位
 */
public class HasInterrputException {
	
	private static SimpleDateFormat formater 
		= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss_SSS");
	
	private static class UseThread extends Thread{
		
		public UseThread(String name) {
			super(name);
		}
		
		@Override
		public void run() {
			String threadName = Thread.currentThread().getName();
			while(!isInterrupted()) {
				try {
					System.out.println("UseThread:"+formater.format(new Date()));
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					System.out.println(threadName+" catch interrput flag is "
							+isInterrupted()+ " at "
							+(formater.format(new Date())));
					interrupt();
					e.printStackTrace();
				}
				System.out.println(threadName);				
			}
			System.out.println(threadName+" interrput flag is "
					+isInterrupted());
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Thread endThread = new UseThread("HasInterrputEx");
		endThread.start();
		System.out.println("Main:"+formater.format(new Date()));
		Thread.sleep(800);
		System.out.println("Main begin interrupt thread:"+formater.format(new Date()));
		endThread.interrupt();
		

	}

}



线程的状态

此处应该有一个图

1.2 线程之间的共享

synchronized内置锁

用于控制不同线程间对于同一个对象的控制。

对象锁和类锁

volatile 关键字

volatile关键字的作用:用来保证对变量修改后,能立即写回主存,从而保证共享变量的修改对所有线程是可见的。JVM语言规范将该特性称为happens-before。具体可以参考AtomicInteger类里面的incrementAndGet的实现

cup core								cup core
	+ register(寄存器)	(修改v1)					+ register(寄存器)(读取v1)
	+ cache(一级或多级高速缓存)(v1)				+ cache(一级或多级高速缓存) (v1)
	||										||
	VV										VV
=================================================================
RAM(内存)	v1 给v1增加volatile修饰可以保存线程对v1的修改可以立刻协会内存。保证了v1对其他线程的可见性。
=================================================================

1.3 线程私有变量 ThreadLocal

  • get() 获取每个线程自己的threadLocals中的本地变量副本。
  • set() 设置每个线程自己的threadLocals中的线程本地变量副本。
    ThreadLocal有一个内部类ThreadLocalMap:
		    public T get() {
	        Thread t = Thread.currentThread();
	        //根据当前的线程返回一个ThreadLocalMap.点进去getMap
	        ThreadLocalMap map = getMap(t);
	        if (map != null) {
	            ThreadLocalMap.Entry e = map.getEntry(this);
	            if (e != null) {
	                @SuppressWarnings("unchecked")
	                T result = (T)e.value;
	                return result;
	            }
	        }
	        return setInitialValue();
	    }
	    
	   	 //点击去getMap(t)方法发现其实返回的是当前线程t的一个内部变量ThreadLocal.ThreadLocalMap
	        ThreadLocalMap getMap(Thread t) {
	        return t.threadLocals;
	    }
	    //由此可以知道,当调用ThreadLocal的get方法是,其实返回的是当前线程的threadLocals(类型是ThreadLocal.ThreadLocalMap)中的变量。调用set方法也类似。
		
		//举例一个使用场景
		/**
	 * ThreadLocal使用场景:把数据库连接对象存放在ThreadLocal当中.
	 * 优点:减少了每次获取Connection需要创建Connection
	 * 缺点:因为每个线程本地会存放一份变量,需要考虑内存的消耗问题。
	 * @author luke Lin
	 *
	 */
	public class ConnectionThreadLocal {
		private final static String DB_URL = "jdbc:mysql://localhost:3306:test";
		private static ThreadLocal<Connection> connectionHolder  = new ThreadLocal<Connection>(){
			protected Connection initialValue() {
				try {
					return DriverManager.getConnection(DB_URL);
				} catch (SQLException e) {
					e.printStackTrace();
				}
				return null;
			};
		};
		
		/**
		 * 获取连接
		 * @return
		 */
		public Connection getConnection(){
			return connectionHolder.get();
		}
		
		/**
		 * 释放连接
		 */
		public void releaseConnection(){
			connectionHolder.remove();
		}
	}
		
		//解决ThreadLocal中弱引用导致内存泄露的问题的建议
		+ 声明ThreadLoal时,使用private static修饰
		+ 线程中如果本地变量不再使用,即使使用remove()
		

1.4 线程间的协作

wait() notify() notifyAll()

		//1.4.1通知等候唤醒模式
			//1)等候方
				获取对象的锁
				在循环中判断是否满足条件,如果不满足条件,执行wait,阻塞等待。
				如果满足条件跳出循环,执行自己的业务代码
			
			//2)通知方
				获取对象的锁
				更改条件
				执行notifyAll通知等等待方
		//1.4.2 
			//wait notify notifyAll都是对象内置的方法
			//wait notify notifyAll 都需要加synchronized内被执行,否则会抱错。
			//执行wait方法是,会让出对象持有的锁,直到以下2个情况发生:1。被notify/notifyAll唤醒。2。wait超时
		//1.4.3 举例使用wait(int millis),notifyAll实现一个简单的线城池超时连接
/*
 * 连接池,支持连接超时。
 * 当连接超过一定时间后,做超时处理。
 */
public class DBPool2 {
	LinkedList<Connection> pools;
	//初始化一个指定大小的新城池
	public DBPool2 (int poolSize) {
		if(poolSize > 0){
			pools =  new LinkedList<Connection>(); 
			for(int i=0;i < poolSize; i++){
				pools.addLast(SqlConnectImpl.fetchConnection());
			}
		}
	}
	
	
	/**
	 * 获取连接
	 * @param remain 等待超时时间
	 * @return
	 * @throws InterruptedException 
	 */
	public Connection fetchConn(long millis) throws InterruptedException {
		// 超时时间必须大于0,否则抛一场
		synchronized (pools) {
			if (millis<0) {
				while(pools.isEmpty()) {
					pools.wait();
				}
				return pools.removeFirst();
			}else {
				// 超时时间
				long timeout = System.currentTimeMillis() + millis;
				long remain = millis;
				// 如果当前pools的连接为空,则等待timeout,如果timeout时间还没有返回,则返回null。
				while (pools.isEmpty() && remain > 0) {
					try {
						pools.wait(remain);
						remain = timeout - System.currentTimeMillis();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				Connection result = null;
				if (!pools.isEmpty()) {
					result = pools.removeFirst();
				}
				return result;
			}
		}

	}
	
	/**
	 * 释放连接
	 */
	public void releaseConn(Connection con){
		if(null != con){
			synchronized (pools) {
				pools.addLast(con);
				pools.notifyAll();
			}
		}
	}
}
		

1.5 sleep() yield()

sleep会让出cpu时间轮转片,然后等待sleep时间结束或者被interrupt后,重新竞争cpu时间片。

yield方法会让出cpu时间轮转片,同时立刻重新竞争cpu时间片.

1.6 join()

面试点:线程A执行了县城B的join方法,那么线程A必须等到线程B执行以后,线程A才会继续自己的工作。

1.7 wait() notify() yield() sleep()对锁的影响

面试点:
线程执行yield(),线程让出cpu执行时间,和其他线程同时竞争cup执行机会,但如果持有的锁不释放。
线程执行sleep(),线程让出cpu执行时间,在sleep()醒来前都不竞争cpu执行时间,但如果持有的锁不释放。
notify调用前必须持有锁,调用notify方法本身不会释放锁。
wait()方法调用前必须持有锁,调用了wait方法之后,锁就会被释放。当wait方法返回的时候,线程会重新持有锁。

posted on 2019-05-17 22:56  lukelin1989  阅读(120)  评论(0编辑  收藏  举报

导航