Java多线程--安全问题

来源:互联网 发布:c语言输出人名心形图案 编辑:程序博客网 时间:2024/06/16 22:06

Java多线程--同步和死锁

一、同步问题的出现

     通过Runnable接口实现多线程,类中的属性被多个对象共享。此时就出现了访问冲突这个严重的问题。

     典型的java例子:

     class myThread implements Runnable{
        private int ticket = 5;
        public void run(){
            for(int i = 0;i < 50;i++){
                if(ticket > 0){
                    try{
                        Thread.sleep(1000);
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                    System.out.print(Thread.currentThread().getName());
                    System.out.println(" Ticket:" + ticket--);
                }
            }
        }
    }
    public class SyncDemo{
        public static void main(String[]args){
            myThread my = new myThread();
            Thread t1 = new Thread(my,"Runnable-A");
            Thread t2 = new Thread(my,"Runnable-B");
            Thread t3 = new Thread(my,"Runnable-C");
            t1.start();
            t2.start();
            t3.start();
        }
    }

    在上例中通过现实Runnable接口实现多线程,产生了三个线程对象,三个线程对象都是通过Runnale接口子类myThread实例化。三个线程对象共享ticket属性。这个程序的运行结果:

    Runnable-B Ticket:5
    Runnable-A Ticket:4
    Runnable-C Ticket:3
    Runnable-B Ticket:2
    Runnable-A Ticket:1
    Runnable-C Ticket:0
    Runnable-B Ticket:-1

    分析运行结果发现,出现了票数为0 和-1的情况,那么一个线程就有可能在还没有对票数进行减操作之前,其他线程已经将票数减少了,这样出现了票数为非正的情况。如果解决这样的问题,必须使用同步。同步就是指多个操作在一个时间段内只能有一个线程进行操作,其他线程要等待该线程操作完之后再进行操作。


二、同步

    解决资源共享的同步操作,可以使用同步代码块和同步方法两种方式完成。

    Java中的每一个对象都有一个锁(lock),或者叫监视器(monitor),还称为同步互斥锁。当一个线程访问某个对象的synchronized方法时,将该对象上锁,其他线程都无法访问该对象的synchronized方法了,直到之前的那个线程执行完毕或者抛出异常,才会将该对象的锁释放掉。

    1、同步代码块

    同步代码块的格式:

    synchronized(同步对象){

         需要同步的代码;

     }

     使用同步代码块的时候必须指定一个需要同步的对象,但是一般设置成当前对象(this)。使用的最多的一种方式。

    class myThread implements Runnable{
        private int ticket = 5;
        public void run(){
            for(int i = 0;i < 50;i++){
                synchronized(this){
                    if(ticket > 0){
                        try{
                            Thread.sleep(1000);
                        }catch(Exception e){
                            e.printStackTrace();
                        }
                        System.out.print(Thread.currentThread().getName());
                        System.out.println(" Ticket:" + ticket--);
                    }
                }
            }
        }
    }
    public class SyncDemo{
        public static void main(String[]args){
            myThread my = new myThread();
            Thread t1 = new Thread(my,"Runnable-A");
            Thread t2 = new Thread(my,"Runnable-B");
            Thread t3 = new Thread(my,"Runnable-C");
            t1.start();
            t2.start();
            t3.start();
        }
    }

    2、同步方法

    使用synchronized关键字将一个方法修成同步方法。相当于第一种方式的缩写。

    同步方法的格式:

    访问权限  synchronized 方法返回值  方法名称(参数列表){

          方法体;

    }

    使用同步方法示例:

    class myThread implements Runnable{
        private int ticket = 5;
        public void run(){
            for(int i = 0;i < 50;i++){
                this.sale();
            }
        }
        public synchronized void sale(){
            if(this.ticket > 0){
                try{
                    Thread.sleep(1000);
                }catch(Exception e){
                    e.printStackTrace();
                }
                System.out.print(Thread.currentThread().getName());
                System.out.println(" Ticket:" + this.ticket--);
            }
        }
    }
    public class SyncDemo{
        public static void main(String[]args){
            myThread my = new myThread();
            Thread t1 = new Thread(my,"Runnable-A");
            Thread t2 = new Thread(my,"Runnable-B");
            Thread t3 = new Thread(my,"Runnable-C");
            t1.start();
            t2.start();
            t3.start();
        }
    }

使用synchronized关键的注意事项:

   (1) 只能同步方法和代码块,而不能同步变量和类。

   (2) 每个对象只有一个同步锁。当使用同步时,要弄清在哪个对象上同步。如果是其他一个无关的对象,就没用了。

   (3) 不必同步类中所有的方法,类可以同时拥有同步和非同步方法。

   (4) 如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

   (5) 如果线程有同步和非同步方法,非同步方法可以被多个线程自由访问而不受限制。

   (6) 线程睡眠时,它所持的任何同步锁都不会释放。 

   (7) 线程可以获得多个同步锁。 

   (8) 同步损害并发性,应该尽量缩小使用范围。能用同步代码块就用同步代码块。 

   (9) 编写线程安全的代码会使系统总体性能降低,要适量使用。

一个线程取得了同步锁什么时候会释放掉:

   (1) 同步代码块或者同步方法正常结束

   (2) 使用return或break终止执行,或者抛出未处理的异常。

   (3) 当线程执行同步代码块或方法时,程序执行了同步锁对象的wait()方法。

三、死锁

    死锁:多个线程同时被阻塞,他们中的一个或者全部都在等待某个资源被释放。由于线程无限期的阻塞,导致程序无法正常运行。

    死锁的根源:过多和不适当的运synchronized关键词来管理线程对特定的对象访问。


    因此避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C。 















0 0
原创粉丝点击