目录
卖票——三个窗口卖100张票
问题示范
public class Demo {
public static void main(String[] args) {
//这里Runnable接口实现类只能创建一个对象
RunnableImpl runnable = new RunnableImpl();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
public class RunnableImpl implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
//票卖完了
if (ticket == 0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName()+"在卖票,还剩下"+ticket+"张");
}
}
}
}
这里输出结果如下:有重复票和复数;原因是线程在run方法处抢夺执行权,共同操作同一对象造成的线程安全问题
解决办法
同步代码块![](https://i-blog.csdnimg.cn/blog_migrate/77864a9b82e537b8467e312188903ff3.png)
同步的好处和弊端,括号中也可以使用this代表当前的锁对象
-
好处:解决了多线程的数据安全问题
-
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
public class Ticket implements Runnable {
//票的数量
private int ticket = 100;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){//多个线程必须使用同一把锁,保证多个线程只能操作同一个共享数据
//如果是想创建多个线程操作同一数据,则需要共享数据为静态,锁对象静态唯一
if(ticket <= 0){
//卖完了
break;
}else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
}
}
}
}
}
public class Demo {
public static void main(String[] args) {
/*Ticket ticket1 = new Ticket();
Ticket ticket2 = new Ticket();
Ticket ticket3 = new Ticket();
Thread t1 = new Thread(ticket1);
Thread t2 = new Thread(ticket2);
Thread t3 = new Thread(ticket3);*/
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
同步方法
静态同步方法和同步代码块共同使用一把锁
public class RunnableImpl implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while (true){
if ("窗口一".equals(Thread.currentThread().getName())){
//同步方法
boolean result = synchronizedMthod ();
if (result){
break;
}
}
if ("窗口二".equals(Thread.currentThread().getName())){
//同步代码块
synchronized (RunnableImpl.class){//这里要保持锁的一致
if (ticket==0){
break;
}else {
try {
Thread.sleep(12);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName()+"在卖票,还剩下"+ticket+"张");
}
}
}
}
}
//变成同步方法
private synchronized static boolean synchronizedMthod() {
if (ticket==0){
return true;
}else {
try {
Thread.sleep(12);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println(Thread.currentThread().getName()+"在卖票,还剩下"+ticket+"张");
return false;
}
}
}
public class Demo {
public static void main(String[] args) {
RunnableImpl mr = new RunnableImpl();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
- ReentrantLock构造方法
| 方法名 | 说明 |
| ReentrantLock() | 创建一个ReentrantLock的实例 |
- 加锁解锁方法
| 方法名 | 说明 |
| void lock() | 获得锁 |
| void unlock() | 释放锁 |