问题提出
今假设今天晚上北京到上海的高铁只剩下1张二等座,购票流程为查询余票,付钱,出票三个步骤。此时张三和李四分别在不同的窗口购票,若此时没有同步机制,则可能出现张三在出票前,李四查询任然有一张余票,可付款,出票,则此时会出现一张票同时卖给两个人。这种情况当然是不允许的,不然春运回家时会发现你的座位上坐着一个抠脚大汉。。。。
解决方法
解决方法很简单,只要保证张三访问火车票在退出(出票)前,其他人(李四)不能够访问操作这张票就可以。
线程同步
由上述例子可以看出,张三李四相当于两个线程,同时访问“票”这个资源,必须保证同一时刻只能有一个人(线程)去访问操作票(共享数据、资源)。
故可引出线程同步的概念:多个线程相互协调的访问同一资源。
代码示例:
package thread;
/**
* 线程同步
*/
public class T04_Sync implements Runnable{
Timer timer=new Timer();
public static void main(String[] args) {
T04_Sync sync=new T04_Sync();
Thread t1=new Thread(sync);
Thread t2=new Thread(sync);
t1.setName("T1");
t2.setName("T2");
t1.start();
t2.start();
}
@Override
public void run() {
timer.add(Thread.currentThread().getName());
}
class Timer {
private int num=0;
public void add(String name){
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"你是第"+num+"个使用timer的线程");
}
}
}
输出结果:
此时输出结果与我们预期不符,代码加入同步(对方法加锁)
public synchronized void add(String name){
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"你是第"+num+"个使用timer的线程");
}
输出结果:
此时输出结果符合我们的预期
实现线程同步方式
synchronized关键字
synchronized是基本的线程同步方式。当存在共享数据,且同时有多个线程同时操作共享数据时,synchronized关键字能保证在同一时刻,只有一个线程能访问或执行某一方法或代码块。
原理
先看代码
public class T{
private int count =10;
private Object o=new Object();
public void m(){
synchronized(o){
count--;
System.out.println(count)
}
}
}
任何线程要执行count--时,都必须要获取到o这把锁,此时线程进入临界区,其他线程不能往下执行,必须等待线程执行完毕,把o锁释放,在申请绑定,获得锁后才能继续往下执行。
那么如何加锁?
class Timer {
private int num=0;
//此时锁是当前timer对象
public synchronized void add(String name){
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"你是第"+num+"个使用timer的线程");
}
//静态方法加锁,此时锁时Timer.class对象
public synchronized static void coutName(){
System.out.println(Thread.currentThread().getName());
}
//代码块加锁,此时锁是lock对象
Object lock=new Object();
public void coutName1(){
synchronized (lock){
System.out.println(Thread.currentThread().getName());
}
}
}
加锁的三种方式
- 普通方法加锁:此时锁是调用方new的对象。
- 静态方法加锁:此时锁是xxx.Class对象。
- 代码块加锁:此时锁是lock对象。
volatile关键字
volatile:使一个变量再多个线程中可见。
使用场景:线程AB都是用a变量,会在各自的线程中保留一份copy,当A线程改线a变量的值时,B线程是无法知道的。
使用vloatile关键字,会让所有线程都读到变量的修改值
package thread;
import java.util.concurrent.TimeUnit;
public class T05_Volatile {
boolean running=true;
void m(){
System.out.println("m start");
while (running){
}
System.out.println("m end");
}
public static void main(String[] args) {
T05_Volatile t=new T05_Volatile();
new Thread(t::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running=false;
}
}
输出结果
加上volatile后
volatile boolean running=true;
输出结果为
使用volatile后,会强制所有线程去堆中读取running的值,但是volatile只能保证可见性,不能保证原子性;synchronized 既能保证可见性,也能保证原子性。
ReentrantLock
reetrantLock通俗理解就是手工锁,与synchronized的区别是reetrantLock是手动上锁,手动解锁,而synchronized是自动上锁,自动解锁,reetrantLock要更加灵活一些。注意的是,使用reetrantLock时必须手动释放锁。
lock()--获得锁
package thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T06_Lock {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void m2(){
lock.lock();
System.out.println("m2 exe...");
lock.unlock();
}
public static void main(String[] args) {
T06_Lock t=new T06_Lock();
new Thread(t::m1).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(t::m2).start();
}
}
trylock(5,TimeUnit.SECONDS)--尝试获得锁
等待一段时间后获得锁,若失败,则返回false
/*使用trylock进行尝试锁定,不管锁定与否,
方法豆浆继续执行,可以根据trylock的返回值来判定是否锁定,
也可以指定tryLock的时间,由于trylock(time)抛出异常,
所以要注意unlock的处理, 必须放到finally中*/
void m2(){
boolean locked=lock.tryLock();
System.out.println("m2 locked");
if(locked)lock.unlock();
locked=false;
try {
//等待5s后捕获锁,若失败则返回false
locked=lock.tryLock(5,TimeUnit.SECONDS);
System.out.println("m2 locked");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(locked)lock.unlock();
}
}
lockInterruptibly()--可以对现成的interrupt()方法做出响应,在线程在等待锁的过程中可以被打断。
package thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T07_LockInterrupter {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
lock.lock();
System.out.println("t1 start...");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
System.out.println("interrupted");
} finally {
lock.unlock();
}
});
t1.start();
Thread t2 = new Thread(() -> {
try {
// lock.lock();
lock.lockInterruptibly();
System.out.println("t2 start...");
TimeUnit.SECONDS.sleep(5);
System.out.println("t2 end...");
} catch (InterruptedException e) {
System.out.println("interrupted");
} finally {
lock.unlock();
}
});
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();//打断t2线程的等待
}
}
线程t1长时间睡眠,t2无法获取锁,调用interrupt()方法,t2线程等待结束,输出结果。
Lock lock=new ReentrantLock(true)----将锁指定为公平锁
package thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用ReentrantLock是可以指定锁为公平锁
*/
public class T08_Lock extends Thread{
private static Lock lock=new ReentrantLock(true);//将锁指定为公平锁
@Override
public void run() {
for(int i=0;i<5;i++){
lock.lock();
System.out.println(Thread.currentThread().getName()+" get lock");
lock.unlock();
}
}
public static void main(String[] args) {
T08_Lock l=new T08_Lock();
Thread t1=new Thread(l);
Thread t2=new Thread(l);
t1.start();
t2.start();
}
}
输出结果
总结
- .reentrantlock可以完成synchronized的功能,可以替代synchronized
- 2.比synchronized灵活
- 3.可以使用lockinterruptibly 打断锁,
- 4.还可以把锁指定为公平锁