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();
                }
            }
        }
    }
}

 

上一篇:Java与Python在对象初始化上的不同


下一篇:leetcode-初级算法-数组