浅谈同步
1.为什么需要“线程同步”
线程间共享代码和数据虽然可以节省系统开销,提高程序运行效率,但同时也导致了数据的“访问冲突”问题,如何实现线程间的有机交互、并确保共享资源在某些关键时段只能被一个线程访问,即所谓的“线程同步”(Synchronization)就变得至关重要。
2. 临界资源
多个线程间共享的数据称为临界资源(CriticalResource),由于是线程调度器负责线程的调度,程序员无法精确控制多线程的交替顺序。因此,多线程对临界资源的访问有时会导致数据的不一致性即(出现线程的安全隐患)。
例:一售票系统工作
package csdn.java.thread;
publicclass TestMulThread {
/**
* @param args
*/
publicstaticvoid main(String[] args) {
Tickets t = new Tickets();
TicketsThread tt= new TicketsThread("北京",t);
TicketsThread tt1= new TicketsThread("青年",t);
}
}
class Tickets{
publicintticket ;
public Tickets(){
this.ticket = 10;
}
publicsynchronizedvoid show(String name){
System.out.println("name"+name+"..."+"抢购了the"+ticket+"张票");
ticket--;
}
}
class TicketsThread extends Thread{
String name;
Tickets t;
public TicketsThread(String name,Tickets t){
this.name =name;
this.t =t;
start();
}
publicvoid run(){
for(int i=0;i<5;i++){
t.show(name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
}
}
}
3. 互斥锁
每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
Java对象默认是可以被多个线程共用的,只是在需要时才启动“互斥锁”机制,成为专用对象。
/'sɪŋkrənaɪz /
关键字synchronized用来与对象的互斥锁联系
当某个对象用synchronized修饰时,表明该对象已启动“互斥锁”机制,在任一时刻只能由一个线程访问,即使该线程出现堵塞,该对象的被锁定状态也不会解除,其他线程任不能访问该对象。
synchronized关键字的使用方式有两种:
ü 用在对象前面限制一段代码的执行(同步代码块)
ü 同步代码块可以使用任意对象作为锁,同步方法使用的锁只有一个--this。static同步方法使用的锁是该方法所属类的对象。类型.class
ü
public void push(char c){
…
sychronized(this){
data[index]=c;
index++
}
}
ü 用在方法声明中,表示整个方法为同步方法
public synchronizedchar pop(){
index--;
return data[index];
}
同步好处:决了线程安全问题
同步弊端
ü 降低了运行效率(判断锁是较为消耗资源的)
ü 同步嵌套,容易出现死锁
4.死锁
两个线程A、B用到同一个对象s(s为共享资源),且线程A在执行中要用到B运行后所创造的条件。在这种前提下A先开始运行,进入同步块后,对象s被锁定,接着线程A因等待B运行结束而进入阻塞状态,于是B开始运行,但因无法访问对象s,线程B也进入阻塞状态,等待s被线程A解锁。最终的结果:两个线程互相等待,都无法运行。
如:
public class DeadLock {
/**
* @param args
*/
public static voidmain(String[] args) {
Demo6 d1=newDemo6(true);
Demo6 d2=newDemo6(false);
Thread t1=newThread(d1);
Thread t2=newThread(d2);
t1.start();
t2.start();
}
}
class MyLock{
static MyLock lock1=newMyLock();
static MyLock lock2=newMyLock();
}
class Demo6 implements Runnable{
//String str1=newString("aaa");
//String str2=new String("bbb");
private boolean flag;
public Demo6(boolean flag){
this.flag=flag;
}
@Override
public void run() {
if(flag){
synchronized(MyLock.lock1){
System.out.println(Thread.currentThread().getName()+"...if...str1");
synchronized(MyLock.lock2){
System.out.println(Thread.currentThread().getName()+"...if...str2");
}
}
}else{
synchronized(MyLock.lock2){
System.out.println(Thread.currentThread().getName()+"...else...str2");
synchronized(MyLock.lock1){
System.out.println(Thread.currentThread().getName()+"...else...str1");
}
}
}
}
}
这是线程死锁的典型表现,两个以上线程并发运行,他们均因其他线程锁定了自己运行所需资源而陷入阻塞状态,同时自己也锁定了其他线程所需资源。
分析:子线程t并未锁定任何共享资源,只是因为无法访问共享资源sb而陷入阻塞状态。主线程则是因为串行加入了子线程t而进入阻塞状态,且必须要等线程t运行完毕才可能恢复运行并解除对共享资源的锁定,双方僵持、互不相让,导致进入“死锁”状态。