线程安全问题产生的原因:
1,多个线程在操作共享的数据。
2,操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。就会导致线程安全问题的产生。
解决思路;
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码 ;
}
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。
同步的前提:同步中必须有多个线程并使用同一个锁。
例1:多个窗口购买火车票实例:
package day04;
public class JavaTest {
public static void main(String[] args) {
SaleTicket st=new SaleTicket();
Thread t1=new Thread(st,"一号窗口");
Thread t2=new Thread(st,"二号窗口");
Thread t3=new Thread(st,"三号窗口");
Thread t4=new Thread(st,"四号窗口");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class SaleTicket implements Runnable{//sale英 [seɪl] n.拍卖;卖,出卖;
private int tickets=100;
@Override
public void run() {
while(tickets>0){
sale();
}
}
private synchronized void sale(){//synchronized 同步。
if(tickets>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(100-tickets+1)+"张票");
tickets --;
try{
Thread.sleep(500);//假设购票过程需要500毫秒。
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
运行结果:
一号窗口卖出了第1张票
四号窗口卖出了第2张票
二号窗口卖出了第3张票
三号窗口卖出了第4张票
三号窗口卖出了第5张票
.................
四号窗口卖出了第99张票
四号窗口卖出了第100张票
例2: 创建两个线程t1和t2同时减桌子上的20颗豆子。至到豆子的数量减为0,人为抛出异常为止。这是桌上的豆子就是公共事件,两个线程会同时去访问。所以要加synchronized将公共资源同步的锁起来。
package day04;
/**
* 当多个线程同时操作一段数据时,由于线程切换的不确定性,可能会导致逻辑出现混乱。
* @author Administrator
*/
public class SyncDemo {
public static void main(String[] args) {
final Table table = new Table();
Thread t1 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
//模拟CPU没有时间了
Thread.yield(); //yield英 [ji:ld] vi.放弃;退让,退位
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
Thread.yield(); //yield英 [ji:ld] vi.放弃;退让,退位
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int beans = 20;//桌子上有20个豆子
/*
* 当一个方法被synchronized修饰后,该方法 就是一个同步方法了。多个线程调用时不会同时进到方法内部。
* 需要注意的是synchronized一定会给一个对象上锁。在这里,锁的是当前方法所属的对象。
* 也就是上面main方法中创建的table实例。由于两个线程调用的是同一个table的getBean方法。所以两个线程不能同时进到方法内部。
*/
public synchronized int getBean(){
if(beans==0){throw new RuntimeException("没有豆子了!");}
Thread.yield();
return beans--;
}
}
运行结果:
Thread-1:20
Thread-1:18
Thread-1:17
Thread-1:16
Thread-1:15
Thread-1:14
Thread-1:13
Thread-1:12
Thread-1:11
Thread-1:10
Thread-1:9
Thread-1:8
Thread-1:7
Thread-1:6
Thread-1:5
Thread-1:4
Thread-1:3
Thread-1:2
Thread-1:1
Thread-0:19
Exception in thread "Thread-1" Exception in thread "Thread-0" java.lang.RuntimeException: 没有豆子了!
at day04.Table.getBean(SyncDemo.java:41)
at day04.SyncDemo$2.run(SyncDemo.java:22)
java.lang.RuntimeException: 没有豆子了!
at day04.Table.getBean(SyncDemo.java:41)
at day04.SyncDemo$1.run(SyncDemo.java:12)