java学习笔记(十二)-- 线程同步与死锁

一 线程的同步   ***************

同步问题:每一个线程对象轮番抢占共享资源带来的问题

首先看一段代码:

class MyThread implements Runnable{
    private int ticket=10;
    @Override
    public void run() {
        while (this.ticket>0){
            try{
                //模拟网络延迟
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+
",还有"+this.ticket--+"张票");
        }
    }
}
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt=new MyThread();
        new Thread(mt,"黄牛A").start();
        new Thread(mt,"黄牛B").start();
        new Thread(mt,"黄牛C").start();
    }
}
这时会发现运行到最后时会出现如下结果,当只剩下一张票的时候,却被三个人拿去同时买:
......
黄牛B,还有1张票
黄牛C,还有0张票
黄牛A,还有-1张票

上面出现现象就可以称之为不同步操作,其唯一好处就是处理速度快(多个线程并发执行)

1 同步处理

1.1 使用synchronize关键字(内建锁,JDK1.0作为关键字提供的同步手段)来处理同步问题  对象锁一定要清楚锁的对象

synchronize处理同步有两种模式:同步代码块,同步方法

同步代码块:(推荐使用,锁粒度较细)要使用同步代码块必须设置一个锁定的对象,一般可以锁当前对象this

同一时刻只能有一个线程进入代码块,方法内仍然是多线程

synchronized(this){

    //需要同步代码块

}

class MyThread implements Runnable{
    private int ticket=1000;
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            synchronized (this){
                if(this.ticket>0){
                    System.out.println(Thread.currentThread().getName()+
",还有"+this.ticket--+"张票");
                }
            }
        }
    }
}
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt=new MyThread();
        new Thread(mt,"黄牛A").start();
        new Thread(mt,"黄牛B").start();
        new Thread(mt,"黄牛C").start();
    }
}
会发现执行结果中没有负数:
......
黄牛B,还有3张票
黄牛B,还有2张票
黄牛B,还有1张票

同步方法:在方法上添加synchronize关键字,表示此方法只有一个线程能进入。隐式锁对象,this

同一时刻只有一个线程能进入此方法

范例:

class MyThread implements Runnable{
    private int ticket=1000;
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            if(this.ticket>0){
                this.sala();
            }
        }
    }
    public synchronized void sala(){
        if(this.ticket>0){
            try {
                //模拟网络延迟
                Thread.sleep(20);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+
",还有"+this.ticket--+"张票");
        }
    }
}
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt=new MyThread();
        new Thread(mt,"黄牛A").start();
        new Thread(mt,"黄牛B").start();
        new Thread(mt,"黄牛C").start();
    }
}
会发现执行结果中没有负数:
......
黄牛C,还有3张票
黄牛A,还有2张票
黄牛A,还有1张票

同步的缺点:虽然可以保证数据的完整性(线程安全操作),但是其执行的速度回很慢  StringBuilder 和 StringBuffer

  • StringBuffer  JDK1.0 ,采用同步处理,线程安全,效率较低
  • StringBuilder   JDK1.5,采用异步处理,线程不安全,效率较高


1.2 关于synchronized的额外说明

实际上synchronized(this)

非static的synchronized方法,只能防止多个线程同时执行同一个对象的同步代码块。

synchronized叫对象锁(默认只能锁一个对象),锁的是对象本身即this。

范例:观察synchronized的额外说明

class Sync{
    public synchronized void test(){    //默认锁的是当前对象
        System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("test方法结束,当前线程为"+Thread.currentThread().getName());
    }
}
class MyThread extends Thread{
  //使用static只实例化一个Sync对象,这样三个线程使用同一块锁
    //static Sync sync=new Sync();
    @Override
    public void run() {
        Sync sync=new Sync();
        sync.test();
    }
}
public class Test2 {
    public static void main(String[] args) {
        for(int i=0;i<3;i++){
            Thread thread=new MyThread();
            thread.start();
        }
    }
}
观察结果,三个线程同时开始,同时结束:
test方法开始,当前线程Thread-0
test方法开始,当前线程Thread-1
test方法开始,当前线程Thread-2
test方法结束,当前线程为Thread-0
test方法结束,当前线程为Thread-1
test方法结束,当前线程为Thread-2

若要锁住这段代码还有三中思路:

范例: 锁同一个对象

class Sync{
    public synchronized void test(){
        System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("test方法结束,当前线程为"+Thread.currentThread().getName());
    }
}
class MyThread extends Thread{
    private Sync sync;
    public MyThread(Sync sync){
        this.sync=sync;
    }
    @Override
    public void run() {
        this.sync.test();
    }
}
public class Test2 {
    public static void main(String[] args) {
        //实例化一个Sync对象
        Sync sync=new Sync();
        for(int i=0;i<3;i++){
            Thread thread=new MyThread(sync);
            thread.start();
        }
    }
}

对象锁与全局锁:

synchronized默认对象锁,锁的是当前对象而非代码块

全局锁锁的是真正代码段,与对象无关!

实现全局锁的两种方式:

1.在同步代码段锁Class对象

2.使用static synchronized方法

范例:使用全局锁,锁的是类而不是this对象

class Sync{
    public void test(){
        //使用全局锁,锁的是以下代码段,与对象无关
        synchronized (Sync.class){
            System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("test方法结束,当前线程为"+Thread.currentThread().getName());
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        Sync sync = new Sync() ;
        sync.test();
    }
}
public class Test2 {
    public static void main(String[] args) {
        for(int i=0;i<3;i++){
            Thread thread=new MyThread();
            thread.start();
        }
    }
}

static synchronized方法,static方法可以直接类名加方法名调用,方法中无法使用this,所以它锁的不是this,而是类 的Class对象,所以,static synchronized方法也相当于全局锁,相当于锁住了代码段。

范例:使用static synchronized方法

class Sync{
    //此时锁的为一下代码块,与对象无关
    public static  synchronized void test(){    //默认锁的是当前对象
        System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("test方法结束,当前线程为"+Thread.currentThread().getName());
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        Sync sync=new Sync();
        sync.test();
    }
}
public class Test2 {
    public static void main(String[] args) {
        for(int i=0;i<3;i++){
            Thread thread=new MyThread();
            thread.start();
        }
    }
}

范例:看一段代码( **** 重要 **** )典型面试题

class MyThread implements Runnable{
    private ThreadTest test;
        public MyThread(ThreadTest test){
        super();
        this.test=test;
    }
    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("Thread-1")){
            this.test.testA();
        }else if(Thread.currentThread().getName().equals("Thread-2")){
            this.test.testB();
        }
    }
}
class ThreadTest{
    public synchronized void testA(){
        System.out.println(Thread.currentThread().getName()+"tsetA方法");
        while(true){}
    }
    public synchronized void testB(){
        System.out.println(Thread.currentThread().getName()+"tsetB方法");
    }
}
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        ThreadTest test=new ThreadTest();
        MyThread mythread=new MyThread(test);
        Thread thread1=new Thread(mythread);
        Thread thread2=new Thread(mythread);
        thread1.start();
        Thread.sleep(1000);
        thread2.start();
    }
}

线程2是否能进入到testB中吗?

锁的是当前对象,两个同步方法锁的是同一个对象

同一个对象,当一个类中有两个同步方法时,有一个线程已经进了一个同步方法时,另外一个线程绝对不能进入另外的同步方法

 

如果有一个同步方法一个普通方法,另外一个线程可以进到普通方法



1.3 对象锁(monitor)机制   ---  JDK6之前的synchronized(重量级锁)

同步代码块:执行同步代码块后首先要执行monitorenter指令,退出时执行monitorexit指令。

使用内建锁(synchronized)进行同步,关键在于获取指定锁对象monitor对象,当线程获取monitor后才能继续向下执行,否则就只能等待。这个获取过程是互斥的,即同一时刻只有一个线程能够获取到对象monitor。

 

通常一个monitorenter指令会包含若干个monitorexit指令。原因在于JVM需要确保在正常执行路径以及异常执行路径上都能正确的解锁,任何情况下都能够解锁。

同步方法:当使用所有synchronized标记方法时,编译后字节码中方法的访问标记多了一个 ACC_SYNCHRONIZED。该标记表示,进入该方法时,JVM需要进行monitorenter操作,退出该方法时,无论是否正常返回,JVM均需要进行monitorexit操作。

当执行monitorenter时,如果目标锁对象的monitor计数器为0,表示此对象没有被任何其他对象所持有。此时JVM会将该锁对象的持有线程设置为当前线程,并且有计数器+1;如果目标锁对象计数器不为0,判断锁对象的持有线程是否是当前线程,如果是再次将计数器+1(锁的可重入性),如果锁对象的持有线程不是当前线程,当前线程需要等待,直至持有线程释放锁。

 

当执行monitorexit时,JVM会将锁对象的计数器-1,当计数器减为0时,代表该锁已经被释放。

 

JDK1.5提供的Lock锁(了解即可 )(任然是对象锁)

范例:使用ReentrantLock进行同步处理,在需要上锁的时候Lock,在finally中unLock

class MyThread implements Runnable{
    private int ticket=100;
    private Lock ticketLock=new ReentrantLock();
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            try{
                ticketLock.lock();
                if(this.ticket>0) {
                System.out.println(Thread.currentThread().getName() +
"还剩下:" + this.ticket-- + "票");
                }
            }finally{
                //手工释放锁
                ticketLock.unlock();
            }
        }
    }
}
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt=new MyThread();
        Thread thread1=new Thread(mt,"黄牛1");
        Thread thread2=new Thread(mt,"黄牛2");
        Thread thread3=new Thread(mt,"黄牛3");
        thread1.setPriority(Thread.MIN_PRIORITY);
        thread2.setPriority(Thread.MAX_PRIORITY);
        thread3.setPriority(Thread.MAX_PRIORITY);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值