最近重新复习了一边java 线程方面的基础知识,于是做了这篇归纳整理。
大纲
- java线程基础概念
- 两种创建线程的方式
- 线程的生命周期
- 线程死亡的几种情况
- 控制线程
- 线程同步:synchronized Lock
- 线程通信
- 线程池
- 线程安全的集合类
1,java线程基础概念
进程,正在运行的程序。是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
在同一个进程内又可以执行多个任务,而每一个任务我们就可以看成是一个线程。
多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
2,两种创建线程的方式
Thread 类代表线程,所有线程对象都必须是Thread类或其子类的实例。
new Thread() {
@Override
public void run() {
super.run();
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
3,线程的生命周期
线程启动后不一定就进入执行状态,和处于执行状态。经过,新建(New),就绪(Runnale),运行(Running),阻塞(blocked),死亡(Dead)5种状态。
调用start()方法后,线程就进入就绪状态。何时运行取决于JVM里线程调度器。当线程运行后,它不可能一直霸占CPU独立运行,于是线程状态会多次在运行,阻塞之间切换。
下面的情况也会进入阻塞状态
1,sleep();
2,调用了阻塞式IO方法,在该方法返回之前,该线程被阻塞
3,线程试图获得一个同步监视器,但该监视器正被其他线程持有。
4,线程在等待某个通知(notify)
5,suspend方法将该线程挂起,此方法易导致死锁,避免使用。
被阻塞后,其他线程就可以获得执行机会,被阻塞的线程会在合适的时候重新进入就绪状态。
4,线程死亡的几种情况
1,run()方法执行完成,线程正常结束
2,线程抛出一个未捕获异常Exception,Error
3,调用stop方法,该方法容易导致死锁,避免使用
判断时候死亡,调用isAlive()方法,就绪运动阻塞返回true,新建死亡返回false。
不要试图对一个已经死亡的线程调用start()方法使它重新启动,死亡就是死亡。
5,控制线程
- 后台线程
- join线程
- sleep线程睡眠
- yield线程让步
- 线程优先级
后台线程
有一种线程被称为后台线程(Daemon Thread),又称守护线程。调用对象thread.setDaemon(true),指定为后台线程。该线程的特征是,所有前台线程都死亡,后台线程会自动死亡。
join线程
让一个线程等待另一个线程完成的方法,当某线程调用join()方法后,该线程将被阻塞,直到被join()方法加入的线程执行完成
public static void main(String[] args)throws Exception{
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
//main线程调用了jt线程的join()方法,main线程必须等jt执行结束才会向下执行
jt.join();
System.out.print(Thread.currentThread().getName());
}
sleep线程睡眠
调用Thread静态方法sleep(),使线程暂停一段时间,并进入阻塞状态。
yield线程让步
与sleep()有些类似,Thread静态方法,可以让当前线程暂停,但不会阻塞,而是让其转入就绪状态,yield方法只是让当前线程暂停一些,让系统线程调度器重新调度一次,完全可能的是调用后调度器又将其调度出来执行。
实际上,当某线程调用yield方法暂停后,只有优先级与当前线程相同,或者更高的线程处于就绪状态才能获得执行机会。
线程优先级
每个线程都具有一定的优先级,优先级高的线程活得跟多执行机会,setPriority()参数可以是个整数,范围是1~10,也可以是Thread类下的三个静态常量
MIN_PRIORITY = 1;
NORM_PRIORITY = 5;
MAX_PRIORITY = 10;
6,线程同步:synchronized Lock
java的多线程支持引入了同步监视器,来解决线程安全的问题。
同步代码块
synchronized (obj){
//此处的代码就是同步代码块
}
上面语法格式中synchronized后括号里的obj就是同步监视器,含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。
任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完后,该线程就会释放对该同步监视器的锁定。
//经典的银行存钱取钱的案例,下面为简化的取钱线程
class DrawThread extends Thread {
//模拟用户账户
Account account;
//当前取钱线程希望取钱数
double drawAount;
@Override
public void run() {
//使用account 作为同步监视器,任何线程进入下面同步代码块之前。必须先获得对account的锁定
//这种做法符合: 加锁-修改-释放锁 的逻辑
synchronized (account) {
//账户余额大于取钱数目
if (account.Balance >= drawAount) {
System.out.print("取钱成功");
//修改金额
account.Balance -= drawAount;
System.out.print("余额为" + account.Balance);
} else {
System.out.print("余额不足");
}
}
}
}
同步方法
java线程安全还提供了同步方法,对于synchronized修饰的实例方法(非static),无须显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。
通过同步方法可以非常方便的实现线程安全类,每个线程调用该对象的同步方法后,都能得到安全的结果。
把上面的例子中的Account 对象中,把修改Balance 的方法变成同步方法即可。
class Account {
//封装账户余额,不能直接访问
private double Balance;
//提供一个线程安全的改变Balance变量的取钱操作
public synchronized void draw(double drawAount) {
//账户余额大于取钱数目
if (Balance >= drawAount) {
System.out.print("取钱成功");
//修改金额
Balance -= drawAount;
System.out.print("余额为" + Balance);
} else {
System.out.print("余额不足");
}
}
}
使用account的draw方法就会获得this就是account的同步锁
synchronized 可以修饰代码块,修饰方法,但不能修饰构造器,成员变量等。
释放同步监视器的锁定有以下几种情况
1,当前线程的同步方法 同步代码块执行结束,释放同步监视器
2,当前线程的同步方法 同步代码中遇到 break,return终止了继续执行,释放同步监视器
3,当前线程的同步方法 同步代码出现未处理的Error Exception,释放同步监视器
4, 当前线程执行同步方法 同步代码时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。
wait() 与 notify/notifyAll() 的执行过程
由于 wait()与notify/notifyAll() 是放在同步代码中的,因此线程在执行它们时,该线程肯定是获得了锁的。
当线程执行wait()时,会把当前的锁释放,然后让出CPU,进入等待状态。
当执行notify/notifyAll方法时,会唤醒一个处于等待该 对象锁 的线程,然后继续往下执行然后继续往下执行,直到执行完退出synchronized修饰的代码块)后再释放锁。
从这里可以看出,notify/notifyAll()执行后,并不立即释放锁,而是要等到执行代码块后,再释放。
在实际编程中,我们应该尽量在线程调用notify/notifyAll()后,立即退出代码块。即不要在notify/notifyAll()后面再写一些耗时的代码。
public class Service {
public void testMethod(Object account) {
try {
synchronized (account) {
System.out.println("begin wait() ThreadName="
+ Thread.currentThread().getName());
account.wait();
System.out.println(" end wait() ThreadName="
+ Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void synNotifyMethod(Object account) {
try {
synchronized (account) {
System.out.println("begin notify() ThreadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
account.notify();
Thread.sleep(5000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
同步锁(Lock)
java提供了一个功能更强大的线程同步机制-通过显式定义同步锁对象来实现同步。这种机制下,同步锁有Lock对象来充当
Lock比synchronized方法和代码块有更广泛的锁定操作,更灵活的结构。
Lock是readwriteLock提供了两个根接口
1.可重入锁(常用) ReentranLock
2.可中断锁 ReentranReadWriteLock ReentranLock
3.公平锁 ReentranReadWriteLock ReentranLock
4.读写锁 ReentranReadWriteLock
ReentranLock 锁具有可冲入性,也就是一个线程可以对已加锁的ReentranLock锁再次加锁,ReentranLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()枷锁后,必须显式调用unlock()来释放锁。
class Account {
//封装账户余额,不能直接访问
private double Balance;
//定义锁对象
private final ReentrantLock lock = new ReentrantLock();
//提供一个线程安全的改变Balance变量的取钱操作
public synchronized void draw(double drawAount) {
//加锁
lock.lock();
try {
//账户余额大于取钱数目
if (Balance >= drawAount) {
System.out.print("取钱成功");
//修改金额
Balance -= drawAount;
System.out.print("余额为" + Balance);
} else {
System.out.print("余额不足");
}
} finally {
//释放锁
lock.unlock();
}
}
}
死锁的情况
当两个线程相互等待对方释放同步监视器时就会发生死锁,多线程编程应该采取措施避免死锁出现。一旦出现程序不会有异常,也不会有提示,只是所有线程处于阻塞状态,无法继续。
Lock和synchronized的选择
总结来说,Lock和synchronized有以下几点不同:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
7,线程通信
- synchronized同步 通过wait/notify机制,利用共享内存的方式通信,
- 管道通信:管道流主要用来实现两个线程之间的二进制数据的传播。比如通过以PipedInputStream类和PipedOutputStream类为例,通过一边写入,一边读取来通信。
- android 多线程提供了handler 通信
8,线程池
Executors工厂类来生成线程池。
newFixedThreadPool 该模式全部由核心线程去实现,并不会被回收,没有超时限制和任务队列的限制,会创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newCachedThreadPool 该模式下线程数量不定的线程池,只有非核心线程,最大值为Integer.MAX_VALUE,会创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newScheduledThreadPool 该模式下核心线程是固定的,非核心线程没有限制,非核心线程闲置时会被回收。会创建一个定长线程池,执行定时任务和固定周期的任务。
newSingleThreadExecutor 该模式下线程池内部只有一个线程,所有的任务都在一个线程中执行,会创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
newSingleThreadScheduledExecutor 创建只有一个线程池,它可以指定延迟后执行
newWorkStealingPool() 创建持有足够的线程的线程池来支持给定的并行级别,该方法使用多个队列来减少竞争
9,线程安全的集合类
Collections类中提供了多个synchronizedXxx,该方法返回指定集合对象对应的同步对象,从而可以解决多线程并发访问集合时的线程安全问题.
线程安全的集合类
java.util.concurrent包下提供了大量支持高效并发访问集合,
如ConcurrentHashMap、CopyOnWriteArrayList 和CopyOnWriteArraySet
拓展
Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。