线程安全是指多个线程访问同一代码,不会产生不确定的结果;
线程同步:
为了避免多线程在共享资源时发生冲突,所以 要在线程使用该资源时,就为线程上一把“锁”,第一个访问资源的线程为资源上锁,其他线程若想访问该资源,则必须等待解锁为止,解锁的同时,另一个线程访问资源并为资源上锁。
在使用多线程编程时,一般解决同步问题的方法有两种(都是用synchronize关键字):一种是同步代码块,一种是同步方法(同步方法使用this关键字作为标记,即对象本身作为同步监视器);但还有一种非常灵活简便的同步锁可以选择,可以通过java.util.concurrent.locks.ReentrntLock类 的对象调用lock()方法来实现加锁操作,此时可以进行同步操作,同步操作结束后,调用unlock()释放同步锁。(在调用同一对象的wait()和notify()方法语句必须放在同步代码块中,并且同步代码块使用该对象的同步锁,否则在运行时会抛出ILLegalMonitorStateException)异常。
同步代码块:
synchronize(Object obj) {
//需要同步的代码块
}
同步方法:
【访问控制符】 【static|final】synchronize 返回类型 方法名(参数列表){
//需要同步的代码
【return 返回值】
}
其中,synchronize实现同步代码块的关键字,obj表示任意对象,可以是实际存在的也可以是假设的,在Java中任意一个对象都有一个同步锁,以下是注意事项:
- 对象O的同步锁在任何时刻最多只能被一个线程拥有;
- 如果对象O的同步锁被线程T拥有,那么当其他线程来访问O时,这些线程将被放到O的锁池中,并将它们转为同步阻塞状态;
- 拥有O的锁的线程T执行完后,会自动释放O的锁。若在执行过程中,线程T发生异常退出,也将自动释放O的锁;
- 如果线程T在执行同步代码块时,调用了O的wait()方法,则线程T会释放O的锁,线程T也将转为等待阻塞状态;
- 如果线程T在执行同步代码块时,调用了Thread类的sleep()方法,线程T将放弃运行权,即放弃CPU,但线程T不会放弃对象O的锁;
- 如果线程T释放了对象O的锁,并放弃运行权,则CPU将会随机分配给对象O锁池中的线程,该线程也将会拥有对象O锁;
/**
* 一个锅里有五碗饭,有两个窗口,一次只能给一个人打饭。
* @author lzq
*
*/
class Ticket implements Runnable {
private int ticket = 5;
public Ticket() {
}
public void run() {
while(ticket >= 1) {
this.sale();
}
}
/**
* 同步方法
*/
private void sale() {
if(ticket > 0) {
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
if(ticket > 0) {
System.out.println("------"+Thread.currentThread().getName()+"卖出第"
+(ticket--)+"碗饭");
}
}
}
}
/**
* 两个窗口卖票,一次卖一张,总共5张
* 使用同步代码块
* @author lzq
*
*/
public class TestDemo13 implements Runnable {
private int ticket = 5;
public TestDemo13() {
}
public void run() {
while(ticket >= 1) {
synchronized (this) {
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
if(ticket > 0) {
System.out.println(Thread.currentThread().getName()+"卖出第"
+(ticket--)+"张门票");
}
}
}
}
public static void main(String[] args) {
TestDemo13 x = new TestDemo13();
Thread t1 = new Thread(x,"第一窗口");
Thread t2 = new Thread(x,"第二窗口");
t1.start();
t2.start();
Ticket y = new Ticket();
Thread z1 = new Thread(y,"第1窗口");
Thread z2 = new Thread(y,"第2窗口");
z1.start();
z2.start();
}
}
运行结果:
------第2窗口卖出第5碗饭
------第1窗口卖出第4碗饭
第一窗口卖出第5张门票
第一窗口卖出第4张门票
------第2窗口卖出第2碗饭
------第1窗口卖出第3碗饭
------第2窗口卖出第1碗饭
第一窗口卖出第3张门票
第一窗口卖出第2张门票
第一窗口卖出第1张门票
/**
* 使用同步锁实现
* 两个窗口卖票,一次卖一张,总共10张
* @author lzq
*
*/
public class TestDemo14 implements Runnable {
private final ReentrantLock lock = new ReentrantLock();//创建锁
private int ticket = 10;
public TestDemo14() {
}
public void run() {
while(ticket > 0) {
lock.lock(); //加锁
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
if(ticket > 0) {
System.out.println(Thread.currentThread().getName()+"卖出第"
+(ticket--)+"张门票");
}
lock.unlock();//释放锁
}
}
public static void main(String[] args) {
TestDemo14 x = new TestDemo14();
Thread t1 = new Thread(x,"第一窗口");
Thread t2 = new Thread(x,"第二窗口");
t1.start();
t2.start();
}
}
运行结果:
第一窗口卖出第10张门票
第一窗口卖出第9张门票
第一窗口卖出第8张门票
第二窗口卖出第7张门票
第二窗口卖出第6张门票
第二窗口卖出第5张门票
第二窗口卖出第4张门票
第一窗口卖出第3张门票
第一窗口卖出第2张门票
第一窗口卖出第1张门票
范例:
- 有两个线程分别为sf和gf,sf用来放置食物,而gf用来取走食物;
- sf一次只能放置一批次食物,sf放置好后通知gf取走;
- gf一次只能取走一批次的食物,等到gf取走食物后通知sf放置食物;
- sf不放置食物,则gf不能取走食物,若gf没有取走食物,则sf不能放置食物;
- 开始时,先由sf放置,再由gf取走;
/**
* 放置食物类
* @author lzq
*
*/
class SetFood implements Runnable {
private Food food;
public SetFood(Food food) {
this.food = food;
}
public void run() {
for(int number = 1;number <= 5;number++) {
try {
Thread.sleep(500);
}catch(InterruptedException e) {
e.printStackTrace();
}
food.setfood(number); //放置食物
}
}
}
/**
* 取走食物类
* @author lzq
*
*/
class GetFood implements Runnable {
private Food food;
public GetFood(Food food) {
this.food = food;
}
public void run() {
for(int number = 1;number <= 5;number++) {
try {
Thread.sleep(500);
}catch(InterruptedException e) {
e.printStackTrace();
}
food.getfood(); //取走食物
}
}
}
/**
* 食物类
* @author lzq
*
*/
public class Food {
private int number = 0; //食物批次
private String food = null; //食物名称
public Food() {
}
public Food(String food,int number) {
this.food = food;
this.number = number;
}
/**
* 放置食物
* @param n
*/
public synchronized void setfood(int n) {
if(this.number != 0) {
try {
wait(); //开始等待
}catch(InterruptedException e) {
e.printStackTrace();
}
}
number = n;
System.out.println("放置"+this.food+"第"+this.number+"批次");
notify(); //通知取走食物
}
/**
* 取走食物
* @param n
* @return
*/
public synchronized String getfood() {
if(this.number == 0) {
try {
wait(); //等待放置食物
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("\t取走"+"第"+this.number+"批次"+this.food);
this.number = 0;
notify(); //通知放置食物
return food+number;
}
public static void main(String[] args) {
Food f= new Food("白菜",0);
SetFood sf = new SetFood(f);
GetFood gf = new GetFood(f);
new Thread(sf).start();
new Thread(gf).start();
}
}
运行结果:
放置白菜第1批次
取走第1批次白菜
放置白菜第2批次
取走第2批次白菜
放置白菜第3批次
取走第3批次白菜
放置白菜第4批次
取走第4批次白菜
放置白菜第5批次
取走第5批次白菜