------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
为什么要使用线程的同步?
在多线程环境中,可能会有两个甚至跟多的线程试图同时访问一个有限的资源。必须对这种潜在的资源冲突进行预防。
解决方法:再使用一个资源时为其加锁。让访问资源的第一个线程为其加上锁以后,其他的线程便不能再使用该资源,除非资源被解锁。
比如下面这种没有对资源进行处理的时候,就会发生多线程对同一个资源进行操作导致发生了数据不一致的错误:
package thread;
public class FetchMoeny
{
public static void main(String[] args)
{
Bank bank = new Bank();
Thread t1 = new MoneyThread2(bank);
Thread t2 = new MoneyThread2(bank);
t1.start();
t2.start();
}
}
class Bank
{
private int money = 1000;
public int get (int number)
{
if(number<0||number>money)
{
return -1;
}
else
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
money -=number;
System.out.println("余额:"+money);
return number;
}
}
}
class MoneyThread2 extends Thread
{
private Bank bank;
public MoneyThread2(Bank bank)
{
this.bank = bank;
}
@Override
public void run()
{
System.out.println("取出了" + bank.get(800));
}
}
余额:200
取出了800
余额:-600
取出了800
也可能是
余额:-600
取出了800
余额:-600
取出了800
为什么会发生这样的事呢?由于bank是同一个对象,当第一个线程进入操作的时候还没修改数据的时候第二个线程又进来了,这时候第二个线程也是可以进来正常执行的,
之后当第一个线程执行完之后第二个线程又可以对第一个线程操作过之后的余额进行操作,这样就发生了上面的与期望需求不一致的数据。
关于200和-600出现的就是第一第二个线程对数据操作的时刻与两个线程输出结果的时刻不确定导致的。
解决方法:使用Synchronied关键字
package thread;
public class FetchMoeny
{
public static void main(String[] args)
{
Bank bank = new Bank();
Thread t1 = new MoneyThread2(bank);
//bank = new Bank(); 這樣就互不影響 相當於兩個帳戶
Thread t2 = new MoneyThread2(bank);
t1.start();
t2.start();
}
}
class Bank
{
private int money = 1000;
public synchronized int get (int number)
{
if(number<0||number>money)
{
return -1;
}
else
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
money -=number;
System.out.println("余额:"+money);
return number;
}
}
}
class MoneyThread2 extends Thread
{
private Bank bank;
public MoneyThread2(Bank bank)
{
this.bank = bank;
}
@Override
public void run()
{
System.out.println("取出了" + bank.get(800));
}
}
执行结果:
余额:200
取出了800
取出了-1
解析:
Synchronied关键字:当一个方法被Synchronied修饰的时候,该方法叫做同步方法。该方法再被多个线程使用的时候当有一个线程时候
时会对该方法加锁,这样别的方法就不能再访问该方法直到锁被释放,这样就会不会出现同一个方法被多个线程同事操作的情况了。
java中的每个对象都有一个锁(lock),或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程
都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他的线程才能
继续访问该synchronized方法。
如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么再该方法没有执行完毕之前,其他线程是无法访问该对象的
任何synchronized方法。
例如下面这种情况:
package thread;
public class ThreadTest4
{
public static void main(String[] args)
{
Example e = new Example();
Thread t1 = new ExamThread(e);
// e = new Example();// 這樣的話就有了兩個對象 此時還是只能給一個對象上鎖 對別的不影響
Thread t2 = new ExamThread2(e);
t1.start();
t2.start();
}
}
class Example
{
public void excute()
{
for(int i = 0;i<20;i++)
{
try
{
Thread.sleep((long) (Math.random()*200));
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("i: "+i);
}
}
public void excute2()
{
for(int i = 0;i<50;i++)
{
try
{
Thread.sleep(200);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("i2: "+i);
}
}
}
class ExamThread extends Thread
{
private Example example;
public ExamThread(Example example)
{
this.example = example;
}
@Override
public void run()
{
this.example.excute();
}
}
class ExamThread2 extends Thread
{
private Example example;
public ExamThread2(Example example)
{
this.example = example;
}
@Override
public void run()
{
this.example.excute2();
}
}
这种情况两个线程就可以同时执行example中的两个方法,打印出的数据是随机的没有规律。
使用synchronied关键字之后:输出的结果就很有规律,先输出excute的数据,再输出excute2的数据:
package thread;
public class ThreadTest4
{
public static void main(String[] args)
{
Example e = new Example();
Thread t1 = new ExamThread(e);
// e = new Example();// 這樣的話就有了兩個對象 此時還是只能給一個對象上鎖 對別的不影響
Thread t2 = new ExamThread2(e);
t1.start();
t2.start();
}
}
class Example
{
public synchronized void excute()
{
for(int i = 0;i<20;i++)
{
try
{
Thread.sleep((long) (Math.random()*200));
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("i: "+i);
}
}
public synchronized void excute2()
{
for(int i = 0;i<50;i++)
{
try
{
Thread.sleep(200);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("i2: "+i);
}
}
}
class ExamThread extends Thread
{
private Example example;
public ExamThread(Example example)
{
this.example = example;
}
@Override
public void run()
{
this.example.excute();
}
}
class ExamThread2 extends Thread
{
private Example example;
public ExamThread2(Example example)
{
this.example = example;
}
@Override
public void run()
{
this.example.excute2();
}
}
如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,
因为java中无论一个类有多少个对象,这些对象对应唯一一个Class对象,因此当线程分别访问同一个类的两个static,synchronized方法时,他们的执行顺序也是顺序的
也就是说一个线程先去执行发发,执行完毕后另一个线程才开始执行。
synchronized快,写法:
synchronized(object)
{
}
表示线程在执行的时候会对object对象上锁。
demo:
package thread;
public class ThreadTest5
{
public static void main(String[] args)
{
Example2 e = new Example2();
Thread t3 = new ExamThread22(e);
Thread t4 = new ExamThread222(e);
t3.start();
t4.start();
}
}
class Example2
{
private Object object = new Object();
public void excute()
{
synchronized(object)
{
for(int i = 0;i<20;i++)
{
try
{
Thread.sleep((long) (Math.random()*200));
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("i: "+i);
}
}
}
public void excute2()
{
synchronized(object)//this 這樣就能達到跟synchronized的方法一樣
//這裡與上鎖的對象沒有關係 鎖什麽都行
{
for(int i = 0;i<20;i++)
{
try
{
Thread.sleep(200);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("i2: "+i);
}
}
}
}
class ExamThread22 extends Thread
{
private Example2 example2;
public ExamThread22(Example2 example2)
{
this.example2 = example2;
}
@Override
public void run()
{
this.example2.excute();
}
}
class ExamThread222 extends Thread
{
private Example2 example2;
public ExamThread222(Example2 example2)
{
this.example2 = example2;
}
@Override
public void run()
{
this.example2.excute2();
}
}
的代码同步,位于方法内、synchronized块之外的代码是可以被多个线程同时访问到的,使用synchronized代码块的效率往往比synchronized方法的效率高。