线程安全
- 互斥:许多线程在同一个共享数据上操作而互不干扰,这就要求同一时刻只能有一个线程访问该共享数据。
- 监视区:同一时刻只能被同一个线程执行的程序段。
- 关键字:synchronized:用于指定监视去
语法规定
实现原理:首先判断对象的锁是否存在。
如果在,就获得锁,然后开始执行紧随其后的代码段。
如果对象的锁不存在(可能被其他线程拿走了),就开始等待,直到获得锁。
当被synchronized限定的 代码段执行完毕,就会释放锁。
用多线程实现简单的存票与售票
//主方法
public class ProducerAndConsumer {
public static void main(String[] args) {
System.out.println("主线程开始执行");
Ticket ticket = new Ticket(10);
new Consumer(ticket).start();
new Producer(ticket).start();
System.out.println("主线程执行完毕");
}
}
//票类
public class Ticket {
//票号
private int _number = 0;
//总票数
private int _size;
//当前是否有票可卖
boolean isAvailable = false;
//构造方法
public Ticket(int size) {
// TODO Auto-generated constructor stub
_size = size;
}
//获取票号
public int getNumber()
{
return _number;
}
//获取票的总数
public int getSize()
{
return _size;
}
//生产票
void produceTicket()
{
_number++;
}
}
//存票线程
public class Producer extends Thread {
//票
private Ticket _t = null;
//构造方法
public Producer(Ticket t) {
// TODO Auto-generated constructor stub
_t = t;
}
//线程体
@Override
public void run() {
// TODO Auto-generated method stub
while(_t.getNumber() < _t.getSize())
{
_t.produceTicket();
System.out.println("Producer puts tickets " + _t.getNumber());
_t.isAvailable = true;
}
}
}
//售票线程
public class Consumer extends Thread {
Ticket _t = null;
int i = 0;
public Consumer(Ticket t) {
// TODO Auto-generated constructor stub
_t = t;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(_t.getSize() > i)
{
if(_t.isAvailable && i < _t.getNumber())
{
i++;
System.out.println("Consumer buys ticket " + i);
if(i == _t.getNumber())
{
try
{
Thread.sleep(1);
}
catch(Exception e)
{
System.out.println(e.getClass().getName());
}
_t.isAvailable = false;
}
}
}
}
}
上述程序会出现错误:这是因为当售票线程的已经售出的票等于当前最大票号的时候,售票线程就是休眠1ms。此时java的线程调度器开始执行存票线程,并一次性生产完了10张票,且将**_t.isAvailable
设置为true
。1ms以后,售票线程休眠完毕,将_t.isAvailable
设置为false
。这就让售票线程陷入了死循环,因为存票线程已经执行完毕,_t.isAvailable
** 的状态只能永远是false
。下面用关键字synchronized
简单的改造一下该程序。
//主方法
public class ProducerAndConsumer {
public static void main(String[] args) {
System.out.println("主线程开始执行");
Ticket ticket = new Ticket(10);
new Consumer(ticket).start();
new Producer(ticket).start();
System.out.println("主线程执行完毕");
}
}
//售票线程
public class Producer extends Thread {
//票
private Ticket _t = null;
//构造方法
public Producer(Ticket t) {
// TODO Auto-generated constructor stub
_t = t;
}
//线程体
@Override
public void run() {
// TODO Auto-generated method stub
while(_t.getNumber() < _t.getSize())
{
synchronized (_t) {
_t.produceTicket();
System.out.println("Producer puts tickets " + _t.getNumber());
_t.isAvailable = true;
}
}
}
}
//存票线程
public class Producer extends Thread {
//票
private Ticket _t = null;
//构造方法
public Producer(Ticket t) {
// TODO Auto-generated constructor stub
_t = t;
}
//线程体
@Override
public void run() {
// TODO Auto-generated method stub
while(_t.getNumber() < _t.getSize())
{
synchronized (_t) {
_t.produceTicket();
System.out.println("Producer puts tickets " + _t.getNumber());
_t.isAvailable = true;
}
}
}
}
//票类
public class Ticket {
//票号
private int _number = 0;
//总票数
private int _size;
//当前是否有票可卖
boolean isAvailable = false;
//构造方法
public Ticket(int size) {
// TODO Auto-generated constructor stub
_size = size;
}
//获取票号
public int getNumber()
{
return _number;
}
//获取票的总数
public int getSize()
{
return _size;
}
//生产票
void produceTicket()
{
_number++;
}
}
其改动主要就是将对象t设置了一个锁,让存票线程和售票线程不能同时访问之。
synchronized的本质就是让被他修饰的代码段变成一个“原子语句”,也就是说这些代码要么不被执行,要么就会被执行完才会允许其他线程操作该对象。
synchronized
可以定义某些方法在同步控制下执行,只要在方法前面加上synchronized
即可。
采用同步方法的方式修改上述代码实现同样的功能。其改动主要就是将售票方法和买票方法的实现写在了Ticket类中,并将其设置为同步方法。其实这样实现是更加合理的方式。
//主方法
public class ProducerAndContums {
public static void main(String[] args) {
System.out.println("主方法开始执行");
Ticket ticket = new Ticket(10);
Producer producer = new Producer(ticket);
Contums contums = new Contums(ticket);
producer.start();
contums.start();
System.out.println("主方法执行完毕");
}
}
//票类
public class Ticket {
//存票序号
private int _number = 0;
//票总数
private int _size;
//当前是否有票可售
private boolean _isAvailiable = false;
//售票号
int _i;
//构造方法
public Ticket(int size) {
// TODO Auto-generated constructor stub
_size = size;
}
//获取存票号
public int getNumber()
{
return _number;
}
//获取售票号
public int getI() {
return _i;
}
//获取最多生产多少票
public int getSize()
{
return _size;
}
//获取当前是否有票
public boolean getAvailable()
{
return _isAvailiable;
}
//存票方法
public synchronized void put() {
if(_number < _size)
{
_number++;
System.out.println("Producer puts ticket " + _number);
_isAvailiable = true;
}
}
//售票方法
public synchronized void sell() {
if(_isAvailiable && _i < _number)
{
_i++;
System.out.println("Customer buys ticket " + _i);
// System.out.println("number = " + _number);
}
if(_i == _number)
{
_isAvailiable = false;
}
}
}
//存票线程
public class Producer extends Thread {
private Ticket _t;
//构造方法
public Producer(Ticket t) {
// TODO Auto-generated constructor stub
_t = t;
}
//线程体
@Override
public void run() {
// TODO Auto-generated method stub
while(_t.getNumber() < _t.getSize())
{
_t.put();
}
}
}
//售票线程
public class Contums extends Thread{
private Ticket _t;
public Contums(Ticket t) {
// TODO Auto-generated constructor stub
_t = t;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(_t.getI() < _t.getSize())
{
_t.sell();
}
}
}
synchronized用法总结
- 该关键字可以实现线程同步,可以同步代码段,也可以同步方法。
- 类可以拥有同步和非同步方法,非同步方法的调用不收限制。
- 如果两个线程使用相同的实例来调用同步方法,那么同一时刻只能有一个线程执行方法,另一个需要等待锁。
- 可以给对象上锁,但是每个对象只有一个锁。
- 线程休眠时,它所持有的锁不会释放。
- 线程可以获得多个锁:比如在一个对象的同步方法中调用另一个对象的同步方法。
- 同步的缺点:损害并发性,应该在保证线程安全的情况下,尽可能缩小同步方法的范围,进而提高并发性。
- 在使用同步代码块的时候,应该说明要获取哪个对象的锁。