解决线程不安全问题,java中的线程同步机制
java中提供了三种解决线程安全的方法
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
有三种方式完成同步操作:
1. 同步代码块。
2. 同步方法。
3. 锁机制。
1.同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){需要同步操作的代码,即可能会出现线程安全的代码}
同步锁(也叫锁对象):
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
1. 锁对象 可以是任意类型,如Object。
2. 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
public class SaleTickets implements Runnable { private int tickets = 100; //新建一个锁对象 Object obj = new Object(); @Override public void run() { // 让其不断的卖票 while(true) { // 使用同步代码块 synchronized (obj){ // tickets必须大于0 if (tickets > 0) { // 取到票后,就休息10毫秒 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 打印出卖票信息 System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "票"); // 票数实现-1 tickets--; } } } } }
2. 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
public synchronized void method(){可能会产生线程安全问题的代码}
注意:synchronized位于修饰符和返回值之间
此时有一个问题,对于同步方法,我们怎么没看到锁对象(同步锁)?
对于非static方法,同步锁就是this。
this即为线程的实现类对象
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
因为this是创建对象后,才存在的;而static方法是优先于对象存在的;故对于static方法,锁对象就是所在类的字节码文件
public class SaleTickets02 implements Runnable { private int tickets = 100; @Override public void run() { // 让其不断的卖票 while(true) { lockTickets(); } } // 把有可能产生线程安全的代码放到方法里面 public synchronized void lockTickets(){ // tickets必须大于0 if (tickets > 0) { // 取到票后,就休息10毫秒 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 打印出卖票信息 System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "票"); // 票数实现-1 tickets--; } } }
通过代码证明,非静态方法,锁对象就是this
// 把有可能产生线程安全的代码放到方法里面 public /*synchronized*/ void lockTickets(){ synchronized(this){ // tickets必须大于0 if (tickets > 0) { // 取到票后,就休息10毫秒 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 打印出卖票信息 System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "票"); // 票数实现-1 tickets--; } }
}
而对于静态方法,锁对象就是该方法所在类的字节码文件
// 把有可能产生线程安全的代码放到方法里面 public static /*synchronized*/ void lockTickets(){ // 此时的锁对象就是方法所在类的字节码文件 synchronized(SaleTickets02.class){ // tickets必须大于0 if (tickets > 0) { // 取到票后,就休息10毫秒 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 打印出卖票信息 System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "票"); // 票数实现-1 tickets--; } } }
证明this就是实现类对象
在实现类的run()方法中,我们插入如下语句:输出调用run方法的对象
System.out.println("this: "+this);

然后测试类中,我们直接输出实现类对象
System.out.println("实现类对象:"+st);

输出结果如下:

发现this就是实现类对象(由于新建三个线程故会输出三次this)
3.Lock锁 :java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock是一个接口,故要使用Lock时,必须new一个其实现类,如ReentrantLock
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
使用步骤: 1.新建一个Lock对象,Lock lk = new ReentrantLock();
2.在可能出现线程安全的代码前调用lock()方法;
3.在可能出现线程安全的代码后调用unlock()方法。
推荐使用try...finally,因为会无条件执行finally里面的释放锁的功能
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }

浙公网安备 33010602011771号