java基础学习之线程同步机制
一、多线程访问共享数据可能会产生线程安全问题
多线程访问共享数据可能会产生线程安全问题
以卖票为例:
public class RunnableImpl1 implements Runnable {
private int count = 100;//初始100张票
@Override
public void run() {
while(true){
if(count>0){
System.out.println(Thread.currentThread().getName()+"在卖第"+count+"张票");
count--;
}
}
}
}
public class Test {
public static void main(String[] args) {
RunnableImpl1 runnableImpl1 = new RunnableImpl1();
Thread thread = new Thread(runnableImpl1);
Thread thread1 = new Thread(runnableImpl1);
Thread thread2 = new Thread(runnableImpl1);
thread.start();
thread1.start();
thread2.start();
}
}
程序运行过程中会出现卖同一张票的情况。
二、解决线程安全问题的一种方案:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
- 同步代码块中的锁对象,可以使用任意的对象。
- 但是必须保证多个线程使用的锁对象是同一个。(特别注意)
- 锁对象作用:
把同步代码块锁住,只让一个线程运行同步代码块。
public class SocketTest implements Runnable{
private int count = 100;
String a = "aaa";//a要定义在run()方法的外面,如果定义在里面线程调用start()方法,执行run()方法的时候,会创建新的对象a,就不能保证是同一个对象
@Override
public void run() {
synchronized (a){//使用同步代码块
while(true){
if(count>0){
System.out.println(Thread.currentThread().getName()+"在卖第"+count+"张票");
count--;
}
}
}
}
}
public class Test {
public static void main(String[] args) {
SocketTest runnableImpl1 = new SocketTest();
Thread thread = new Thread(runnableImpl1);
Thread thread1 = new Thread(runnableImpl1);
Thread thread2 = new Thread(runnableImpl1);
thread.start();
thread1.start();
thread2.start();
}
}
同步技术的原理
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票
t0线程抢到了cpu的执行权,执行run方法,遇到synchronize代码块
这时t0线程会检查synchronize代码块是否有锁对象
发现有,就会获取到锁对象,进入到同步中执行
t1线程抢到了cpu的执行权,执行run方法,遇到synchronize代码块。
这时t1会检查synchronize代码块是否有锁对象,发现没有,t1会进入到阻塞状态,会一直等待t0线程归还锁对象。
一直到t0线程执行完同步中的代码,会把锁对象归还给同步代码块,t1才能获取到锁对象,进入到同步代码块中执行。
注意:
同步保证了只能有一个线程在同步中执行共享数据
程序频繁地判断锁,获取锁,释放锁,程序的效率会降低。
特别注意:
把可能会操作到共享资源A的不同线程中的代码用锁对象1锁住。
把可能会操作到共享资源B的不同线程中的代码用锁对象2锁住。(意会一下)
同步方法
使用synchronized修饰的方法,就叫做同步方法,保证线程A执行该方法的时候,其他线程只能在方法外等着。
把可能产生线程安全问题的代码放到同步方法里。
格式:
public synchronized 返回值类型 方法名(){
}
例子:
public class SocketTest implements Runnable{
private int count = 100;
String a = "aaa";
@Override
public void run() {
synchronized (a){//使用同步代码块
while(true){
method();
}
}
}
public synchronized void method(){
if(count>0){
System.out.println(Thread.currentThread().getName()+"在卖第"+count+"张票");
count--;
}
}
}
同步锁(锁对象)是谁?
对于非static方法,同步锁就是this,调用方法的当前对象
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
Lock锁
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。
解决线程安全问题的第三种方法:使用Lock锁
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
void lock()获取锁
void unlock()释放锁
java.util.concurrent.locks.ReentrantLock impelments Lock
ReentrantLock是Lock接口的实现类。
使用Lock锁时一般应使用这样的结构:
Lock l = …; (new ReentrantLock();或者其它的Lock接口的实现类对象)
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SocketTest implements Runnable {
private static int count = 100;
String a = "aaa";
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
l.lock();
try{
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "在卖第" + count + "张票");
count--;
}
}catch (Exception e){
e.printStackTrace();
}finally {
l.unlock();//无论异常是否发生,都会释放锁
}
}
}
}