为什么会出现同步处理?
如果要写一个多个线程卖票的代码,按照原本思路代码如下:
class MythreadB implements Runnable
{
private Integer tickets=10;
public void run()
{
while(tickets>0) {
try {
System.out.println(Thread.currentThread().getName() + "还剩" + tickets + "张票");
tickets--;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Synch
{
public static void main(String[] args) {
MythreadB thread=new MythreadB();
new Thread(thread,"黄牛1").start();
new Thread(thread,"黄牛2").start();
new Thread(thread,"黄牛3").start();
}
}
从结果可以看出,出现了多个4张票并且2 、3张票不存在,这和事实相悖,是为什么呢?
是因为三个线程并发执行,黄牛2、3、1同时访问共享资源tickets,并且同时卖了一张票,并且将tickets–。事实是对共享资源tickets需要同步访问,即线程1访问时,线程2不能访问,线程2需要等线程1访问结束才可以访问,那么这就需要对共享资源加锁,实现同步处理。
需要同步处理,需要借助关键字synchronized。
同步处理包括同步方法和同步代码块。
同步代码块:
在方法中实现:
synchronized(对象){ }
既然是加锁,那么就需要有锁住的对象,一般锁的是当前对象this,但是也根据情况来锁对象。
同步代码块指同一时刻只有一个线程进入同步代码块,但是多个线程可以进入方法。
////同步代码块
class MythreadB implements Runnable
{
private Integer tickets=10;
public void run()
{
for(int i=0;i<10;i++)
{
////同步代码块,一次只允许一个线程进入该代码块
synchronized (this)
//锁住的是当前MythreadB对象,而只有一个MythreadB对象,3个线程是通过MythreadB转换为Thread创建出来的,
// 所以实现了同步,一次只能有线程卖票,一个线程卖完一张票后,隐式解锁,其他线程可以卖票
{
if(tickets>0) {
try {
System.out.println(Thread.currentThread().getName() + "还剩" + tickets + "张票");
tickets--;
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Synch
{
public static void main(String[] args) {
MythreadB thread=new MythreadB();
new Thread(thread,"黄牛1").start();
new Thread(thread,"黄牛2").start();
new Thread(thread,"黄牛3").start();
}
}
从结果可以看出,实现了同步,同一时刻只有一个线程卖票。注意:synchronized是隐式解锁,后面会讲解。
同步代码块是只能有一个线程进入该代码块,但是会有多个线程进入该方法,如果想要同一时刻只有一个线程进入方法,需要用同步方法。
同步方法
同步方法是当方法声明上加synchronized,表示此时只有一个线程进入同步方法。锁住的该类实例化的对象。
class MythreadB implements Runnable
{
private Integer tickets=10;
public void run()
{
for(int i=0;i<10;i++)
{
sale();
}
}
synchronized public void sale() //同步方法,表示同一时刻只能有一个线程进入该方法,锁住的是MythraedB对象
{
if(tickets>0) {
try {
System.out.println(Thread.currentThread().getName() + "还剩" + tickets + "张票");
tickets--;
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
synchronized锁住的对象,并不是代码块。
验证如下:
class A
{
synchronized public void print()
{
System.out.println(Thread.currentThread().getName()+":进入print方法");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":离开print方法");
}
}
class MythreadB implements Runnable
{
public void run()
{
A myA=new A();
myA.print();
}
}
public class Synch
{
public static void main(String[] args) {
MythreadB thread=new MythreadB();
new Thread(thread,"线程1").start();
new Thread(thread,"线程2").start();
new Thread(thread,"线程3").start();
}
}
从结果分析:如果synchronized锁住的print这个代码块,那么当一个线程进入print方法并且离开print方法,另一个线程才可以进入,但是结果不是这样。是因为synchronized锁住的是A实例化出的对象,而在主方法中有三个线程,每个线程创建后,JVM回调run方法,run方法会new A对象,3个线程就会有3个A对象,synchronized只会将该线程的A对象锁住,并不能锁其他A对象。所以,3个线程会并发进入,并发离开。
总结:synchronized(this)以及普通synchronized方法,只能防止多个线程同时执行同一对象的同步端,synchronized锁的括号中的对象而非代码。
如果想要将这个print代码段锁住,即同一时刻只有一个线程进入print,该怎么做呢?
有2种方法:
锁同一个对象
////锁同一个对象
class A
{
synchronized public void print()
{
System.out.println(Thread.currentThread().getName()+":进入print方法");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":离开print方法");
}
}
class MythreadB implements Runnable
{
private A myA; //现在就只有一个A对象
public MythreadB(A a)
{
this.myA=a;
}
public void run()
{
this.myA.print();
}
}
public class Synch
{
public static void main(String[] args) {
MythreadB thread=new MythreadB(new A()); //同一个A对象
new Thread(thread,"线程1").start();
new Thread(thread,"线程2").start();
new Thread(thread,"线程3").start();
}
}
当只有一个对象A后,synchronized 就锁住了的三个线程的同一个A对象,那么一个线程就会等另一个线程出代码块解锁后才会进入print代码块。
全局锁:
全局锁,锁住的类对象,并不是类实例化出的对象。
如果我们想要锁住多个对象的同一方法,可以使用全局锁,将当前类对象锁住,使用类的静态同步方法 synchronized与static一起使用,此时锁的是当前使用的类对象而非类实例化出的对象。
class A
{
synchronized static public void print()
//定义为静态方法,也就是将A这个类锁住,那么同一时刻,只能有一个类对象进入代码块
{
System.out.println(Thread.currentThread().getName()+":进入print方法");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":离开print方法");
}
}
class MythreadB implements Runnable
{
public void run()
{
A.print();
}
}
public class Synch
{
public static void main(String[] args) {
MythreadB thread=new MythreadB();
new Thread(thread,"线程1").start();
new Thread(thread,"线程2").start();
new Thread(thread,"线程3").start();
}
}
同样可以使用synchronized(this)同步代码块来实现全局锁,
在代码块中锁当前class(类)对象:synchronized(类名称.class){ }
代码如下:
//// 同步代码块 :synchronized(类名称.class)
class A
{
public void print()
{
synchronized (A.class)
{
System.out.println(Thread.currentThread().getName() + ":进入print方法");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":离开print方法");
}
}
}
class MythreadB implements Runnable
{
public void run()
{
new A().print();
}
}
小练习:线程1进入同步方法1,线程2能否进入同步方法2
//线程1进入同步方法1,线程2能否进入同步方法2
class A
{
////线程1持有A实例化出对象锁,并且一直死循环
synchronized public void testA() {
if (Thread.currentThread().getName().equals("线程1")) {
while (true) {
}
}
}
///线程2进入testB
synchronized public void testB() {
if (Thread.currentThread().getName().equals("线程2")) {
System.out.println("线程2进入同步方法");
}
}
}
class MythreadB implements Runnable
{
private A myA;
public MythreadB(A a)
{
this.myA=a;
}
public void run()
{
this.myA.testA();
this.myA.testB();
}
}
public class Synch
{
public static void main(String[] args) {
MythreadB thread=new MythreadB(new A());
new Thread(thread,"线程1").start();
new Thread(thread,"线程2").start();
}
}
结果:并没有输出“线程2进入同步方法”,是因为线程1给A实例化出对象加锁,在线程1没有释放锁前,线程2无法获得实例化对象锁,这也更加说明了synchronized锁的是对象而不是代码块。
但是如果在testA方法前不加synchronized ,即线程1没有A实例化对象锁,线程2可以进入testB,输出“线程2进入同步方法”。