找工作——多线程

1. 进程与线程

定义

  进程是程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

  线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。、

关系

  一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

区别

a.地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。

b.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。

c.调度和切换:线程上下文切换比进程上下文切换要快得多。

d.在多线程OS中,进程不是一个可执行的实体。

2. Java中的线程的生命周期大体可分为5种状态

①NEW:这种情况指的是,通过New关键字创建了Thread类(或其子类)的对象

②RUNNABLE:这种情况指的是Thread类的对象调用了start()方法,这时的线程就等待时间片轮转到自己这,以便获得CPU;第二种情况就是处于BLOCKED状态的线程结束了当前的BLOCKED状态之后重新回到RUNNABLE状态。

③RUNNING:这时的线程指的是获得CPU的RUNNABLE线程,RUNNING状态是所有线程都希望获得的状态。

④DEAD:处于RUNNING状态的线程,在执行完run方法之后,就变成了DEAD状态了。

⑤BLOCKED:这种状态指的是处于RUNNING状态的线程,出于某种原因,比如调用了sleep方法、等待用户输入等而让出当前的CPU给其他的线程。

处于RUNNABLE状态的线程变为BLOCKED状态的原因,除了该线程调用了sleep方法、等待输入原因外,还有就是在当前线程中调用了其他线程的join方法、当访问一个对象的方法时,该方法被锁定等。

相应的,当处于BLocked状态的线程在满足以下条件时就会由该状态转到RUNNABLE状态,这些条件是:sleep的线程醒来(sleep的时间到了)、获得了用户的输入、调用了join的其他线程结束、获得了对象锁。

一般情况下,都是处于RUNNABLE的线程和处于RUNNING状态的线程,互相切换,直到运行完run方法,线程结束,进入DEAD状态。

3. JAVA实现多线程的方式

  • 继承Thread类,重写run方法
  • 实现Runnable接口,实现run方法
  • 实现Callable接口,实现call方法(Callable接口与Runnable接口功能类似,但提供了比Runnable更强大的功能)
    • Callable可以在任务结束后提供一个返回值,Runnable无法提供这个功能。  
    • Callable中的call可以抛出异常,而Runnable中的run方法不能抛出异常。

4. 多线程同步的实现方法

当多线程访问同一个资源时,非常容易出现线程安全问题,因此需要采用同步机制来解决这个问题。

(1)synchronized关键字,在Java语言中每个对象都有一个对象锁与之相关联,该锁表明对象在任何时候只允许被一个线程所拥有,当一个线程调用对象的一段synchronized代码时,需要先获取这个锁,然后去执行相应的代码。synchronized主要有两种用法(synchronized代码块和synchronized方法)

   wait()和notify()

   在synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象锁,进入等待状态,并且可以调用notify()活notifyAll()方法通知正在等待的其他线程。notify仅唤醒一个线程(等待队列中的第一个线程)并允许它去获得锁,notifyAll方法唤醒所有等待这个对象的线程并允许它们去获得(并不是让所有线程都获取到锁,而是让它们去竞争)

http://www.cnblogs.com/dolphin0520/p/3920385.html

(2)Lock:JDK5增加了Lock接口以及它的一个实现类ReentrantLock(重入锁)来实现线程的同步。

synchronized和Lock的区别:

  用法不一样:在需要同步的对象中的对象中加入synchronized控制,synchronized既可以加在方法上也可以加在特定代码块上,括号中表示要锁的对象。而Lock要显示的指定起始位置和终止位置。synchronized是托管给JVM执行的,而Lock的锁定是通过代码实现的,它有比synchronized更精准的线程语义。

  性能不一样:在资源竞争不会很激烈的情况下,synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,synchronized的性能会下降很快,而ReentrantLock的性能基本不变。

  锁机制不一样:synchronized获得锁和释放锁的的方式都是在块结构中,当获取多个锁时,必须以相反的顺序释放,并且是自动解锁,不会因为出了异常而导致锁没有释放从而引发死锁。而Lock则需要开发人员手动去释放,并且必须在finally块中释放,否则会引起思索问题的发生。

 5. Java多线程面试常问:

  • join (父线程的代码需要等待子线程运行完之后才能运行父线程后面的代码,则需要调用子线程的join方法)http://www.open-open.com/lib/view/open1371741636171.html
  •  sleep()和wait:sleep是使线程暂停执行一段时间的方法,wait也是一种使线程暂停执行的方法,例如,当线程交互时,如果线程对一个同步对象x发过一个wait()调用请求,那么该线程会暂停执行,被调用对象进入等待状态,直到被唤醒或等待时间超时。

    区别:

    a)原理不同:sleep是Thread的静态方法,是线程用来控制自身流程的,它会使次线程暂停执行一段时间,而把执行机会让给其它线程,等到计时时间一到此线程会自动苏醒。而wait是Object类的方法,用于线程间的通信,这个方法会使当前拥有该对象锁的进程等待,直到其它线程调用notify方法才“醒”来,不过开发人员可以给它一个指定的时间,自动醒来。

    b)对锁的处理机制不同:由于sleep主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间的通信,因此,调用sleep并不会释放锁。而wait则不同,当调用wait方法后,线程会释放掉它锁占用的锁。

    c)使用区域不同:由于wait有特殊的意义,则它必须放在同步方法活同步代码块中,而sleep可以放在任何位置。

  • 守护线程:Java提供了守护线程和用户线程两种。守护线程是指在程序运行时在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分。用户线程和守护线程几乎一样,唯一的不同之处就在于如果用户线程已经全部退出运行,只剩下守护线程线程存在了,JVM也就退出了。因为当所有非守护线程结束时,没有了被守护者,守护线程也就没有存在的必要了,程序就终止了,同时会杀死所有守护线程。也就是说只要有任何非守护线程还在运行,程序就不会终止。在调用start方法之前调用setDaemon(true)。在一个守护线程中产生了其他线程,那么这些新产生的线程默认还是守护线程。  守护线程一个典型的例子就是垃圾回收器,只要JVM启动,它始终在运行,实时监控和管理系统中可以被回收的资源。
  • 线程上下文ThreadLocal:

 

ThreadLocal:实现线程范围的共享变量
用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时需要共享一份数据,而在另外线程中运行时又要共享另外一份数据。
每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的Map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值,在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。

 

public class ThreadScopeShareData {
	//1. 
	private static int data = 0;
	//2.用Map实现线程范围的共享变量
	private static Map<Thread,Integer> threadData = new HashMap<Thread, Integer>();
	
	public static void main(String[] args) {
		for(int i = 0; i  < 2; i++){
			new Thread(new Runnable() {
				@Override
				public void run() {
//					1. data = new Random().nextInt();
					//2.
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName() + "has put data:"+data);
					threadData.put(Thread.currentThread(), data);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}
	static class A{
		public void get(){
			System.out.println("A "+ Thread.currentThread().getName() + "has get data:"+ threadData.get(Thread.currentThread()));
		}
	}
	static class B{
		public void get(){
			System.out.println("B "+ Thread.currentThread().getName() + "has get data:"+threadData.get(Thread.currentThread()));
		}
	}
}

  

 比如银行转账包括一系列操作:把转出账户的余额减少,把转入账户的余额增加,这两个操作要在同一个事务中完成。则connection这个对象必须为线程私有的

什么是死锁?

 

如果一个进程集合里面的每个进程都在等待只能由这个集合中的其他一个进程(包括他自身)才能引发的事件,这种情况就是死锁。

 

这个定义可能有点拗口,一个最简单的例子就是有资源A和资源B,都是不可剥夺资源,现在进程C已经申请了资源A,进程D也申请了资源B,进程C接下来的操作需要用到资源B,而进程D恰好也在申请资源A,那么就引发了死锁。这个肯定每个人都看过了。然后套用回去定义:如果一个进程集合里面(进程C和进程D)的每个进程(进程C和进程D)都在等待只能由这个集合中的其他一个进程(对于进程C,他在等进程D;对于进程D,他在等进程C)才能引发的事件(释放相应资源)。

 

这里的资源包括了软的资源(代码块)和硬的资源(例如扫描仪)。资源一般可以分两种:可剥夺资源(Preemptable)和不可剥夺资源(Nonpreemptable)。一般来说对于由可剥夺资源引起的死锁可以由系统的重新分配资源来解决,所以一般来说大家说的死锁都是由于不可剥夺资源所引起的。

 

死锁的四个必要条件

 

互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
循环等待条件(Circular wait):系统中若干进程组成环路,改环路中每个进程都在等待相邻进程正占用的资源。

 

处理死锁的策略

 

1.忽略该问题。例如鸵鸟算法,该算法可以应用在极少发生死锁的的情况下。为什么叫鸵鸟算法呢,因为传说中鸵鸟看到危险就把头埋在地底下,可能鸵鸟觉得看不到危险也就没危险了吧。跟掩耳盗铃有点像。
2.检测死锁并且恢复。
3.仔细地对资源进行动态分配,以避免死锁。
4.通过破除死锁四个必要条件之一,来防止死锁产生。

 

posted @ 2016-03-05 17:58  cjt1991  阅读(214)  评论(0编辑  收藏  举报