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