在java中,每个任务都是Runnable接口的一个实例,也可以成为运行对象。线程上本质上讲就是便于任务执行的对象。
任务就是对象。
创建一个线程1:实现Runnable接口
- 创建一个任务
TaskClass task = new TaskClass(...);
- 任务类必须在线程中执行
Thread thread = new Thread(task);
- 然后调用start方法告诉虚拟机该线程准备运行
thread.start();
TaskClass是自己创建的,完成要完成的任务。”=”左边的Taskclass可以换成Runnable。TaskClass实现Runnable接口。
创建一个线程2:继承Thread类
- 创建Thread类扩展对象
TaskClass task = new TaskClass(...);
- 然后调用start方法告诉虚拟机该线程准备运行
thread.start();
这里的TaskClass和上面的区别是:这里是继承了Thread类,其余都一样。Thread类本身实现了Runnable接口。
任务类(这里是实现Runnable接口的任务类)
- 任务类比普通类多了run方法。具体如下:
public void run(...) {
...
}
- 任务类要实现Runnable接口
class TaskClass implements Runnable {
...
}
直接调用任务类中run方法,只是在同一个线程中执行该方法,而没有新的线程被启动。
Thread 类
- Thread类包含stop(),suspend()和resume()方法,普遍认为不安全,不使用
- 代替stop()方法,可以给Thread类赋值null,来表明停止。
- yield()的方法为其他线程临时让出cpu时间
- sleep(long mills)
设置休眠,让出cpu时间,单位毫秒。 - java线程优先级1-10.
- MIN_PRIORITY = 1
- NORM_PRIORITY = 5
- MAX_PRIORITY = 10
如果总有一个较高的优先级在运行,或者相同的优先级线程不退出,那么这个线程可能永远没有运行机会。这种情况称为资源竞争或缺乏状态(contention or starvation)。为了避免竞争,高优先级线程必须定时调用yield方法或sleep方法。 - MIN_PRIORITY = 1
- 线程池是管理并发执行任务的理想方法。Java提供Executor接口来执行线程池中的任务。ExecutorService来管理和控制任务。
执行创建3个线程来并发执行3个任务
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.execute(printA);
executor.execute(thread1);
executor.execute(print100);
executor.shutdown();
- Executors.newFixedThreadPool(3):
将3改为1,这3个任务将顺序执行
改为Executors.newCachedThreadPool(),则为每>>个等待的任务创建一个新线程,即来一个任务,创建一个线程,所以,任务会并发的执行。
shutdown通知执行器关闭,不能接收新任务。但现有任务将继续执行直到完成。
线程同步
为避免竞争状态,应该防止多个线程同时进入程序的某一特定部分,程序中的这部分称为临界区(critical region)。 解决办法 - 添加关键字synchroized,下面两种方法都行。
public synchronized void xMethod() {
//thod body
}
public void xMethod() {
synchronized(this) {
//method body
}
}
同步(synhronized)方法在执行之前都隐式的加了锁。
- (显式)加锁。
- 实例方法:给调用该方法的对象加锁
- 静态方法:要给这个类加锁
- 实例方法:给调用该方法的对象加锁
private static Account account = new Account();
lock.lock();
try {
...
}
catch (InterruptedException ex) {
}
finally {
lock.unlock();
}
线程间协作
- await():让当前的线程都处于等待状态,直到条件发生
- signal():唤醒一个等待的线程
- signalAll():唤醒所有等待的线程
private static Condition newDeposit = lock.newCondition();
newDeposit.await();
newDeposit.signalAll();
java的内置监视器(java5 之前的内容) 一旦一个线程锁住对象,该对象就成为监视器。 用法:在方法或块上使用关键字synchronized. wait(),notify(),或notifyAll()方法在接收对象的同步方法或同步块调用。
synchronized (anObject) {
...
anObject.wait();
...
}
synchronized (anObject) {
...
anObject.notify();
...
}
阻塞队列(blocking queue) 阻塞队列在试图向一个满队列添加元素或者空队列删除元素时,会导致线程阻塞。 java支持3个具体的阻塞队列
- ArrayBlockingQueue :可选的容量ArrayBlockingQueue(capacity:int)或者指定公平性ArrayBlockingQueue(capacity:int,fair:boolea)
- LinkedBlockingQueue 可以创建不受限的LinkedBlockingQueue()或者受限的队列LinkedBlockingQueue(capacity:int)
- PriorityBlockingQueue可以创建不受限的PriorityBlockingQueue()或者受限的队列PriorityBlockingQueue(capacity:int)
- put 向尾添加元素
- take 向头删除元素
信号量
信号量可以用来限制访问共享资源的线程数,在访问资源之前,线程必须从信号量获取许可。在访问完资源之后,这个线程必须将资源返回给信号量。
- 创建许可
private static Semephore semaphore = new Semaphore(n);
- 从信号量获取许可(不可用,等待)
semaphore.acquire();
- 释放对信号源的许可
semaphore.release();
感觉信号量和锁是类似的,获得许可相当于上锁,上锁只允许一个资源使用,获得许可后,只允许有限的线程进行访问资源。
避免死锁
有时两个或者多个线程需要在几个共享的对象上获取锁,这可能会导致死锁。
资源排序(resource ordering)可以轻易避免死锁的发生。假如线程要想对资源2上锁,必须要先获取资源1的锁。
线程的状态
- 新建(New):创建新的线程
- 就绪(Ready):start() 或超时(cpu分配的时间用完)或yield
- 运行(Running):run()
- 阻塞(Blocked):join(等待目标结束中。。),sleep(等待超时中。。。),wait()等待通知中。。当阻塞的行为不起作用时,线程就被重新激活进入就绪状态。
- 结束(finished):run()完成
就绪和运行线程是激活的。新建,阻塞和结束线程没被激活。
同步集合
Java集合框架中的类不是线程安全的,也就是说,如果它们同时被多个线程访问和更新,它们的内容可能被破坏。
java有6中静态方法将集合转换为同步版本,称为同步包装类。
- syncoronizedCollection(c:Collection):Collection
- syncoronizedList(list:List):List
- syncoronizedMap(map:Map):Map
- syncoronizedSet(set:Set):Set 规则集
- syncoronizedSortedMap(s:SortedMap):SortedMap 有序图
- syncoronizedSortedSet(s:SortedSet):SortedSet 有序规则集
java.util.Vector java.util.Stack java.util.HashTable中的方法已经被同步,这些都是java中的旧类。java.util.ArrayList,java.util.LinkedList,java.util.Map是对应的新类。
同步包装类都是线程安全的,但是迭代器具有快速失败的特性:如果整个集合使用一个迭代器,迭代器通常抛出异常。为了避免这个错误,需要创建一个同步集合对象,并且在遍历它时,获取对象上的锁。
Set hashSet = Collections.synchronizedSet(new HshSet());
synchronized (hashSet) {
Iterator iterator = hashSet.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}