java多线程总结<--->线程同步
1:首先我们从一个例子看起:
package MutilpThread;
public class MyThread implements Runnable {
private int times = 0;
public MyThread(int times)
{
this.times = times;
}
@Override
public void run() {
while(this.times>0)
{
System.out.println("执行:"+Thread.currentThread().getName()+times);
times--;
}
System.out.println("在线程执行中"+Thread.currentThread().getName()+"执行结束");
}
public static void main(String [] args) {
// TODO Auto-generated method stub
Thread myThread = new Thread(new MyThread(3),"线程1");
Thread myThread2 = new Thread(new MyThread(5),"线程2");
myThread.start();
myThread2.start();
System.out.println("主线程结束");
}
}
这段代码在我的机器上的执行结果是:
由此可见这段代码并不是像我们想象的那样首先将”线程1“执行完,然后再执行”线程2“。
其实控制台就像一个普通的资源,“线程1”和“线程2”轮流占用控制台资源来输出信息。既然这样,那么“线程1”和“线程2”
分别什么时候占用资源进行控制台输出呢?
当一个程序中运行着多个线程的时候,java平台并不保证这些线程的代码执行顺序。也就是说多线程之间占有资源执行代码的速率是随机的。
2:线程同步
在实际的应用程序中,经常会发生2个线程同时访问一个对象并对这个对象进行修改的情况,这样就会造成线程的倾轧和数据的混乱。这个时候就需要线程的同步来避免这种
情况的发生。
1:使用锁对象进行同步
首先我们再看下上面那个例子,并且加上锁:
import java.util.concurrent.locks.ReentrantLock;
public class MyThread implements Runnable {
private int times = 0;
private ReentrantLock reen ;
public MyThread(int times,ReentrantLock ren)
{
this.times = times;
this.reen = ren;
}
public void printLogs()
{
reen.lock();
try{
while(this.times>0)
{
System.out.println("执行:"+Thread.currentThread().getName()+times);
times--;
}
}
finally{
reen.unlock();
System.out.println(Thread.currentThread().getName()+"退出运行");
}
}
public void run() {
printLogs();
}
public static void main(String [] args) {
ReentrantLock ren = new ReentrantLock();
Thread myThread = new Thread(new MyThread(3,ren),"线程1");
Thread myThread2 = new Thread(new MyThread(5,ren),"线程2");
myThread.start();
myThread2.start();
System.out.println("主线程结束");
}
}
这段代码在我的机器上的运行结果是:
在这里我们可以看到加了锁之后线程的执行不在混乱。
锁的定义:
ReentrantLock myLock = new ReentrantLock();
几个相应的API
lock();//获取这个锁
unlock();//释放这个锁
通过使用锁就可以确保在临界区同一时间只有一个线程在进行操作,从而避免线程的混乱。
2:条件对象
很多情况下当一个线程进入一个临界区后发现必须等待某一个条件满足后才能继续向下执行。我们可以用条件对象来管理那些获得了锁对象但却不能执行工作内容的线程。
条件对象的定义:
Condition con =myLock.newCondition();
条件对象的使用:
1:con.await();
当一个线程对象调用这一个方法后,该线程对象会立即释放锁,同时被阻赛进入等待集合中。
线程调用await方法进入等待状态后和等待获得锁的线程有一个本质的区别,就是当锁对象可获得后并不能立刻解除阻塞状态知道另外一个线程在同一个条件上调用signalAll()方法
2:con.signalAll()
当一个线程调用这个方法后,其它等待这个条件的所有线程将被从等待队列中移除并且成为可运行的。一单锁对象成为可获得的,获得锁的这个线程将从await的调用处返回,从而试图重新进入对象并从调用await处继续执行。
Note:signalAll()仅仅是通知其它的线程现在条件可能满足了,至于是否满足还需要线程本身进行测试。
3:con.signal()
随机解除等待集中的某个线程的等待状态。
锁对象总结:
- 通过使用锁对象可以保护代码片断,任何时刻只有一个线程可以执行被锁保护的代码片段中的代码。
- 锁可以管理试图进入被保护代码片段的线程
- 每一个锁可以拥有几个不同的相关条件对象
- 条件对象可以用来管理那些已经进入被保护代码片段但是还不能执行线程。
3:synchronized 关键字
一个简单的例子:
public synchronized void methond()
{
method();
}
等价于:
public void method()
{
reen.lock();
try{method();}finally{ reen.unlock();}}
使用synchronized加隐式锁时,每一个对象都有锁对象。每个锁都有一个隐式条件对象,使用wait()和notify()/notifyall()来唤醒或者是线程进入等待状态。
由锁来管理进入synchronzied方法的线程,由条件来管理那些进入wait()的线程。
隐式的锁和条件存在的一些缺点:
1:不能中断一个试图获得锁的线程。
2:试图获得锁时不能设定超时
3:每个锁只有一个条件有时显得不够用。
4:虚拟机的加锁原语不能很好的映射到硬件可用的最有效的加锁机制上
那么在代码中选择哪种加锁机制好呢?是使用Lock和Condition对象还是synchronized关键字呢?
1:最好既不使用Lock\Condition对象也不使用synchronized对象,在很多情况下可以使用java.util.concurrent包中的一种机制,他可以为你处理所有的加锁。
2:如果synchronzied关键字在你的代码中可以工作,那么尽量使用它这样可以减少代码的数量,减少出错的几率。
3:如果你非常需要Lock/Condition的特性的时候才选择他们。
4:同步块
例子如下:
synchronzied(obj)
{
//要同步的代码
}
每一个对象都有一个锁,线程通过两种方法获得锁,调用一个同步方法或进入一个同步块。
5:Volatile
有些时候我们只需要同步某一个对象的一个或者两个域,这时如果我们就使用方法同步的话会导致开销过大的问题。
将一个变量用volatile申明可以达到和加锁同样的目的,但是访问volatile生命的变量比访问一般的变量速度要慢,这个是为线程安全付出的代价。