进程和线程
一个进程可以有多个线程,线程有自己的堆栈,自己的程序计数器和局部变量,但是他们共享进程的
系统资源。因此要小心线程之间彼此影响。
并发编程三个特点:
- 原子性问题
- 可见性问题
- 有序性问题
多线程优点
- 进程间内存不能共享,而线程间可以共享内存
- 新建进程需要分配系统资源,而新建线程则不需要分配,创建和切换代价小,从而使多线程来实现
多任务比多进程效率更高。
java线程的创建
- 使用
Thread()创建线程,使用start()方法来启动线程。 - 也可以通过实现
Runnable接口,然后作为Thread的target创建线程。 Runnable接口方式的优点是可以继承其他类。
线程的周期
- 创建。想让新创建的子线程立即执行可以使用
Thread.sleep(1),让主线程暂停一下。 - 就绪
- 运行
- 阻塞
- 死亡
tip
- 注意少使用
suspend()和stop(),这样会造成死锁。 - 使用
isAlive()判断
线程的控制
- join线程
在执行的线程A运行时调用其他线程B的B.join()方法后,A线程将等到B线程结束之后才执行。 - 后台进程
在start()之前,调用setDaemon()。可以使用isDaemon()判断是否是后台线程,后台线程
创建的线程默认是后台线程。程序中的所有前台线程运行结束后,后台线程会被通知死亡,不过这会
有一定的时间。 yield()静态方法
与sleep()类似,但是不会让位于优先级比自己低的线程,所以如果其优先级最高且没有同级的
线程的话,调用此方法后该线程依然继续执行。- 设置线程优先级
setPriority(int)接受1~10的整数,或者三个静态常量:MAX_PRIORITY,MIN_PRIORITY,NORM_PRIORITY,对应的数值依次为10,1,5.
虽然Java提供了1~10共10个级别,但是有的系统并不支持,所以为了移植性,最佳实践是
使用常量而为直接指定数值
线程同步
同步代码块
eg. synchronized(obj),其中obj为同步监视器,Java允许使用任何对象作为同步监视器,
但是由于我们的目的是阻止两条线程并发访问一个共享资源,所以最佳实践是使用可能被并发访问
的资源作为同步监视器。
同步方法
此时不需要指定同步监视器,方法会自动把this当成同步监视器。通过同步方法,一般可以使类
成为线程安全的类,这样的类有如下特点:
- 该类的对象可以被多个线程同时访问
- 访问该对象的任意方法都能得到正确的结果
- 访问该对象的任意方法,该对象都能依然保持合理状态
可变类的线程安全是以降低程序效率为代价的,为了降低对性能的影响,需要采取以下策略:
- 减少同步的使用,只对会改变竞争资源的方法同步
- 如果可变类有两个运行环境:多线程环境和单线程环境,则可以开发线程安全和非线程安全两个
版本
释放同步监视器的锁定
程序无法显示释放对同步监视器的锁定,线程释放锁定有以下几种情况:
- 同步方法或者代码块执行完毕
- 同步方法执行到
break,return - 同步方法执行到
wait()方法 - 同步方法遇到
Error和Exception等中止程序继续运行的情况
线程不会释放锁定的情况: - 执行
sleep(),yield()时 - 执行
suspend()时
同步锁Lock
类似synchronized的机制,Lock常用的是ReentrantLock。同步锁需要显示的加锁和释放。
通常在finally里释放。ReentrantLock具有重入性,线程可以对它已经加锁的ReentrantLock
再次加锁,所以一段被锁保护的方法可以调用另一个被相同锁保护的方法。
死锁
少使用suspend()方法,会造成死锁。
线程通信
synchronized方式wait():导致该线程等待并放弃同步监视器,知道该同步监视器的notify()或者notifyAll()方法来唤醒改线程notify():选择任意一个在该同步监视器上的线程,将其唤醒notifyAll():唤醒所有等待同步监视器的线程- 上述三个方法都属于
Object而非Thread,这三个方法都必须由同步监视器来调用。
- 同步锁方式
- 如果不使用
synchronized而是用Lock的话则不能再使用上述方法,此时我们使用Java提供的Condition类保持协调。Lock代替了同步方法或者同步块,Condition代替了同步监视器的功能。Condition提供了类似的三个方法: await():await()还有多种变体如:awaitNanos(long),awaitUninterruptibly()等signal()signalAll()
- 如果不使用
- 使用管道流
与普通IO类似,可以使用字节流、字符流和新IO Channel三种形式。通常有共享数据就可以了,不需要使用管道流。