• Java 多线程系列02


    1. 线程安全

      synchronized的使用

      在多线程使用共享资源时,可以使用synchronized来锁定共享资源,使得同一时刻,只有一个线程可以访问和修改它,修改完毕后,其他线程才可以使用。

      当一个共享数据被synchronized修饰后,在同一时刻,其他线程只能等待,直到当前线程释放该锁。

      示例1  在不适用线程锁的情况下模拟账户取款操作

    public class AccountTest{
        public static void main(String[] args){    
            Account ac = new Account("actno-1",10000);
            AccountThread at1 = new AccountThread(ac);
            AccountThread at2 = new AccountThread(ac);
            at1.setName("t1");
            at2.setName("t2");
            at1.start();
            at2.start();
        }
    } 
    
    
    class AccountThread extends Thread{
        private Account act;
        
        public AccountThread(){}
        
        public AccountThread(Account act){
            this.act = act;
        }
        
        public void run(){
            act.withdraw(5000);
            System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款成功,余额" + act.getMoney());
            
        }
    }
    
    class Account{
        private String actno;
        private double money;
        public Account(){}
        public Account(String actno, double money){
            this.actno = actno;
            this.money = money;
        }
        
        public void setActno(String actno){
            this.actno = actno;
        }
        public String getActno(){
            return actno;
        }
        public void setMoney(double money){
            this.money = money;
        }
        public double getMoney(){
            return money;
        }
        
        //取款
        public void withdraw(double money){
            double begin = getMoney();
            double after = begin - money;
            try{
                Thread.sleep(1000);   //模拟取款过程中如果发生延迟,则会出现安全问题
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            setMoney(after);
        }
    }
    运行结果
    t1对actno-1取款成功,余额5000.0
    t2对actno-1取款成功,余额5000.0

      显然这个结果非预期的结果,两个线程对同一个账户取款,每次取5000,最后余额应该为0,但是余额还是5000。

      这是因为当t1线程还没执行完,就执行了t2线程,这样就会出现网络安全问题,要解决这个网络安全问题,我们就可以使用synchronized同步锁。

      

      synchronized原理

      当一个线程访问对象的synchronized方法或synchronized代码块时,其他线程对该对象的该synchronized代码块或synchronized方法的访问将会被阻塞。

      

      synchronized的三种作用方式

      修饰实例方法,作用与当前实例加锁,进入同步代码块要获得当前实例的锁;

      修饰静态方法,作用于当前类对象枷锁,进入同步代码块要获得当前类对象的锁;

      修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块要获得给定对象的锁;

      示例2  对取款操作修改,使用synchronized修饰实例方法

    public class AccountTest{
        public static void main(String[] args){    
            Account ac = new Account("actno-1",10000);
            AccountThread at1 = new AccountThread(ac);
            AccountThread at2 = new AccountThread(ac);
            at1.setName("t1");
            at2.setName("t2");
            at1.start();
            at2.start();
        }
    } 
    
    
    class AccountThread extends Thread{
        private Account act;
        
        public AccountThread(){}
        
        public AccountThread(Account act){
            this.act = act;
        }
        
        public void run(){
            act.withdraw(5000);
            System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款成功,余额" + act.getMoney());
            
        }
    }
    
    class Account{
        private String actno;
        private double money;
        public Account(){}
        public Account(String actno, double money){
            this.actno = actno;
            this.money = money;
        }
        
        public void setActno(String actno){
            this.actno = actno;
        }
        public String getActno(){
            return actno;
        }
        public void setMoney(double money){
            this.money = money;
        }
        public double getMoney(){
            return money;
        }
        
        //取款
        public synchronized void withdraw(double money){
            double begin = getMoney();
            double after = begin - money;
            try{
                Thread.sleep(1000);   //模拟取款过程中如果发生延迟,则会出现安全问题
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            setMoney(after);
        }
    }
    运行结果
    t1对actno-1取款成功,余额5000.0
    t2对actno-1取款成功,余额0.0

      修饰示例方法,就是在方法的前面加synchronized,这时的共享对象就是this,同步范围是整个方法体,这样会扩大同步范围,效率较低。

      示例3  对取款操作修改,使用synchronized修饰代码块

    public class AccountTest01{
        public static void main(String[] args){
            Account act = new Account("actno-1",10000);
            AccountThread at1 = new AccountThread(act);
            AccountThread at2 = new AccountThread(act);
            at1.setName("t1");
            at2.setName("t2");
            at1.start();
            at2.start();
        }
    }
    
    class AccountThread extends Thread{
        private Account act;
        public AccountThread(){}
        public AccountThread(Account act){
            this.act = act;
        }
        public void run(){
            act.withdraw(5000);
            System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款成功,余额" + act.getMoney());
        }
    }
    
    class Account{
        private String actno;
        private double money;
        Object obj1 = new Object();
        
        public Account(){}
        public Account(String actno, double money){
            this.actno = actno;
            this.money = money;
        }
        
        public void setActno(String actno){
            this.actno = actno;
        }
        public String getActno(){
            return actno;
        }
        public void setMoney(double money){
            this.money = money;
        }
        public double getMoney(){
            return money;
        }
        
        public void withdraw(double money){
            //Object obj2 = new Object();
            synchronized(this){            //this可以作为共享对象,可以使t1和t2线程排队执行
            //synchronized(obj1){            //obj1也可作为共享对象
            //synchronized(obj2){              //obj2不可以作为共享对象
                double begin = getMoney();
                double after = begin - money;
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                setMoney(after);
            }
        }
    }

      关于synchronized括号内的共享对象

      this和obj1都可以作为共享对象,可以让线程排队执行,所以就是线程的共享对象;

      obj2不可以作为共享对象,obj2是一个局部变量,每次执行都会创建一个对象,不是共享对象。

    2. 死锁

       死锁是两个线程都在等待彼此先执行完,多个线程共享同一资源时需要进行同步,以保证资源操作完整性,可能会产生死锁。

    public class ThreadTest13{
        public static void main(String[] args){
            Object o1 = new Object();
            Object o2 = new Object();
            //mt1线程和mt2线程共享o1和o2对象;
            MyThread1 mt1 = new MyThread1(o1,o2);
            MyThread2 mt2 = new MyThread2(o1,o2);
            mt1.start();
            mt2.start();
        }
    }
    
    class MyThread1 extends Thread{
        Object o1;
        Object o2;
        public MyThread1(Object o1, Object o2){
            this.o1 = o1;
            this.o2 = o2;
        }
        public void run(){
            synchronized(o1){
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
            synchronized(o2){}
        }
    }
    
    class MyThread2 extends Thread{
        Object o1;
        Object o2;
        public MyThread2(Object o1, Object o2){
            this.o1 = o1;
            this.o2 = o2;
        }
        public void run(){
            synchronized(o2){
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
            synchronized(o1){}
        }
    }

      mt1线程和mt2线程共享o1和o2对象,都在等待对方先释放资源,产生死锁。

    3.  售票程序

       用4个线程实现卖票

      MyException.java
    public class MyException extends Exception{
        public MyException(){}
        public MyException(String message){
            super(message);
        }
    }

      Ticket.java

    public class Ticket{
        private String[] tickets;
        private int initialTicketNumber;
        private int sellOutTicket;
        
        public String[] getTickets(){
            return tickets;
        }
        public int getInitialTicketNumber(){
            return initialTicketNumber;
        }
        public Ticket(int initialTicketNumber){
            tickets = new String[initialTicketNumber];
            this.initialTicketNumber = initialTicketNumber;
        }
        public Ticket(){
            this(200);
        }
        public void initializeTicket(){
            for(int i=0; i < tickets.length; i++){
                tickets[i] = i + "号车票";
            }
        }
        public synchronized String sellTickets() throws MyException{
            if(initialTicketNumber - sellOutTicket > 0){
                String ticket = tickets[sellOutTicket];
                try{
                    Thread.sleep(100);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                sellOutTicket++;
                return ticket;
            }else{
                throw new MyException("票已卖完");
            }
        }
    }

      InitialTicketThread.java

    public class InitialTicketThread extends Thread{
        private Ticket tk;
        
        public InitialTicketThread(Ticket tk){
            this.tk = tk;
        }
        
        public void run(){
            tk.initializeTicket();
        }
    }

      SaleTicketThread.java

    public class SaleTicketThread extends Thread{
        private Ticket tk;
        boolean flag = true;
        
        public SaleTicketThread(Ticket tk){
            this.tk = tk;
        }
        
        public void run(){
            while(flag){
                try{
                    String ti = tk.sellTickets();
                    System.out.println(Thread.currentThread().getName() + "卖票成功:" + ti);
                }catch(MyException e){
                    e.printStackTrace();
                    flag = false;
                }
            }
        }
    }

      SaleTicketThreadTest

    public class SaleTicketThreadTest{
        public static void main(String[] args) throws InterruptedException{
            Ticket ticket = new Ticket(10);
            Thread t0 = new Thread(new InitialTicketThread(ticket));
            t0.setName("t0");
            t0.start();
            t0.join();
            
            Thread t1 = new Thread(new SaleTicketThread(ticket));
            t1.setName("t1");
            t1.start();
            
            Thread t2 = new Thread(new SaleTicketThread(ticket));
            t2.setName("t2");
            t2.start();
            
            Thread t3 = new Thread(new SaleTicketThread(ticket));
            t3.setName("t3");
            t3.start();
            
            Thread t4 = new Thread(new SaleTicketThread(ticket));
            t4.setName("t4");
            t4.start();
        }
    }

    4. 生产者消费者模式

       生产者给容器(list)中生产一个产品,消费者从容器中消费一个产品

    public class ThreadTest16{
        public static void main(String[] args){
            List list = new ArrayList();
            Thread t1 = new Thread(new Producer(list));
            Thread t2 = new Thread(new Consumer(list));
            t1.setName("生产者");
            t2.setName("消费者");
            t1.start();
            t2.start();
        }
    }
    
    class Producer implements Runnable{
        //生产者和消费者对同一个容器操作;
        private List list;
        public Producer(List list){this.list = list;}
        
        public void run(){
            while(true){
                synchronized(list){
                    if(list.size() > 0){    //每次容器中只有一个产品
                        try{
                            list.wait();    //生产者处于等待状态,wait()方法同时会释放当前对象的线程所占用的锁
                        }catch(InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                    Object obj = new Object();  //容器中没有产品了,就需要生产了
                    list.add(obj);
                    System.out.println(Thread.currentThread().getName() + "--->" +obj);
                    list.notifyAll();    //唤醒消费者对象开始消费;此时可能会抢占资源,但是就算生产者抢到资源也会因为容器内有产品而处于等待状态
                }
            }
        }
    }
    
    class Consumer implements Runnable{
        private List list;
        public Consumer(List list){this.list = list;}
        
        public void run(){
            synchronized(list){
                while(true){
                    synchronized(list){
                        if(list.size() == 0){    //如果容器中没有产品就需要等待
                            try{
                                list.wait();
                        }catch(InterruptedException e){
                                e.printStackTrace();
                            }
                        }
                        Object obj = list.remove(0);
                        System.out.println(Thread.currentThread().getName() + "--->" +obj);
                        list.notifyAll();
                    }
                }
            }
        }
    }
  • 相关阅读:
    135、JS和Android交互范例
    《将博客搬至CSDN》
    Android常用工具类
    114、drawable和mipmap 目录下图片的区别
    StringUtils 工具类的常用方法(转载)
    判断当前的Activity的是否处于栈顶
    Git-简单的利用SourceTree提交代码(转载)
    133、 Android 自动化测试(转载)
    c# 利用结构体获取json数据
    如何编写PMP项目管理中的项目立项书
  • 原文地址:https://www.cnblogs.com/homle/p/15257736.html
Copyright © 2020-2023  润新知