一、进程和线程的区别
进程:操作系统层面,每个进程拥有独立的代码和数据空间,进程间切换开销很大,进程是资源分配的最小单元;
线程:应用程序层面,同一类线程可以共享代码和数据空间,线程间切换开销较小,线程是CUP调度的最小单元。
线程和进程同样分为五个阶段:创建、就绪、运行、阻塞、终止。
二、线程的创建
多线程的创建有两种手段,一是继承Thread类,二是实现Runnable接口。
run()方法是多线程的约定,所有的多线程代码都在run()方法中编写,通过调用Thread的start()方法执行,进入Runnable就绪状态。
1.继承Thread类(实际上也是实现了Runnable接口)
需要注意:如果一个线程多次调用start(),会抛出java.lang.IllegalThreadStateException
2.实现Runnable接口
实现Runnable接口所具有的优势:
1)避免java中单继承的限制
2)增加程序健壮性,代码可以被多个线程共享
3)适合同一类线程处理一个资源
4)线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
3.使用Callable和Future创建线程
与实现Runnable接口的方式基本相同,但Callable有返回值并可以处理异常
当java执行一个类时,会启动一个JVM,相当于在操作系统中开启了一个进程,每次运行java程序时最少执行两个线程,main方法和垃圾回收器。
三、线程的转换状态(生命周期)
1.创建线程(new)
2.就绪状态(Runnable):当线程执行start()方法时进入就绪态,等待获取CPU的使用权。
3.运行状态(Running):获取到CPU的使用权,执行run()中代码
4.阻塞状态(Blocked):由于某种原因放弃CPU的使用权,直到再次进入就绪态才有机会继续执行。阻塞情况分为三种:
1)等待阻塞(wait):执行wait()方法,使该线程进入线程池中等待(wait会释放持有的锁)
2)同步阻塞:运行线程时获取对象的同步锁时,该同步锁被别的线程占用
3)其他阻塞:当线程执行sleep()或join()方法时会进入阻塞状态
5.死亡状态(Dead):线程执行完毕或异常终止(子线程不会随着主线程结束而结束)
6.阻塞后重新进入就绪状态
1)调用sleep()经过了指定时间
2)调用阻塞式IO方法已经返回
3)正在等待通知,其他线程发布通知
四、部分常用API
Thread.currentThread()获取当前线程对象
getName()返回该线程的线程名字:main方法默认为main 新线程名字Thread-0…
isAlive()判断线程是否死亡
四、常用函数说明
1.sleep(long millis):指定毫秒数内让指定线程休眠并进入阻塞状态,用于暂停程序
2.Join():线程启动后直接调用,作用是主线程等待子线程执行完毕后才终止
重载形式:Join(long millis) 等待最长时间为millis毫秒
3.setDaemon(true):将线程设置为后台线程,当所有前台线程结束时自动结束,必须在start()方法之前调用,否则抛异常。
4.
5.Yiled():调用该方法时使该线程进入可运行态,不能保证下次调用还是不是它
6.SetPriority(Thread.MAX_PRIORITY):更改线程优先级,优先级高的线程获得更多的执行机会
MAX_PRIORITY = 10;
MIN_PRIORITY = 1;
NORM_PRIORITY = 5;
五、线程同步(加锁→修改→释放)
当两个线程并发修改同一个文件时就有可能出现错误
1.同步代码块
为了解决这个问题,引入synchronized(obj)来同步代码块,其中obj为同步监视器,即需要监视的对象。
任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完毕后,该线程会释放对同步监视器的锁定。
2.同步方法
使用synchronized修饰的方法即为同步方法,它修饰的是调用这个方法的对象。
3.线程同步会导致效率大大降低,因此为减少线程安全带来的影响,可采用如下策略
1)只对那些需要线程同步的方法进行同步
2)如果该类有两种运行环境:单线程和多线程,应该写两个版本,以提高效率。(例如StringBuilder和StringBuffer分别为单线程和多线程)
4.释放同步监视器的锁定
1)线程的同步方法执行完毕
2)遇到return或break
3)代码块异常结束
4)该对象执行wait方法,线程暂停,释放锁
5)调用sleep或yield方法不会释放锁
5.同步锁Lock
Lock提供了比synchronized更加灵活的结构,可以显式的加锁与释放锁,比较常用的实现类为ReentrantLock。
private final ReentrantLock lock = new ReentrankLock()
……
public void m(){
lock.lock();
……
lock.unlock();
}
六、线程通信
1.传统的线程通信(隐式synchronized)
借助于Object提供的wait、notify、notifyAll三个方法实现
对于synchronized的同步方法可以直接使用这三个方法
对于synchronized的同步代码块必须使用同步监视器调用这三个方法
2.方法说明
wait:导致线程等待并释放对同步监视器的锁定。
notify:唤醒在此同步监视器等待的单个线程,只有线程释放锁才可以唤醒
notifyAll:唤醒在此同步监视器等待的所有线程
3.使用Condition类控制线程通信(显式Lock,无法使用隐式的线程通信方法)
private final ReentrantLock lock = new ReentrankLock();
private final Condition con = lock.newCondition();
……
Lock.lock
……
con.await();
……
Con.signalAll();
……
lock.unlock();
4.使用阻塞队列(BlockingQueue)控制线程通信
添加元素,队列满时阻塞;取出元素,队列空时阻塞。
七、线程池
当线程中存在大量的生存期很短的线程时,可以使用线程池。线程池中存放了大量空闲线程等待被调用;除此之外,线程池可以控制并发的数量,优化程序。
1.创建线程池的方法
1)newCachedThreadPool():创建一个具有缓存功能的线程池
2)newFixedThreadPool():创建一个可重用的、具有固定数量线程的线程池
3)newSignalThreadPool():创建单线程的线程池
2.创建线程池的步骤
1)调用Executor的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池
ExecutorService pool = Excutors.newFixedThreadPool(6);
2)创建Runnable或Callable实现类作为线程任务
3)调用pool.submit()来提交实例(而不是通过创建线程、启动线程来执行该任务)
4)调用pool.shutdown()来关闭线程池
八、ThreadLocal类
1.原理
ThreadLocal类定义一个变量,可以为每一个使用该变量的线程创建该变量的副本,并可以自行修改。ThreadLocal隔离了多个线程之间的数据共享,从根本上避免了多个线程对共享资源的竞争。
2.方法
get set remove