案例–买票
在实现Runnable接口的类中,获取当前类线程的名字:Thread.currentThread().getName()
public class SellTickket implements Runnable { private int ticket = 100; @Override public void run() { while ( true ) { if ( ticket>0 ) { System.out.println( Thread.currentThread().getName()+"正在出售第"+ticket+"张票" ); ticket--; } } } } public class Test { public static void main(String[] args) { SellTickket sellTickket = new SellTickket(); Thread t1 = new Thread(sellTickket, "窗口1"); Thread t2 = new Thread(sellTickket, "窗口2"); Thread t3 = new Thread(sellTickket, "窗口3"); t1.start(); t2.start(); t3.start(); } }
买票出现的问题:
1.相同的票出现了多次
2.出现负数的票
出现问题的原因:
线程执行的随机性导致
解决多线程导致的问题
为什么出现问题:
这也是判断多线程程序是否会有数据安全问题的标准
1.是否为多线程环境
2.是否共享数据
3.是否有多条语句操作共享数据
如何解决多线程安全问题:
基本思想:让程序没有安全问题的环境
如何实现:把多条语句操作共享数据的代码锁起来,让任意时刻只有一个线程执行即可,Java提供了同步代码块的方式来解决
同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
格式:
synchronized ( 任意对象 ) { 多条语句操作共享数据的代码 }
synchronized ( 任意对象 ) :就相当于给代码加锁,任意对象可以看出一把锁
public class SellTickket implements Runnable { private int ticket = 100; private Object obj = new Object(); @Override public void run() { while (true) { synchronized (obj) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票"); ticket--; } } } } }
同步锁中的对象,必须所有线程是同一个对象,否则每个线程会各自有一把锁,无法解决多线程导致的问题
同步的好处与弊端:
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,很耗费资源,会降低程序的运行效率
同步方法
同步方法:
就是把synchronized关键字加到方法上
格式:
修饰符 synchronized 返回值类型 方法名( 方法参数 ) { } • 1
public synchronized void sellTicket() { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票"); ticket--; } }
同步方法中的锁对象是 this
同步静态方法
就是把synchronized关键字加到静态方法上
格式
修饰符 static synchronized 返回值类型 方法名( 方法参数 ) { }
同步静态方法的锁对象是 类名.class
线程安全的类
Collections中存在方法可以将线程不同步的转化为线程同步
Collections.synchronizedList(); Collections.synchronizedMap(); Collections.synchronizedSet(); ......
Lock锁
Lock中提供了获得锁和释放所的方法,使用该方法比synchronized更加直观
void lock () 获得锁 void unlock () 释放锁
Lock是接口,不能直接实例化,采用Lock的实现类ReentrantLock实例化一个对象
Lock lock = new ReentrantLock(); //需要加锁的代码块前一行加上 lock.lock(); //加锁的代码块执行结束释放锁 lock.unlock();
//为了保证线程的锁一定会释放 //会把释放锁放在finally中 try { lock.lock(); if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票"); ticket--; } } finally { lock.unlock(); }
生产者消费者