在前面的抢票中出现了多个人抢到了同一张票,还有负数。可以发现多线程出现了问题。这是由于同一进程的多个线程共享一块存储空间,在带来方便的时候,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入了锁机制synchronized当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁。判断一个多线程是否有问题的标准:
- 是否是多线程环境
- 是否存在共享数据
- 是否存在多条语句同时操作共享数据
解决多线程安全
1.同步方法
针对方法提出一套机制 , 这套机制就是 synchronized 关键字他有两种用法synchronized 方法 和synchronized 块。
同步方法
public synchronized void method(int args) {}
synchronized方法控制对 “对象” 的访问 , 每个对象对应一把锁 , 每个synchronized方法都必须获得调用该方法的对象的锁才能执行 , 否则线程会阻塞 ,方法一旦执行 , 就独占该锁 , 直到该方法返回才释放锁 , 后面被阻塞的线程才能获得这个锁 , 继续执行
同步方法的锁对象: 是this
静态同步方法的锁对象:就是当前类对应的字节码文件对象
同步方法的弊端
优点:同步的出现解决了线程安全问题
缺点:当线程过多时,因为每个线程都会去判断同步上的锁,会很耗费资源,从而大大的降低程序的运行效率。
public class Ticket implements Runnable {
private int ticketNums = 10;
private boolean flag = true;
@Override
public void run() {
while (flag){
take();
}
}
private synchronized void take() {
if(ticketNums<=0){
flag = false;
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNums-- +"张票");
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"张三").start();
new Thread(ticket,"李四").start();
new Thread(ticket,"王五").start();
}
}
运行结果为:
张三抢到了第10张票
张三抢到了第9张票
张三抢到了第8张票
张三抢到了第7张票
张三抢到了第6张票
张三抢到了第5张票
王五抢到了第4张票
李四抢到了第3张票
王五抢到了第2张票
张三抢到了第1张票
2.同步块
同步代码块的格式
synchronized(Obj){
需要同步的代码;
}
Obj称之为同步监视器,
1.Obj可以时任何对象,但是推荐使用共享资源作为同步监视器
2.同步方法中无需指定监视器,因为同步方法的同步监视器就是this,这个对象本身。
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定进行访问
public class Ticket2 implements Runnable {
private int ticketNums = 10;
private boolean flag = true;
private static final Object obj = new Object() ;
@Override
public void run() {
while (flag){
take();
}
}
private void take() {
synchronized (obj){
if(ticketNums<=0){
flag = false;
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNums-- +"张票");
}
}
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(ticket,"张三").start();
new Thread(ticket,"李四").start();
new Thread(ticket,"王五").start();
}
}
2.Lock锁
Lock锁:在上面的同步代码块和同步方法锁对象,但是没有看见哪里上了锁,哪里释放了锁,为了更清晰的表达如何加锁以及释放锁,JDK1.5之后提供了一个新的锁对象Lock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket1 implements Runnable {
private int ticketNums = 10;
private boolean flag = true;
//创建所对象
private static final Lock lock = new ReentrantLock() ;
@Override
public void run() {
while (flag){
take();
}
}
private void take() {
//添加锁
lock.lock();
if(ticketNums<=0){
flag = false;
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNums-- +"张票");
//释放锁
lock.unlock();
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"张三").start();
new Thread(ticket,"李四").start();
new Thread(ticket,"王五").start();
}
}