JAVA------多线程之探究同步

问题提出

       今假设今天晚上北京到上海的高铁只剩下1张二等座,购票流程为查询余票,付钱,出票三个步骤。此时张三和李四分别在不同的窗口购票,若此时没有同步机制,则可能出现张三在出票前,李四查询任然有一张余票,可付款,出票,则此时会出现一张票同时卖给两个人。这种情况当然是不允许的,不然春运回家时会发现你的座位上坐着一个抠脚大汉。。。。

解决方法

       解决方法很简单,只要保证张三访问火车票在退出(出票)前,其他人(李四)不能够访问操作这张票就可以。

线程同步

       由上述例子可以看出,张三李四相当于两个线程,同时访问“票”这个资源,必须保证同一时刻只能有一个人(线程)去访问操作票(共享数据、资源)。

        故可引出线程同步的概念:多个线程相互协调的访问同一资源。

代码示例:

package thread;

/**
 * 线程同步
 */
public class T04_Sync implements Runnable{

    Timer timer=new Timer();

    public static void main(String[] args) {
        T04_Sync sync=new T04_Sync();
        Thread t1=new Thread(sync);
        Thread t2=new Thread(sync);
        t1.setName("T1");
        t2.setName("T2");
        t1.start();
        t2.start();
    }
    @Override
    public void run() {
        timer.add(Thread.currentThread().getName());
    }
    class Timer {
        private  int num=0;
        public void  add(String name){
            num++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name+"你是第"+num+"个使用timer的线程");
        }
    }
}

输出结果:

此时输出结果与我们预期不符,代码加入同步(对方法加锁)

  public synchronized  void  add(String name){
            num++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name+"你是第"+num+"个使用timer的线程");
        }

输出结果:

此时输出结果符合我们的预期

实现线程同步方式

synchronized关键字

synchronized是基本的线程同步方式。当存在共享数据,且同时有多个线程同时操作共享数据时,synchronized关键字能保证在同一时刻,只有一个线程能访问或执行某一方法或代码块。

原理

      先看代码

public class T{
	private int count =10;
	private Object o=new Object();
	public void m(){ 
		synchronized(o){
			count--;
			System.out.println(count)
		}
	}
 }

任何线程要执行count--时,都必须要获取到o这把,此时线程进入临界区,其他线程不能往下执行,必须等待线程执行完毕,把o锁释放,在申请绑定,获得锁后才能继续往下执行。

那么如何加锁?

class Timer {
    private  int num=0;
    //此时锁是当前timer对象
    public synchronized  void  add(String name){
        num++;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"你是第"+num+"个使用timer的线程");
    }
    //静态方法加锁,此时锁时Timer.class对象
    public synchronized static void coutName(){
        System.out.println(Thread.currentThread().getName());
    }
    
    //代码块加锁,此时锁是lock对象
    Object lock=new Object();
    public  void coutName1(){
        synchronized (lock){
            System.out.println(Thread.currentThread().getName());
        }
    }
}

加锁的三种方式

  • 普通方法加锁:此时锁是调用方new的对象。
  • 静态方法加锁:此时锁是xxx.Class对象。
  • 代码块加锁:此时锁是lock对象。

volatile关键字

    volatile:使一个变量再多个线程中可见。

使用场景:线程AB都是用a变量,会在各自的线程中保留一份copy,当A线程改线a变量的值时,B线程是无法知道的。

使用vloatile关键字,会让所有线程都读到变量的修改值

package thread;

import java.util.concurrent.TimeUnit;

public class T05_Volatile {
    boolean running=true;
    void m(){
        System.out.println("m start");
        while (running){

        }
        System.out.println("m end");
    }

    public static void main(String[] args) {
        T05_Volatile t=new T05_Volatile();
        new Thread(t::m,"t1").start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.running=false;
    }
}

输出结果

加上volatile后

 volatile boolean running=true;

输出结果为 

使用volatile后,会强制所有线程去堆中读取running的值,但是volatile只能保证可见性,不能保证原子性;synchronized 既能保证可见性,也能保证原子性。

ReentrantLock

reetrantLock通俗理解就是手工锁,与synchronized的区别是reetrantLock是手动上锁,手动解锁,而synchronized是自动上锁,自动解锁,reetrantLock要更加灵活一些。注意的是,使用reetrantLock时必须手动释放锁。

lock()--获得锁

package thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class T06_Lock {
    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock();
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
    void m2(){
        lock.lock();
        System.out.println("m2 exe...");
        lock.unlock();
    }

    public static void main(String[] args) {
        T06_Lock t=new T06_Lock();
        new Thread(t::m1).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(t::m2).start();
    }
}

      trylock(5,TimeUnit.SECONDS)--尝试获得锁

等待一段时间后获得锁,若失败,则返回false

    /*使用trylock进行尝试锁定,不管锁定与否,
    方法豆浆继续执行,可以根据trylock的返回值来判定是否锁定,
    也可以指定tryLock的时间,由于trylock(time)抛出异常,
    所以要注意unlock的处理, 必须放到finally中*/
    void m2(){
        boolean locked=lock.tryLock();
        System.out.println("m2 locked");
        if(locked)lock.unlock();
        locked=false;
       try {
           //等待5s后捕获锁,若失败则返回false
           locked=lock.tryLock(5,TimeUnit.SECONDS);
           System.out.println("m2 locked");
       } catch (InterruptedException e) {
           e.printStackTrace();
       }finally {
           if(locked)lock.unlock();
       }

    }

lockInterruptibly()--可以对现成的interrupt()方法做出响应,在线程在等待锁的过程中可以被打断。

package thread;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class T07_LockInterrupter {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            try {
                lock.lock();
                System.out.println("t1 start...");
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                System.out.println("interrupted");
            } finally {
                lock.unlock();
            }
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            try {
//                lock.lock();
                lock.lockInterruptibly();
                System.out.println("t2 start...");
                TimeUnit.SECONDS.sleep(5);
                System.out.println("t2 end...");
            } catch (InterruptedException e) {
                System.out.println("interrupted");
            } finally {
                lock.unlock();
            }
        });
        t2.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.interrupt();//打断t2线程的等待
    }
}

线程t1长时间睡眠,t2无法获取锁,调用interrupt()方法,t2线程等待结束,输出结果。

   Lock lock=new ReentrantLock(true)----将锁指定为公平锁

package thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用ReentrantLock是可以指定锁为公平锁
 */
public class T08_Lock extends  Thread{
    private static Lock lock=new ReentrantLock(true);//将锁指定为公平锁

    @Override
    public void run() {
        for(int i=0;i<5;i++){
            lock.lock();
            System.out.println(Thread.currentThread().getName()+" get lock");
            lock.unlock();
        }
    }


    public static void main(String[] args) {
        T08_Lock l=new T08_Lock();
        Thread t1=new Thread(l);
        Thread t2=new Thread(l);
        t1.start();
        t2.start();
    }
}

输出结果

总结

  • .reentrantlock可以完成synchronized的功能,可以替代synchronized
  • 2.比synchronized灵活
  • 3.可以使用lockinterruptibly 打断锁,
  • 4.还可以把锁指定为公平锁
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值