【TX】前言:在 http://blog.csdn.net/tangmingxin0529/article/details/79514849 这篇文章中我们了解了JAVA多线程的一些概念及常用方法。我们已经知道,一个多线程的程序如果是通过Runnable接口实现的,则意味着类中的属性将被多个线程共享,那么这样一来就会出现资源的同步问题。先看2个例子:
例1:启用3个售票线程
public class RunnableTest1 implements Runnable{
private String name;
private int ticket=3;
public RunnableTest1(String name) {
super();
this.name = name;
}
@Override
public void run(){
for (int i = 0; i < 10; i++) {
if(ticket>0){
System.out.println(name+"卖票:ticket="+ticket--);
}
}
}
public static void main(String[] args) {
RunnableTest1 rnb1=new RunnableTest1("售票员");
Thread thread1 = new Thread(rnb1);
Thread thread2 = new Thread(rnb1);
Thread thread3 = new Thread(rnb1);
thread1.start();
thread2.start();
thread3.start();
}
}
例1运行结果
售票员卖票:ticket=3
售票员卖票:ticket=2
售票员卖票:ticket=2
售票员卖票:ticket=1
例2:依然启动三个售票线程,但加入了线程休眠
public class RunnableTest1 implements Runnable{
private String name;
private int ticket=3;
public RunnableTest1(String name) {
super();
this.name = name;
}
@Override
public void run(){
for (int i = 0; i < 10; i++) {
if(ticket>0){
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"卖票:ticket="+ticket--);
}
}
}
public static void main(String[] args) {
RunnableTest1 rnb1=new RunnableTest1("售票员");
Thread thread1 = new Thread(rnb1);
Thread thread2 = new Thread(rnb1);
Thread thread3 = new Thread(rnb1);
thread1.start();
thread2.start();
thread3.start();
}
}
例2运行结果:
售票员卖票:ticket=3
售票员卖票:ticket=2
售票员卖票:ticket=3
售票员卖票:ticket=1
售票员卖票:ticket=0
售票员卖票:ticket=-1
那么,为什么会出现这种情况:
先分析售票操作步骤;
(1)判断票数是否大于0,是则表示有票可买,否则表示无票
(2)若有票可买,则将票卖出。
但是当加入sleep方法后,那么就存在一个线程还没来的及对票数-1,另一个线程就已经将票卖出去了,这样就会出现票数为负数的情况。
要解决这样的问题,就必须使用同步。同步是指多个操作在同一时间段内只能有一个线程进行,其他线程要等待此线程完成之后才能继续执行。
一:使用同步解决问题
解决资源共享的同步操作,可以使用同步代码块及同步方法两种方式完成。这两种方式都依赖于synchronized这个关键字。
Synchronized是Java并发编程中最常用的用于保证线程安全的方式。
1:同步代码块。定义如下
synchronized(同步对象){
需要同步的代码;
}
由定义可见,在使用同步代码块时必须制定一个需要同步的对象,但一般都将当前对象(this)设置为同步对象。
public class RunnableTest1 implements Runnable{
private String name;
private int ticket=3;
public RunnableTest1(String name) {
super();
this.name = name;
}
@Override
public void run(){
for (int i = 0; i < 10; i++) {
synchronized(this){
if(ticket>0){
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"卖票:当前ticket="+ticket--);
}else{
System.out.println("售罄:"+ticket);
return;
}
}
}
}
public static void main(String[] args) {
RunnableTest1 rnb1=new RunnableTest1("售票员");
Thread thread1 = new Thread(rnb1);
Thread thread2 = new Thread(rnb1);
Thread thread3 = new Thread(rnb1);
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
售票员卖票:当前ticket=3
售票员卖票:当前ticket=2
售票员卖票:当前ticket=1
售罄:0
售罄:0
售罄:0
2:同步方法。使用synchronized关键字声明的方法称为同步方法。
public class RunnableTest1 implements Runnable{
private String name;
private int ticket=3;
public RunnableTest1(String name) {
super();
this.name = name;
}
@Override
public void run(){
for (int i = 0; i < 10; i++) {
this.sale();
}
}
public synchronized void sale(){
if(ticket>0){
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"卖票:当前ticket="+ticket--);
}
}
public static void main(String[] args) {
RunnableTest1 rnb1=new RunnableTest1("售票员");
Thread thread1 = new Thread(rnb1);
Thread thread2 = new Thread(rnb1);
Thread thread3 = new Thread(rnb1);
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
售票员卖票:当前ticket=3
售票员卖票:当前ticket=2
售票员卖票:当前ticket=1
二:死锁的产生
同步可以保证资源功效操作的正确性,但是过多同步会产生死锁问题。比如给车加油,司机说“先给我加油,我再付钱”,加油员说“先给钱,后加油”。这样两人都在等对方先答复,最终干等下去。那么,所谓死锁就是两个线程都在等对方先完成,都等对方释放了资源后再执行自己,从而造成了程序的停滞。一般程序的死锁都是在程序运行时出现的。
package com.tmx.threads;
class Driver {
public void say() {
System.out.println("1:司机说:先给我加油,后给你钱");
}
public void give() {
System.out.println("3:司机加到了油");
}
}
class SalesMan {
public void say() {
System.out.println("2:加油员说:先给我钱,后给你加油");
}
public void get() {
System.out.println("4:加油员拿到了油钱");
}
}
public class MyThreads implements Runnable {
private static Driver driver = new Driver();
private static SalesMan salesMan = new SalesMan();
private boolean flag = false;
@Override
public void run() {
if (flag) {
synchronized (driver) {
driver.say();// 1
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
System.out.println(e);
;
}
synchronized (salesMan) {
salesMan.get();// 4
}
}
} else {
synchronized (salesMan) {
salesMan.say();// 2
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
System.out.println(e);
;
}
synchronized (driver) {
driver.give();// 3
}
}
}
}
public static void main(String[] args) {
MyThreads myThreads1 = new MyThreads();
MyThreads myThreads2 = new MyThreads();
myThreads1.flag = true;
myThreads2.flag = false;
Thread thread1 = new Thread(myThreads1);
Thread thread2 = new Thread(myThreads2);
thread1.start();
thread2.start();
}
}
程序执行结果:
可见两个线程都在等对方先执行完成,这样就造成了死锁的现象。
在这个案例中一定要注意Driver和SalesMan的实例对象使用了Static关键字,如果不使用Static,则无法达到数据共享目的。
private Driver driver = new Driver();
private SalesMan salesMan = new SalesMan();
private boolean flag = false;
这样声明的对象的结果大不相同:
1:司机说:先给我加油,后给你钱
2:加油员说:先给我钱,后给你加油
4:加油员拿到了油钱
3:司机加到了油
死锁的产生条件,这四个条件同时满足时才会产生死锁:
1.互斥条件:一个资源每次只能被一个进程使用。独木桥每次只能通过一个人。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。乙不退出桥面,甲也不退出桥面。
3.不剥夺条件: 进程已获得的资源,在未使用完之前,不能强行剥夺。甲不能强制乙退出桥面,乙也不能强制甲退出桥面。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。如果乙不退出桥面,甲不能通过,甲不退出桥面,乙不能通过。