java多线程及安全问题

来源:互联网 发布:网络暴力论文与人文 编辑:程序博客网 时间:2024/06/05 03:07



进程:是一个正在执行中的程序。
      每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

线程:就是进程中的一个独立的控制单元。
        线程在控制着进程的执行。

通过对api的查找,java已经提供了对线程这类事物的描述,就是Thread
1.创建线程的第一种方式:继承Thread类。
    步骤:
        1.定义类继承Thread
        2.复写Thread类中的run方法。
            目的:将自定义代码存储在run方法,让线程运行。
        3.调用线程的start方法,启动线程,调用run方法。
    class Demo extends Thread
    {
        //run()方法为线程的主要方法,所要依赖线程运行的代码都要在这个函数里运行
        public void run()
        {
            //多线程运行的代码块
        }
    }
    main(String args[])
    {
        //创建Thread子类对象的同时,线程也被创建了。
        Demo d = new Demo();
        //start方法开启线程,执行Demo类中的run方法。
        d.start();
        //如果调用run方法,就仅仅是调用了Demo类中run方法,并未启动新的线程
        d.run();
    }

各种方法的作用:
    sleep(time) 使线程冻结一段时间,时间到后线程自动运行;
    wait() 使线程暂停,等待notify() 方法唤醒后线程方可继续运行;
    stop() 结束当前线程。



例子:多个窗口同时卖票。
    利用第一种创建线程的方式实现卖票
class Ticket extends Thread{
    //利用静态成员变量共享票数。但是静态变量比较耗费资源。
    private static int tick = 100;
    public void run(){
        while(true){
            if(tick>0){
                System.out.println(Thread.currentThread().getName()+": "+tick--);
            }
        }
    }
}
    public static void main(String args[]){
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        Ticket t4 = new Ticket();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

2.创建线程的第二种方式:实现Runable接口

    步骤:
    1.定义类实现Runnable接口
    2.覆盖Runnable接口中的run方法。
        将线程要运行的代码存放在该run 方法中。
    3.通过Thread类建立线程对象。
    4.Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
        为什么要将Runnable接口的子类对象传递给Thread的构造函数。
        因为,自定义的run方法所属的对象时Runnable节后的子类对象。
        所以要让线程去指定指定对象的run方法,就必须明确该run方法所属对象。
    5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

售票例子:
class Ticket implements Runnable{
    private int tick = 100;
    public void run(){
        while(true){
            if(tick>0){
                System.out.println(Thread.currentThread().getName()+": "+tick--);
            }
        }
    }
}
    public static void main(String args[]){
        //与上面的售票相比,实现票数共享是通过只创建一个Runnable子类对象来实现
        //将此对象通过多个线程来运行,以达到多线程效果。
        Ticket t1 = new Ticket();
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
        new Thread(t4).start();
    }

实现方式和继承方式有什么区别呢?(面试常考)
    现方式好处:避免了单继承的局限性。
    在定义线程时,建议使用实现方式。
    java机制只允许继承一个父类,但是可以实现多个接口,有的时候一个子类需要继承父类
    但同时又需要被多线程执行时,就不可能再继续用继承Thread类的方式实现多线程了,
    只能通过实现Runnable 接口来实现多线程。


多线程安全问题:

问题的原因:    
    当多条哦语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完。
    另一个线程参与进来执行,导致共享数据的错误。
解决办法:
    对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可参与执行。
    同步代码块。

    synchronized(对象){
        //需要被同步的代码
    }
    对象如同锁,持有锁的线程可以在同步中执行。
    没有持有锁的线程即使获取cpu执行权,也进不去,因为没有获取锁。

    同步的前提:
        1.必须要有两个或者两个以上的线程。
        2.必须是多个线程使用同一个锁
    好处:解决了多线程的安全问题。
    弊端:多个线程需要判断锁,较为消耗资源。

    如何找问题:
    1.明确哪些代码是多线程运行代码。
    2.明确共享数据。
    3.明确多线程运行代码中哪些语句是操作共享数据的。

同步有两种形式:
    1.同步代码块 : synchronized(对象){
                    //需要被同步的代码
                   }
    2.同步函数 : 把synchronized作为修饰符放在函数上,此函数就具备了同步的特性。
        
        同步函数用得到是哪一个锁呢?
        函数需要被对象调用,那么函数都有一个所属对象引用,就是this
        所以同步函数使用的锁是this。利用如下代码验证:
        private int tick = 100;
        public void run(){
            if(flag){
                while(true){
                    synchronized(this/*此处未为验证精髓*/){
                        //同步代码块    
                        if(tick>0){
                            try{Thread.sleep(10);}catch(Exception e){}
                            System.out.println(Thread.currentThread().getName()+"..code.."+tick--)
                        }
                    }
                }
            }else
                while(true)
                show();
        }
        public synchronized void show(){
            //需要同步的内容
            if(tick>0){
                try{Thread.sleep(10);}catch(Exception e){}
                System.out.println(Thread.currentThread().getName()+"..show.."+tick--)
            }
        }
        //通过观察输出票数是否安全便可知道,同步函数的锁是否是this
        public void main(String args[]){
            new Thread(对象).run();
            //让线程等待10ms,以便确认0线程已进入代码块
            try{Thread.sleep(10);}catch(Exception e){}
            flag=false;
            new Thread(对象).run();
        }

        静态同步函数,使用的锁是什么呢?
        通过验证,不是this,因为静态方法中也不可以定义this
        静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
        类名.class 该对象的类型是Class ,所使用的锁是该方法所在类的字节码文件对象。

(重点)单例设计模式:
    饿汉式:
    class Single{
        //先创建对象,需要的时候直接调用
        private static final Single s = new Single();
        //将构造函数私有化,禁止外界的访问。
        private Single(){}
        public static Single getInstance(){
            return s;
        }
    }
    懒汉式:
    class Single{
        private static Single s = null;
        private Single(){}
        public static  Single getInstance(){
            //双重判断,提高懒汉式的效率,一次创建对象以后,就不再需要判断锁
            if(s==null){
                //多线程多次判断锁,影响效率
                synchronized(Single.class){
                    //如果不存在对象,才要创建对象,称延迟加载
                    if(s==null)
                        s=new Single();
                }
            }
            return s;
        }
    }


死锁:例子:
    public void run(){
        if(flag){
            while(true){
                synchronized(MyLock.locka){
                    Syste.out.printlnt("if locka");
                    synchronized(MyLock.lockb){
                        Syste.out.printlnt("if lockb");
                    }
                }
            }
        }else{
            while(true){
                synchronized(MyLock.lockb){
                    Syste.out.printlnt("else lockb");
                    synchronized(MyLock.locka){
                        Syste.out.printlnt("else locka");
                    }
                }
            }
        }
    }
    class MyLock {
        public static Object locka=new Object();
        public static Object lockb=new Object();
    }
        



原创粉丝点击