1.同步的定义:
在引出线程同步问题之前首先要了解什么是同步?
所谓的同步指的是所有的线程对于同一个资源的访问上的时序性。
2.Synchronized关键字
在java中使用synchronized关键字来实现线程同步的问题【即加锁操作】
3.synchronized关键字实现线程同步问题的方式:
(1)同步代码块:
即在方法中使用synchronized(对象)的格式,一般可以锁定当前对象 this
举个例子:黄牛卖票
代码如下:
class Ticket implements Runnable{
private int tickets=10;
@Override
public void run() {
for(int i=0;i<10;i++){
synchronized (this){//表示为逻辑上锁
if(this.tickets>0){
try{
Thread.sleep(1000);//线程休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
",还有"+this.tickets--+"张票");
}
};//在同一时刻,只允许一个线程进入到代码块里
}
}
}
public class ThreadSyn{
public static void main(String[] args) {
Ticket ticket=new Ticket();
new Thread(ticket,"黄牛a").start();
new Thread(ticket,"黄牛B").start();
new Thread(ticket,"黄牛C").start();
}
}
程序运行结果【3种】:
有程序运行结果看出有三种情况,而这三种情况恰恰也就证明了同步代码块是在方法里边拦截的,也就意味着进入到方法中的线程依然有可能是多个。
(2)同步方法:
同步方法是在方法声明上加synchronized关键字,表示此时只允许一个线程能够进入方法中。
程序代码:
class Ticket implements Runnable{
private int tickets =10;
@Override
public void run() {
this.sale();
}
public synchronized void sale(){//在方法声明中加入synchronized关键字
for(int i=0;i<10;i++){
if(this.tickets>0){
try{
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
",还有"+this.tickets--+"张票");
}
}
}
}
public class ThreadSyn{
public static void main(String[] args) {
Ticket mt=new Ticket();
Thread thread1=new Thread(mt,"黄牛A");
Thread thread2=new Thread(mt,"黄牛B");
Thread thread3=new Thread(mt,"黄牛C");
thread1.start();
thread2.start();
thread3.start();
}
}
程序运行结果:
由运行结果可以得出,此时只有一个线程进入sale()方法中,虽然这种方式解决了,多个线程进入同一个方法的问题,但是运行速度太慢。
(3)全局锁:即让synchronized锁这个类对应的class对象
程序代码:
class Sync{
public void test(){
synchronized (Sync.class){//给Sync的class对象加锁
System.out.println("test()方法开始,当前线程为:"+
Thread.currentThread().getName());
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test()方法结束,当前线程为:"+
Thread.currentThread().getName());
}
}
}
class Mythreads extends Thread{
@Override
public void run() {
Sync sync=new Sync();
sync.test();
}
}
public class ThreadSyn{
public static void main(String[] args) {
for(int i=0;i<3;i++){
Thread thread=new Mythreads();
thread.start();
}
}
}
程序运行结果:
4.synchronized底层实现:
(1)同步代码块的底层实现:
执行同步代码块后首先要执行monitorenter指令,退出时要执行monitorexit指令,使用synchronized实现同步,关键就是要获取对象的监视器monitor对象,当线程获取到monitor对象后,才可以往下执行,否则就只能等待,并且在同一时刻只能有一个线程可以获取到该对象的monitor监视器。
通常一个monitorenter 指令会包含多个monitorexit指令,因为JVM要确保所获取的锁无论是在正常执行路径还是异常执行路径都能正常破解锁。
(2)同步方法底层实现:
当使用 synchronized 标记方法时,字节码会出现访问标记 ACC_SYNCHRONIZED。该标记标识号在进入该方法时,JVM需要执行monitorenter 指令,在退出该方法时,无论是否正常返回,JVM均需要进行monitorexit操作。
关于monitorenter 和 monitorexit的作用,可以抽象的理解为每个对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
当执行monitorenter指令时,如果目标对象的计数器为0,那么说明它没有被线程所持有,在此种情况下,JVM会将该锁对象的持有线程改为当前线程,并将其计数器加1。
当执行monitorexit时,JVM则将锁对象的计数器减1,直到计数器减为0时,则锁已被释放。