线程的同步与死锁

一、线程的同步问题

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

示例:

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();
                }
           
                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对象为同一个,那么只能有一个线程执行,其它线程受到阻塞。

示例:

import java.util.concurrent.ExecutionException;

class Test implements Runnable {
    private String str;
    private Account account;

    public Test(String str, Account account) {
        this.str = str;
        this.account = account;
    }

    @Override
    public String toString() {
        return this.str;
    }

    @Override
    public void run() {
        fun1();
    }

    public void fun1(){
        synchronized (account){
            for (int i=0;i<10;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" fun1 "+this+" "+i);

            }
        }
    }

    public void fun2(){
        synchronized (account){
            for (int i=0;i<10;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" fun2 "+this+" "+i);

            }
        }
    }
}

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Account account1=new Account();
        Account account2=new Account();
        Thread.currentThread().setName("Thread-M");
        Test test1=new Test("account1",account1);
        Test test2=new Test("account2",account2);
        new Thread(test1,"Thread-A").start();
        new Thread(test2,"Thread-B").start();
        test1.fun2();
        test2.fun2();

    }
}

结果:

 

Thread-M fun2 account1 0
Thread-B fun1 account2 0
Thread-M fun2 account1 1
Thread-B fun1 account2 1
Thread-M fun2 account1 2
Thread-B fun1 account2 2
Thread-M fun2 account1 3
Thread-B fun1 account2 3
Thread-M fun2 account1 4
Thread-B fun1 account2 4
Thread-M fun2 account1 5
Thread-B fun1 account2 5
Thread-M fun2 account1 6
Thread-B fun1 account2 6
Thread-M fun2 account1 7
Thread-B fun1 account2 7
Thread-M fun2 account1 8
Thread-B fun1 account2 8
Thread-B fun1 account2 9
Thread-M fun2 account1 9
Thread-A fun1 account1 0
Thread-M fun2 account2 0
Thread-M fun2 account2 1
Thread-A fun1 account1 1
Thread-M fun2 account2 2
Thread-A fun1 account1 2
Thread-A fun1 account1 3
Thread-M fun2 account2 3
Thread-M fun2 account2 4
Thread-A fun1 account1 4
Thread-A fun1 account1 5
Thread-M fun2 account2 5
Thread-M fun2 account2 6
Thread-A fun1 account1 6
Thread-A fun1 account1 7
Thread-M fun2 account2 7
Thread-M fun2 account2 8
Thread-A fun1 account1 8
Thread-A fun1 account1 9
Thread-M fun2 account2 9

可以看出,首先是Thread-M的test1.fun2()和Thread-B的test2.fun1()并发执行。等到Thread-M执行完test1.fun2()才执行Thread-A的test1.fun1();等到Thread-B执行完test2.fun1()才执行Thread-M的test2.fun2()。如果我们把代码改成test和test1拥有同一个account对象,那么所有的线程都不能并发执行:

Test test1=new Test("account1",account1);
Test test2=new Test("account2",account1);

结果:

Thread-M fun2 account1 0
Thread-M fun2 account1 1
Thread-M fun2 account1 2
Thread-M fun2 account1 3
Thread-M fun2 account1 4
Thread-M fun2 account1 5
Thread-M fun2 account1 6
Thread-M fun2 account1 7
Thread-M fun2 account1 8
Thread-M fun2 account1 9
Thread-M fun2 account2 0
Thread-M fun2 account2 1
Thread-M fun2 account2 2
Thread-M fun2 account2 3
Thread-M fun2 account2 4
Thread-M fun2 account2 5
Thread-M fun2 account2 6
Thread-M fun2 account2 7
Thread-M fun2 account2 8
Thread-M fun2 account2 9
Thread-B fun1 account2 0
Thread-B fun1 account2 1
Thread-B fun1 account2 2
Thread-B fun1 account2 3
Thread-B fun1 account2 4
Thread-B fun1 account2 5
Thread-B fun1 account2 6
Thread-B fun1 account2 7
Thread-B fun1 account2 8
Thread-B fun1 account2 9
Thread-A fun1 account1 0
Thread-A fun1 account1 1
Thread-A fun1 account1 2
Thread-A fun1 account1 3
Thread-A fun1 account1 4
Thread-A fun1 account1 5
Thread-A fun1 account1 6
Thread-A fun1 account1 7
Thread-A fun1 account1 8
Thread-A fun1 account1 9

 

综上得知,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()方法,那么就不会死锁。

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值