在使用java多线程的时候如果着多个线程访问的是同一个共享数据;
例如售票, 多个售票点线程各自售票, 但都是从一个地方取票, 这就会造成操作系统让多个线程能够"同时"访问这个共享数据, 所谓同时并不是真正意义上的同时,而是很短间隔;
只是这个线程不能一次性执行完它的线程体, 比如说它现在只拿到了共享变量的值, 但是还没有做修改,
但是这时很可能会由于cpu分配给自己的时间片用完了而进入阻塞状态, 这时候另一个线程开始, 并且成功的拿到了并且修改了这个共享变量,的值;
很显然他们俩拿到的是同一个值,这就是问题所在.
于是线程同步就提出来了;
而线程同步又是基于对象锁的;
一般用对象锁更灵活;
未加线程代码:
package MultiThread;
public class TicketSellTest{
public static void main(String[] args) {
System.out.println("main线程开始");
TicketOffice tick = new TicketOffice();
Thread s1 = new Thread(tick);
Thread s2 = new Thread(tick);
Thread s3 = new Thread(tick);
s1.setName("售票点1 ");
s2.setName("售票点2 ");
s3.setName("售票点3 ");
s1.start();
s2.start();
s3.start();
System.out.println("main线程结束");
}
}
class TicketOffice implements Runnable{
// 售出的票计数器
private int tickets = 0;
// 不管用这个类创立多少个线程, 每个线程执行start()的时候只执行run()方法
public void run() {
boolean flag = true;
while(flag) {
flag = sell();
}
}
// 售票方法
private boolean sell() {
boolean flag = true;
if (tickets < 10) {
tickets = tickets + 1;
// 获取正在执行这里的线程对象的引用的名字, 即是获取正在执行这里的线程名字
System.out.println(Thread.currentThread().getName() + "卖出第 "+tickets +" 张票");
}else {
System.out.println("无票可卖!!");
flag = false;
}
// 为了增大出错几率, 让线程停留一会儿
try {
Thread.sleep(300);
}catch(InterruptedException e) {
System.out.println("售卖过程中出现InterruptedException");
e.printStackTrace();
}
// 返回True表示有票可卖
return flag;
}
}
// 执行结果
// 可以看到这里就是由于不加限制访问共享变量造成的后果, 就是一个线程没有完成一个完整的操作, 就被另一个线程介入了
main线程开始
售票点1 卖出第 2 张票
main线程结束
售票点2 卖出第 2 张票
售票点3 卖出第 3 张票
售票点1 卖出第 4 张票
售票点2 卖出第 6 张票
售票点3 卖出第 6 张票
售票点1 卖出第 7 张票
售票点2 卖出第 8 张票
售票点3 卖出第 9 张票
售票点1 卖出第 10 张票
无票可卖!!
无票可卖!!
无票可卖!!
然后解决办法是进行线程同步,此处有两个方法:
第一个
//将sell()方法变成一个同步方法,即在方法定义的时候加入synchronized修饰符
//如下
private synchronized boolean sell(){}
//这会使得此方法为同步方法,作用是每次线程执行此方法不会被打断,一定要执行完
还有一个方法是将操作共享变量的那一块代码变成同步代码块,即用synchronized(this){}将操作共享变量的那段代码包围起来;
其中括号里的参数表示获取制定对象的对象锁;
如下:
// 售票方法
private boolean sell() {
boolean flag = true;
// 加入同步代码块,这个this表示调用此方法的对象的引用;获取此对象的对象锁
synchronized(this) {
if (tickets < 10) {
tickets = tickets + 1;
// 获取正在执行这里的线程对象的引用的名字, 即是获取正在执行这里的线程名字
System.out.println(Thread.currentThread().getName() + "卖出第 "+tickets +" 张票");
}else {
System.out.println("无票可卖!!");
flag = false;
}
}
// 为了增大出错几率, 让线程停留一会儿
try {
Thread.sleep(300);
}catch(InterruptedException e) {
System.out.println("售卖过程中出现InterruptedException");
e.printStackTrace();
}
// 返回True表示有票可卖
return flag;
}
//这样的运行结果就不会有冲突,如下
main线程开始
售票点1 卖出第 1 张票
售票点2 卖出第 2 张票
main线程结束
售票点3 卖出第 3 张票
售票点1 卖出第 4 张票
售票点2 卖出第 5 张票
售票点3 卖出第 6 张票
售票点1 卖出第 7 张票
售票点2 卖出第 8 张票
售票点3 卖出第 9 张票
售票点1 卖出第 10 张票
无票可卖!!
无票可卖!!
无票可卖!!
对象锁
同步机制的实现就是利用了对象锁;
JVM对每一个对象都有一个对象锁; 这个对象锁代表任何时候只允许一个线程能够有访问权限;
即是如果线程获取了这个对象的对象锁, 那么在它释放之前这段时间里面其他线程都不能访问;
其作用和synchronized同步机制是一样的,但是这个对象锁用起来更为灵活;
java提供了一种显示加锁机制, 使用java.util.concurrent.locks.Lock接口提供的lock()方法来获取锁;
用unlock()方法解锁,通常使用ReentrantLock这个类来实现, 上面更改之处的示例如下:
// 对象锁的实现所依赖的包
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 售票方法
// 创建Lock锁实例
private Lock lock = new ReentrantLock();
private boolean sell() {
boolean flag = true;
// 获取对象锁
lock.lock();
if (tickets < 10) {
tickets = tickets + 1;
// 获取正在执行这里的线程对象的引用的名字, 即是获取正在执行这里的线程名字
System.out.println(Thread.currentThread().getName() + "卖出第 "+tickets +" 张票");
}else {
System.out.println("无票可卖!!");
flag = false;
}
// 释放锁
lock.unlock();
|
|