让线程同步的方式有两种,一种是使用synchronized(){}代码块,一种是使用synchronized关键字修饰待保证同步的方法。
class Ticket implements Runnable { private int num; //初始化票的数量 private Object obj = new Object(); Ticket(int num){ this.num = num; } //售票 public void sale() { synchronized(obj) { //使用同步代码块封装需要保证原子性的代码 if(num>0) { num--; System.out.println(Thread.currentThread().getName()+"-------"+remain()); } } } //获取剩余票数 public int remain() { return num; } public void run(){ while(true) { sale(); } } } class Ticket implements Runnable { private int num; //初始化票的数量 Ticket(int num){ this.num = num; } public synchronized void sale() { //使用synchronized关键字,方法变为同步方法 if(num>0) { num--; System.out.println(Thread.currentThread().getName()+"-------"+remain()); } } //获取剩余票数 public int remain() { return num; } public void run(){ while(true) { sale(); } } }使用同步之后,if(num>0)、num--、return num和print(num)这4个任务就强制具有原子性。某个线程只要开始执行了if语句,它就一定会继续执行直到执行完print(num),才算完成了一整个任务。只有完成了一整个任务,线程才会释放锁(当然,也可能继续判断while(true)并进入下一个循环)。
4.3 同步代码块和同步函数的区别以及锁是什么前面的示例中,同步代码块synchronized(obj){}中传递了一个obj的Object对象,这个obj可以是任意一个对象的引用,这些引用传递给代码块的作用是为了标识这个同步任务所属的锁。
而synchronized函数的本质其实是使用了this作为这个同步函数的锁标识,this代表的是当前对象的引用。但如果同步函数是静态的,即使用了static修饰,则此时this还没出现,它使用的锁是"类名.class"这个字节码文件对象,对于java来说,这也是一个对象,而且一个类中一定有这个对象。
使用相同的锁之间会互斥,但不同锁之间则没有任何影响。因此,要保证任务同步(原子性),这些任务所关联的锁必须相同。也因此,如果有多个同步任务(各自保证自己的同步性),就一定不能都使用同步函数。
例如下面的例子中,写了两个相同的sale()方法,并且使用了flag标记让不同线程能执行这两个同步任务。如果出现了多线程安全问题,则表明synchronized函数和同步代码块使用的是不同对象锁。如果将同步代码块中的对象改为this后不出现多线程安全问题,则表明同步函数使用的是this对象。如果为sale2()加上静态修饰static,则将obj替换为"Ticket.class"来测试。
class Ticket implements Runnable { private int num; //初始化票的数量 boolean flag = true; private Object obj = new Object(); Ticket(int num){ this.num = num; } //售票 public void sale1() { synchronized(obj) { //使用的是obj标识锁 if(num>0) { num--; try{Thread.sleep(1);} catch (InterruptedException i){} //为了确保num--和println()分开,加上sleep System.out.println(Thread.currentThread().getName()+"===sale1==="+remain()); } } } public synchronized void sale2() { //使用this标识锁 if(num>0) { num--; try{Thread.sleep(1);} catch (InterruptedException i){} System.out.println(Thread.currentThread().getName()+"===sale2==========="+remain()); } } //获取剩余票数 public int remain() { return num; } public void run(){ if(flag){ while(true) { sale1(); } } else { while(true) { sale2(); } } } } public class Mytest { public static void main(String[] args) { Ticket t = new Ticket(200); //创建多个线程对象 Thread t1 = new Thread(t); Thread t2 = new Thread(t); //开启多个线程使其执行任务 t1.start(); try{Thread.sleep(1);} catch (InterruptedException i){} t.flag = false; t2.start(); } }