java中的多线程
概念:多线程在较低的层次上扩展了多任务的概念:一个程序同时执行多个任务。通常,每一个任务成为一个线程,他是线程控制的简称。可以同时运行一个线程以上的程序叫多线程程序。
线程和进程的区别:本质区别在于每个进程有自己独立的变量,而线程共享变量。与进程相比,线程更“轻量级”,创建撤销一个线程比启动新进程的开销小很多。
- 什么时候使用线程?
执行一个比较耗时的操作,应使用独立的线程。
- 在一个单独的线程中执行一个任务的简单过程:
- 将任务代码写在实现了Runnable接口的类的run方法里。
- 创建一个类对象
- 由Runnable创建一个Thread类对象。
- 执行start()方法,启动线程。
注意:启动线程时,不要调用Thread类或Runnable接口的run方法。因为直接调用run方法,只会执行同一个线程中的任务,而不会启动线程。应该调用Thread.start方法。这个方法将创建一个执行run方法的新线程。
- 如何控制线程之间的交互?
通过锁来实现线程之间的交互。在javase 5.0后 ,多线程发生了重大变化,引入大量的类和接口。
锁对象。ReentrantLock和Lock。在java.util.current包中
例子:mylock.lock(){ try{ 临界区; } finally{ mylock.unlock(); }
这一结构确保任何时刻只有一个线程进入临界区。锁是可重入的。那么什么叫可重入的呢?线程可以重复的获得已有的锁。锁保持一个持有计数器来跟踪对lock方法的调用。
条件对象
条件对象出现的原因。想像这样一种情况,我们进入临界区后,发现在满足某一条件后他才能执行。这是我们就需要引入一个条件对象。
线程进入阻塞状态,需要等待某一条件得到满足才能继续执行。这个唤醒条件的执行需要其他线程来实现。这里会涉及到两个方法,await方法,还有一个signal(signalAll)方法。
wait方法是当某一条件不满足时。线程用来主动让自己进入阻塞状态。signal方法用来唤醒进程的,当一个进程释放该条件后,会随机通知一个线程进入可执行状态(signalAll方法把释放所有需要该条件的线程,让他们都有竞争的条件)
Condition 类。
synchronzied关键字。
如果一个方法用synchronized 关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。
一起使用的方法有wait,notify。调用sychronized 方法时,该方法会获得相关的内部锁。每一个对象有一个内部锁,并且该锁有一个内部条件。由锁来管理那些试图进入sychronized方法的线程,由条件来管理那些调用wait的线程。
内部锁和条件有一些局限:
1、不能中断一个正在试图获取锁的线程。
2、试图获取锁时不能获设定超时。
3、每个锁仅有单一的条件,可能会不够。
volatile关键字:
volatile 关键字的适用场景:
例如:有一个变量 flag;
private flag;
public synchronized boolean getflag(){return flag;}
public synchronzied void setflag(){ flag=true;}
如果另一个线程已经对该对象加锁,那么get 和setf方法可能会阻塞。这时内部锁可能不是一个好的策略。
这时就需要将域声明为volatile.
volatile 关键字为实例域的同步访问提供了一种免锁的机制。
警告:volatile 关键字不能提供原子性。不能保证变量读取,计算和写入的过程不被中断。
final变量
将一个变量声明为final,也可以安全地访问一个共享域。
原子性:
java.util.concurrent.atomic包中的很多类使用了高效的机器级指令来保证其他操作的原子性。
线程局部变量:
有时为了避免使用共享变量,可以使用ThreadLocal辅助类为各个线程提供各自的实例。
用法:例如要构造一个SimpleDateFormat对象。
读写锁:
java.util.concurrent.locks提供了两个锁类,ReentrantLock和ReentrantReadWriteLock类。
使用读写锁的步骤:
1、新建一个ReentrantReadWriteLock类的对象。
private ReentrantReadWriteLock rwl=new ReentrantLock();
2、抽取读写锁
private ReadLock rel=rwl.readLock(); private WriteLock wrl=rwl.writeLock();
3、对所有的获取方法加读锁。
4、 对所有的获取方法加写锁。
线程安全的集合: