欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线程的同步与死锁

程序员文章站 2022-03-10 16:02:26
...

一、线程的同步问题

     当多个线程访问同一个资源对象时会引发同步问题。

示例:

public class MyThread implements Runnable {
    private int n=10;//n为共享资源
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            if (n>0){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (this) {
                    System.out.println(Thread.currentThread().getName() + "卖票" + n--);
                }
            }
        }

    }
}

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread=new MyThread();
        Thread a=new Thread(myThread,"A");
        Thread b=new Thread(myThread,"B");
        Thread c=new Thread(myThread,"C");
        a.start();
        b.start();
        c.start();
    }
}

 

输出:

线程的同步与死锁

      

       为什么会出现负数的情况呢?原因如下:A线程进人if判断语句,当前票数=1>0,进入休眠的时候,B线程也进入if判断语句,当前票数也=1,B线程也进入休眠,同样,C线程也如此。然后,A线程休眠完了,打印票数=1并将票数减1变为0。此时,B线程也休眠完,打印票数=0减1变为-1,那么当C打印票数的时候就是-1。

       你可能会疑惑,这样说来,应该是先打印B卖票0,再打印C卖票-1啊。这个又要跟print()括号里有关了,其实是先执行n--,再执行打印的。n--和打印字符串并不是两步并不是原子性操作。所以当B线程执行n--后,可能还没打印n,C线程也执行了n--,并且先打印了n。

       所以据此我猜想,print那行代码,是分三步执行的,先将括号里的合成一个字符串str,再n--,最后打印str,只有最后的打印是原子性操作。可参考 留意i--与System.out.println()的异常

 

二、线程的同步处理——synchronized

1.一个线程访问一个对象中的synchronized(this)时,其他的线程就不能访问该对象的所有synchronized(this)代码。

 

示例:

线程的同步与死锁

输出:

线程的同步与死锁

      

       无论test.fun1()还是test.fun2(),输出结果都一样,都是一个线程执行完再执行另一线程。因为A线程和main线程都试图访问同一个对象test的synchronized(this)代码块,所以其中必有一个线程需要等待另一个线程访问完才能访问。

 

       如果我们把代码改一下:

线程的同步与死锁

     

      输出:

线程的同步与死锁

        我们仅仅把test.fun1()改成test1.fun1(),结果就不一样。因为test和test1是两个不同的对象,而synchronized(this)锁住的是当前对象,有且仅有一个对象,所以A线程和main线程可以并发执行。

 

2.当一个线程访问一个对象的synchronized(this)代码块时,其他线程仍能访问该对象的非synchronized(this)代码块。

  

    仍然是上面的例子,我们稍稍给动一下代码,将fun2()改成非同步代码块:

   线程的同步与死锁

 

   

 结果:

线程的同步与死锁

      

       可以看出,虽然两个线程访问的都是同一个对象test,但是非synchronized()代码块是不受阻挠的,两个线程仍能并发执行。

 

3.指定要给某对象加锁。这里只是把synchhronized(this)括号中的this换成一个指定对象,同步原理相同。当几个线程同时访问synchronized(x对象)代码块时(此处不仅仅是指访问同一处的,而是指所有的,只要加了synchronized(x对象),其实与this是相同的),若这几个线程所拥有的x对象为同一个,那么只能有一个线程执行,其它线程受到阻塞。

示例:

线程的同步与死锁线程的同步与死锁

 

结果:

线程的同步与死锁

线程的同步与死锁

 

   可以看出,main线程中只有test1.fun2()可以和A线程的test.fun1()并发执行,因为test1和test所拥有的对象account是不同的,如果我们把代码改成test和test1拥有同一个account对象:

线程的同步与死锁

结果:

线程的同步与死锁

 

综上得知,synchronized()锁住的其实是括号里的对象,不同线程执行时,我们要看括号里的对象是否为同一个,如果是,则要等待解锁。

 

4.修饰一个普通方法,与2效果相同。

5.修饰一个静态方法=修饰一个类。锁住的是该类的所有对象。

 

参考http://www.importnew.com/21866.html

 

三、死锁

      当A线程在B个线程执行完,B线程又在等A线程执行完的时候,就会出现死锁。

 

示例:

线程的同步与死锁

线程的同步与死锁

结果:

线程的同步与死锁

 

       当main线程执行到b.say(a),打印完第一行后,进入休眠2秒的状态,此时,子线程Thread-0执行a.say(b),也打印完第二行。主线程b.say(a)中的a.get()需要等子线程a.say(b)方法执行完,而a.say(b)中的b.get()又要等主线程的b.say(a)执行完,所以就形成死锁。

       假如把休眠2秒去掉:

线程的同步与死锁

    

结果:

线程的同步与死锁

 

      此时,main线程执行a.get()时,子线程还未执行run()方法,那么就不会死锁。