多线程(2)- 线程同步

来源:互联网 发布:edius mac 编辑:程序博客网 时间:2024/06/11 08:19

卖票的例子

class Ticket implements Runnable{    private  int tick = 100;    public void run()    {        while(true)        {            if(tick>0)            {                System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);            }        }    }}class  TicketDemo{    public static void main(String[] args)     {        Ticket t = new Ticket();        Thread t1 = new Thread(t);//创建了4个线程;        Thread t2 = new Thread(t);        Thread t3 = new Thread(t);        Thread t4 = new Thread(t);        t1.start();        t2.start();        t3.start();        t4.start();    }}

事实上,该代码是存在安全问题的。当余票为1时,某线程符合if(tick>0),进入if里的执行语句,但是刚好CPU切到别的地方执行别的程序,当前线程具有执行资格但没有执行权,需要等待。而此时tick仍然等于1,别的线程仍能进入if里的执行语句,同样进行等待。当CPU回来处理该块代码时,就会发生多个线程都要被执行,打印出0,-1等错票
出现该问题的原因是:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
Java对于多线程的安全问题提供了专业的解决方式-同步代码块。

同步代码块

synchronized(对象)
{
需要被同步的代码
}
对象如同锁,这里可以是任意对象。持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

改进后代码:

class Ticket implements Runnable{    private  int tick = 100;    Object obj=new Object();    public void run()    {        while(true)        {            synchronized(obj)            {                if(tick>0)                {                    System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);                }            }        }    }}class  TicketDemo{    public static void main(String[] args)     {        Ticket t = new Ticket();        Thread t1 = new Thread(t);//创建了4个线程;        Thread t2 = new Thread(t);        Thread t3 = new Thread(t);        Thread t4 = new Thread(t);        t1.start();        t2.start();        t3.start();        t4.start();    }}

同步的前提:
1. 必须要有两个或者两个以上的线程。
2. 必须是多个线程使用同一个锁。
3. 必须保证同步中只能有一个线程在运行。

好处:解决了多线程的安全问题。
弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形
中会降低程序的运行效率。

同步函数

上面的Ticket也可以写成同步函数的形式

class Ticket implements Runnable{    private  int tick = 100;    public void run()    {        while(true)        {            show();        }    }    public synchronized void show()    {        if(tick>0)        {             System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);        }    }}
  • 需要注意的是需要同步的代码是哪一段:如果直接将run函数用synchronized修饰显然是不对的,需要同步的是里面的代码,所以这个地方新建一个函数,并用synchronized修饰,并在run方法里进行调用。
  • 在上面的同步代码块中,锁对应的对象是我们创建的Object类的对象。而在同步函数中,锁对应的对象是 this。
  • 在静态同步函数中,锁对应的对象为该方法所在类的字节码文件对象,即 类名.class

死锁

两个线程相互等待对方释放资源锁(即刚好A线程要用到B线程锁定的一个资源,而B线程中刚好也要用到A线程中锁定的一个资源),两个线程相互等待。
任何操作系统都无法完全避免死锁,所以编程时一定要注意,特别是在同步锁特别多的情况下死锁多发。
eg:

class Demo implements Runnable{    private boolean flag;    Demo(boolean flag)    {        this.flag = flag;    }    public void run()    {        if(flag)        {            while(true)            {                synchronized(MyLock.locka)                {                    System.out.println(Thread.currentThread().getName()+"...if locka ");                    synchronized(MyLock.lockb)                    {                        System.out.println(Thread.currentThread().getName()+"..if lockb");                                      }                }            }        }        else        {            while(true)            {                synchronized(MyLock.lockb)                {                       System.out.println(Thread.currentThread().getName()+"...else lockb");                    synchronized(MyLock.locka)                    {                 System.out.println(Thread.currentThread().getName()+".....else locka");                    }                }            }        }    }}class MyLock{    static Object locka = new Object();    static Object lockb = new Object();}class Test{    public static void main(String[] args)     {        Thread t1 = new Thread(new Demo(true));        Thread t2 = new Thread(new Demo(false));        t1.start();        t2.start();    }}

在这个例子中,flag为true时拥有locka锁的线程没有lockb锁,flag为false时拥有lockb锁的线程没有locka锁,都无法进入下一个输出语句代码块,僵持不下产生死锁

0 0