线程
进程---计算机在执行的任务或者逻辑---服务(没有界面的进程)
线程---进程中任务的一个小任务---QQ,下载软件,JVM
记事本是单进程多线程
自定义线程
1. 继承 Thread类,重写其中的run方法,将线程要执行的逻辑写到run方法。通过线程对象身上的start方法来启动线程。在创建线程对象时传入子类对象。
2. 实现Runnable接口,重写的run方法。通过Thread对象来启动这个线程。在创建线程对象时候传入该接口的子类对象。
3. 实现Callable<T>接口,重写的call方法。
第1种方式无法继承其他类,第2,3种可以继承其他类;第2,3种方式多线程可以共享同一个target对象,多个线程处理同一个资源;一般使用第2,3种方式创建线程。
线程的执行并不是有序的,是相互抢占,而且抢占并不是只在线程执行的开始而是发生在线程执行的每一步过程中---由于多个线程之间相互抢占资源导致出现了不符合常理的情况,称之为多线程的并发安全问题
线程的状态

同步代码块---将可能出现问题的代码放到了synchronized中,需要一个锁对象---必须是所有线程都认识的资源---共享资源,类的字节码,this
同步方法---synchronized修饰方法---锁对象是this
问题 如何避免线程的安全问题:在java中使用synchronized关键字使代码同步,还可以使用Lock对象为必要的代码部分加锁与解锁。相比前者后者比较灵活更具扩展性。
使用synchronized进行同步不太灵活,当多生产多消费的时候往往要使用notifyall()方法,该方法会唤醒所有线程,这样的话就会造成程序的效率降低。如果使用多个synchronized嵌套虽然可以解决问题但是会容易造成死锁的问题。
那么怎样解决以上问题呢?在JDK1.4以前是只能那样做。在JDK1.5以后添加了Lock接口,和condition接口,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。随着灵活性的增加,也带来了一些问题。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。我们必须把执行代码放在一个块里面,而锁的释放给放在finnally里。详细可查API crourrent包下的lock接口。
小例子:
Lock l = new Reentrant();
l.lock();
try{
//do something....
}finally{
l.unlock();
}
死锁---产生的原因:多个线程,共享资源过多,锁对象不统一,锁的嵌套---避免死锁:统一锁对象,减少锁的嵌套
问题:什么是死锁?
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。
活锁与死锁的区别?
活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。
同步/异步---如果一个对象在某个时间段内只允许一个线程操作--同步
同步一定是安全的,不安全一定是异步的。
HashMap---异步式线程不安全
Hashtable---同步式线程安全
ConcurrentHashMap---异步式线程安全
通过等待唤醒机制调节线程之间的执行顺序---线程之间的相互通信
线程在等待期间是位于线程池中的。---线程池本质上是一个存储线程的队列
线程安全集合
ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap等都是线程不安全;如果多个线程对这些集合读,写时,会破坏这些集合数据的完整性。
线程安全集合:ConcurrentHashMap、 ConcurrentSkipListMap,、ConcurrentSkipListSet、ConcurrentLinkedQueue, ConcurrentLinkedDeque、CopyOnWriteArrayList 、CopyOnWriteArraySet
等待唤醒机制:
wait(); 使线程处于等待状态,其实 把线程临时存储到了线程池中;notify();唤醒任意一个线程;
notifyAll();唤醒所有线程。
等待和唤醒必须使用在同步中。因为必须标示wait和notify所对应的锁,同一个锁对象下的只能唤醒该锁下的线程。
为什么这些方法定义在object中,因为锁可以是任意对象,而任意对象都能调用的方法必然在object中。
面试题:
sleep与wait的区别:
1.sleep方法必须制定线程休眠的时间
wait方法可以制定也可以不指定线程停止的时间。
2.sleep方法执行后不会释放监视器锁权限,此时处于临时阻塞状态。
wait方法则在执行后立即释放锁权限
3.sleep方法到时间后自动唤醒并继续执行
wait方法则在执行后如果没有被notify或者notifyAll唤醒则一直处于等待状态。
4.sleep方法不一定要定义在同步中
wait方法则必须在同步中使用
守护线程的概念:
守护线程其实指的是后台线程,我们正常创建的线程都是前台线程,当调用线程的setDaemon方法后该线程会被转换成后台线程,也就是守护线程,守护线程的作用是配合前台线程一起运行完成某些事情,而前台线程都结束后虚拟机会认为该进程结束,所有的后台线程都会结束,哪怕是后台线程仍然处于等待状态。线程优先级的概念:
线程在创建以后是具备默认优先级的,默认的优先级是当前线程的优先级,也就是被哪个线程创建就是默认是哪个线程的优先级(比如main主线程)。线程的优先级是使用1-10的数字表示,数字越大优先级越高。默认的优先级是5。在Thread类中已经定义了优先级的字段。MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY。使用setPriority方法可以对线程进行优先级的设定。
线程组的概念:
线程组就是一个集合,存放了多个单独的线程,线程组有封装的类ThreadGroup,该类里面定义了常用的一些操作方法。在创建线程的时候可以指定该线程的所属线程组。这样的好处是便于线程的管理,比如批量停止线程等。
线程池
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
Java四种线程池的使用
Java通过Executors提供四种线程池,分别为:
1.newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2.newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3.newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4.newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
创建线程池的步骤
- 调用Executors类的静态方法创建一个ExecutorService对象,得到一个线程池。
- 创建Runnbaler的实现类
- 通过submit()方法向线程池提交runnbale线程
- 通过shutdown()方法关闭线程池