Java的线程的同步与死锁

线程的同步

一、什么是线程的同步

1、线程同步的意义。

线程的同步是为了保证代码的原子性,保证每个线程在调用对象同步方法独占的方法操作该对象。一段代码就好像一座独木桥,任何一个时刻,只能有一个人在桥上行走,程序中不能有多个线程同时在这两句代码之间执行,这就是线程同步。比如:银行的自动柜员机,一次只能一个人取钱,其他人必须排队。公司的打印机虽然是多台电脑共用的,但是每次也只能一台电脑在打印。

同步是以牺牲程序的性能为代价的。所以,如果确定程序没有安全性的问题,就没有必要使用同步。

2、同步的实现,使用synchronize关键字

我们将需要具有原子性的代码放入synchronize语句内,形成同步代码块。就可以保证线程安全了。每个对象都有一个标志位(锁旗标),该标志位有两个状态0、1,其开始状态为1,当执行synchronized(Object)语句后,Object对象的标志位变为0状态,直到执行完整个synchronized语句中的代码块后才又回到1状态。

一个线程执行到synchronized(Object)的时候,先检查Object对象的标志位,0表示有线程在执行,这个线程将暂时阻塞,让出CPU资源,直到另外线程执行完有关代码,将Object对象状态恢复到1状态,这个阻塞才被取消,然后线程开始执行,同时将Object对象的标志位变为0,防止其他线程再进入有关的同步代码块中。

当线程执行到synchronized的时候,如果得不到锁标记,这个线程会被加入到一个与该对象的锁标记相关连的等待线程池当中,等待锁标记。当线程执行完同步代码,就会释放锁标记。

synchronized有两种用法

(1)定义同步方法: synchronized 方法名称(同步){ 方法体 } ,使用这个方法对象都会同步。

(2)同步块,灵活锁住对象同步方法就等于锁住this:synchronized(对象){ 同步块 }

二、示例代码

下面我们模拟公司有一台打印机,有多个学生同时操作电脑,点击打印自己的成绩,如果不使用同步,打印机打印出来的结果将会错乱,然后我们改成使用同步后,再查看结果,打印机会打印一个学生后,再打印另外一个学生,有序的把所有的学生成绩打印出来。

1、先定义打印机类,暂时不实现同步,代码如下:

/**
 * 打印机
 */
public class Print {
    
    public  void p(String name,int en,int math){
        System.out.print("姓名:"+name+":");
        System.out.println("数学成绩:"+math+",英语成绩:"+en);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

2、实现学生打印操作类,每个操作类就是一个线程,该线程调用同一个打印对象实现打印。代码如下:

public class StudentThread extends Thread {
    
    private Print print;
    private String name;
    private int en;
    private int math;
    
    public StudentThread(Print print,String name,int en,int math){
        this.print=print;
        this.name=name;
        this.en=en;
        this.math=math;
    }

    @Override
    public void run() {
        print.p(name, en, math);
    }
}

3、在main方法中定义一个打印机对象,和三个学生操作类线程,三个线程同时启动,代码如下:

public class Run {
    public static void main(String[] args) {
        Print print=new Print();
        
        StudentThread stu1=new StudentThread(print, "诸葛亮", 100, 100);
        StudentThread stu2=new StudentThread(print, "张飞", 10, 40);
        StudentThread stu3=new StudentThread(print, "刘备", 80, 90);
        
        stu1.start();
        stu2.start();
        stu3.start();
        
    }

}

运行上面的代码,控制台打印如下:

Java的线程的同步与死锁

可以看到所有人的成绩都错乱了,根据不知道那个成绩是那一个学生的。要防止这种情况非常简单,只需要在打印类中的打印方法前面加上synchronized关键字实现同步即可,修改打印机类如下:

public class Print {
    
    public synchronized  void p(String name,int en,int math){
        System.out.print("姓名:"+name+":");
        System.out.println("数学成绩:"+math+",英语成绩:"+en);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

再次运行代码,结果如下:

Java的线程的同步与死锁

 

死锁

一、死锁

同步有可能会导致另一个问题,就是死锁。死锁是指两个线程,都相互等待对方释放,死锁有时是不可测知或避开的,我们应该应采取措施避免死锁的出现。

二、死锁的例子

当然,这是一个反面例子。

1、定义一个资源类,为了标识每个资源,该类只有一个属性,就是资源名称。代码如下:

public class Resource {
    
    private String Name;

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }
}

2、定义一个资源使用类,该类将会使用到两个资源对象,都是先锁住第一个时,然后在企图锁定另外一个。代码如下:

public class ResourceThread extends Thread{
    
    private String threadName;//线程名称
    
    private Resource rs1;
    private Resource rs2;
    
    public ResourceThread(String threadName,Resource rs1,Resource rs2){
        
        this.threadName=threadName;
        this.rs1=rs1;
        this.rs2=rs2;
        
    }
    
    public void run(){
        
        //
        System.out.println(threadName+"企图占有"+rs1.getName());
        synchronized (rs1) {
            System.out.println(threadName+"已经锁定了"+rs1.getName());
            
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            System.out.println(threadName+"企图占有"+rs2.getName());
            synchronized (rs2) {
                System.out.println(threadName+"已经锁定了"+rs2.getName());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
            }
            System.out.println(threadName+"释放"+rs2.getName());
            
        }
        System.out.println(threadName+"释放"+rs1.getName());
        
    }

}

3、模拟,现在只有三个资源对象,三个线程对象,

线程一锁住资源一,其他占用资源二;

线程二锁住资源二,其他占用资源三;

线程三锁住资源三,其他占用资源一;

示意图如下:

Java的线程的同步与死锁

代码实现如下:

public class Run {
    
    public static void main(String[] args) {
        
        Resource resource1=new Resource();
        resource1.setName("资源一");
        
        Resource resource2=new Resource();
        resource2.setName("资源二");
        
        Resource resource3=new Resource();
        resource3.setName("资源三");
        
        ResourceThread thread1=new ResourceThread("线程一", resource1, resource2);
        ResourceThread thread2=new ResourceThread("线程二", resource2, resource3);
        ResourceThread thread3=new ResourceThread("线程三", resource3, resource1);
        
        thread1.start();
        thread2.start();
        thread3.start();
        
    }

}

结果是哪一个线程都不能正常结束,程序也就一直耗着。你也只能终止这个程序了。