“非线程安全”——多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是取到的数据其实是被更改过的。
1、方法内的变量为线程安全的
方法内部的私有变量,则不存在“非线程安全”的问题,所得结果也就是“线程安全”的。
2、实例变量非线程安全
如果多个线程共同访问一个对象中的实例变量,则有可能出现“非线程安全”问题。
对于上一篇《多线程的数据共享》作进一步改进,解决同步问题
利用同步块可以保证一个线程在一个代码块中访问同步资源未结束时,阻塞其他线程访问这个同步资源,保证了操作的一致性。
/**
*
* 内部类:只想在当前类使用,其他地方用不到的情况下 使用内部类,可以访问外部类的成员变量
*/
public class TicketSys {
private int ticketNum = 10;// 实例变量
private class Window extends Thread {
public Window(String name) {
super(name);
}
@Override
public void run() {
// TODO Auto-generated method stub
while (ticketNum > 0) {
synchronized (TicketSys.class) {
// 1.收钱
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 2.售票
System.out.println(Thread.currentThread().getName() + "sell ticket" + ticketNum);
// 3.票数减少
ticketNum--;
}
}
}
}
/**
* 创建并启动线程
*
* @param name
*/
public void openWindow(String name) {
new Window(name).start();
}
}
这是针对继承Thread实现数据共享的改进,代码只多了一个 synchronized (TicketSys.class) {}同步块,其他地方并无改动,而运行结果则正常了,不会出现同一数字出现多次,也不会出现负数的情况。
调用关键字synchronized声明的方法一定是排队运行的(同步的)。只有共享资源的读写访问才需要同步化,如果不是共享资源,根本没有同步的必要。
通常还是推荐使用实现Runnable实现数据共享,下面就针对通过实现runnable实现数据共享的代码做改进,解决同步问题:
/**
* @author cuijiao
*
*/
public class TicketSys implements Runnable {
private int ticketNum = 10;// 实例变量
@Override
public void run() {
// TODO Auto-generated method stub
while (ticketNum > 0) {
sellTicket();
}
}
public synchronized void sellTicket() {// 同步方法:锁-this,所以当前类在系统中只能new一次
if (ticketNum > 0) {
// 1.收钱
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 2.售票
System.out.println(Thread.currentThread().getName() + "sell ticket" + ticketNum);
// 3.票数减少
ticketNum--;
}
}
}
只是对售票操作抽成了一个方法并加了synchronized 同步锁,其它代码并未改动。
同步方法所在对象在系统中不可new多次,因为同步方法的锁即this-当前类
jdk5之后java.util.concurrent.locks提供锁,比synchronized更加灵活,功能更多
下面使用排他锁ReentrantLock解决多线程的同步问题
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketSys implements Runnable {
private int ticketNum = 10;// 实例变量
private Lock lock = new ReentrantLock();// 排他锁
@Override
public void run() {
// TODO Auto-generated method stub
while (ticketNum > 0) {
try {
lock.lock();
if (ticketNum > 0) {
// 1.收钱
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 2.售票
System.out.println(Thread.currentThread().getName() + "sell ticket" + ticketNum);
// 3.票数减少
ticketNum--;
}
} finally {
lock.unlock();
}
}
}
}
如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,需以下面这种形式去使用的:
Lock lock = ...;
lock.lock();
try
{
//处理任务
}
catch
(Exception ex){
}
finally
{
lock.unlock();
//释放锁
}