多线程学习笔记(2)
四、线程同步
4.1.ReentranLock
有两种机制防止代码块受到并发访问的干扰,java语言提供了一个synchronized关键字达到这一目的,以及在jdk5.0引入了ReentrantLoc类 先看看ReentranLOck,这个是jdk1.5添加
myLock.Lock();
try{
执行语句
}
finally
{
myLock.unLock
}
这一结构确保任何时刻只有一个线程进入try执行任务,一旦一个线程用锁封锁了对象,其他任何线程都无法访问lock语句,当其他线程调用的时候,将会进入阻塞状态,直到前一个线程释放锁对象,即unLock(); 另外,把解锁操作放在finally语句中很重要,可以确保不管try区的代码是否抛出异常,锁都可以被释放,如果没用被释放,那其他正在阻塞的线程将被永远阻塞。 举一个例子
public class Bank{
Lock bankLock = new ReentrantLOck();
public void transfer(int from ,int to,int amount)
{
bankLock.lock();
try
{
System.out.println(Thread.currentThread());
amount[from] -= amount;
amount[from] += amount;
}
finally {
bankLock.unlock();
}
}
}
在上面这个例子中如果有一个线程调用transfer,如果第二个线程也调用了transfer,由于第二个线程不能获得锁,将在调用lock方法时被阻塞,必须等到第一个线程完成transfer方法执行完成,且执行了unlock方法之后才能被激活然后运行。 一个锁对象可以有一个或者多个条件对象,可以用newcondition获得一个条件对象, 例如
public condition sss
public method()
{
sss = 当前条件对象.newcondition
}
然后判断是否符合条件
例如
while(条件)
sss.await()
ReentrantLock中的await()方法
一旦一个线程调用了await()方法,将进入等待,直到另一个线程调用同一条件的signalAll方法激活所有因为这一条件而等待的线程, 并且,当一个线程调用await方法的时候,它没有办法激活自身,只有通过其他方法来激活,如果没有线程来激活,将进入死锁状态。
4.2.synchronized
如果一个方法用synchronized 关键字声明,那么对象的锁将保护整个方法,换句话说
public synchronized void method()
{
}
等价于
public void method()
{
myLock.lock();
try
{}
finally{myLock.unlock}
}
synchronized相比ReentrantLock来说更简洁,synchronized的等待方法为wait和notifyAll,换句话说,调用wait和notifyAll等价于,sss.await和sss.signalAll,但是功能没有ReentrantLock强大 举一个带有等待方法的例子
class {
public synchronized void method()
{
while(判断条件)
wait()
执行代码
notifyAll();
}
}
具体就是,每一个对象有一个内部锁,并且该锁有一个内部条件,由锁来管理进入synchronized的线程,由条件来管理调用wait的线程;
五、锁的选择
- 关于使用哪种锁 synchronized使用简单,功能不够强大。能用synchronized完成的工作就尽量用synchronized以减少出错的几率。
- ReentrantLock使用较synchronized复杂,功能强大。
- ReentrantReadWriteLock读写锁,读可以并行,写要串行,条件允许的情况下性能好。
以下为转载
- synchronized关键字的作用域有二种: 1) 是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
- 除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;
- synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法; Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。 总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述之前,我们需要明确几点: A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。 B.每个对象只有一个锁(lock)与之相关联。 C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
六、线程的注意事项
1、线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏。 2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。 3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。 4、对于同步,要时刻清醒在哪个对象上同步,这是关键。 5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。 6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
七、线程池
基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。 线程池的优点 1)避免线程的创建和销毁带来的性能开销。 2)避免大量的线程间因互相抢占系统资源导致的阻塞现象。 3}能够对线程进行简单的管理并提供定时执行、间隔执行等功能。
如何使用线程池 创建一个线程池对象,控制要创建几个线程对象。 public static ExecutorService newFixedThreadPool(int nThreads)
这种线程池的线程可以执行: 可以执行Runnable对象或者Callable对象代表的线 ,做一个类实现Runnable接口。
调用如下方法即可 Future<?> submit(Runnable task)
Future submit(Callable task) 关闭线程池
shutdown()
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
public class ExecutorsDemo {
public static void main(String[] args) {
// 创建一个线程池对象,控制要创建几个线程对象。
// public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
八、一些简单的面试题
1:多线程有几种实现方案,分别是哪几种? 两种。
继承Thread类 实现Runnable接口
扩展一种:实现Callable接口。这个得和线程池结合。
2:同步有几种方式,分别是什么? 两种。
同步代码块
同步方法
3:启动一个线程是run()还是start()?它们的区别? start();
run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
start():启动线程,并由JVM自动调用run()方法
4:sleep()和wait()方法的区别 sleep():必须指时间;不释放锁。 wait():可以不指定时间,也可以指定时间;释放锁。 5:为什么wait(),notify(),notifyAll()等方法都定义在Object类中 因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。 而Object代码任意的对象,所以,定义在这里面