Java线程安全与通信

线程安全
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,即一是存在共享数据,二是存在多条线程共同操作共享数据。因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,从而保证了该变量的唯一性和准确性。
解决方法
1.synchronized
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

  1. 同步代码块
    即有synchronized关键字修饰的语句块
  2. 同步方法
    即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当 用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,
public synchronized void active(){}
  1. 同步静态方法
 public static synchronized void sendSms(){}

八锁
即关于锁的八个问题
线程八锁的关键:
A:同步代码块这里的锁对象可以是任意对象。
B:同步方法这里的锁对象是this
C:静态同步方法这里的锁对象是当前类的字节码文件对象

2.java.util.concurrent.locks包下使用重入锁(Lock)实现线程同步
1.Lock
  Lock是一个接口:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
一般格式:上锁——处理事务(try-catch)——释放锁

Lock lock = ...;
lock.lock();
try{
    //处理任务
}catch(Exception ex){
     
}finally{
    lock.unlock();   //释放锁
}

Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
ReentrantLock(可重入锁)是唯一实现了Lock接口的类

synchornized和lock 的区别

  1. synchronized是关键字,是JVM层面的底层什么都帮我们做了,而Lock是一个接口,是JDK层面的有丰富的API。
  2. synchronized会自动释放锁,而Lock必须手动释放锁(try-catch-finally模式)。
  3. synchronized是不可中断的线程,Lock可以中断线程。
  4. 通过Lock可以通过tryLock()方法知道线程有没有拿到锁,而synchronized不能。
  5. synchronized能锁住方法和代码块,而Lock只能锁住代码块。

案例卖电影票
方式一、同步方法

public class SellTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{
            for(int i=1;i<50;i++){
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for(int i=1;i<50;i++){
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for(int i=1;i<50;i++){
                ticket.sale();
            }
        },"C").start();

    }
}
class Ticket{
    private int num = 50;

    //买票方式
    public synchronized void sale() {
        if(num>0) {
            System.out.println(Thread.currentThread().getName()+"卖出了" + (num--) +"张票,剩余" + num);
        }
    }
}

方式二、使用lock

public class SellTicket2 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{for(int i=1;i<50;i++)ticket.sale();},"A").start();
        new Thread(()->{for(int i=1;i<50;i++)ticket.sale();},"B").start();
        new Thread(()->{for(int i=1;i<50;i++)ticket.sale();},"C").start();
        new Thread(()->{for(int i=1;i<50;i++)ticket.sale();},"D").start();


    }
}

class Ticket2{
    //加锁三步曲
    //1.new ReentrantLock()
    //2.lock.lock 加锁
    //3.finally lock.unlock 释放锁
    private int num = 50;

    Lock lock = new ReentrantLock();

    //买票方式
    public  void sale() {
        lock.lock();
        try {
            //业务
            if(num>0) {
                System.out.println(Thread.currentThread().getName()+"卖出了" + (num--) +"张票,剩余" + num);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
}

线程通信
1、借助于Object类的wait()、notify()和notifyAll()实现通信
线程执行wait()后,就放弃了运行资格,处于冻结状态;线程再次运行时依然再这此处复活;
线程运行时,内存中会建立一个线程池,冻结状态的线程都存在于线程池中,notify()执行时唤醒的也是线程池中的线程,线程池中有多个线程时唤醒第一个被冻结的线程。
notifyall(), 唤醒线程池中所有线程。
wait(), notify(),notifyall()都用在同步里面,因为这3个函数是对持有锁的线程进行操作,而只有同步才有锁,所以要使用在同步中;
wait(),notify(),notifyall(), 在使用时必须标识它们所操作的线程持有的锁,因为等待和唤醒必须是同一锁下的线程;而锁可以是任意对象,所以这3个方法都是Object类中的方法

单个生产者和消费者

public class PrCoDemo {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for(int i=0; i<10;i++ ){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"B").start();
    }
}
class Data {
    private int num = 0;
    public synchronized void increment() throws InterruptedException {
        if(num != 0) {
            this.wait();
        }
        //业务
        num++;
        System.out.println(Thread.currentThread().getName() + "=>" + num);
        //通知唤醒
        this.notify();
    }
    public synchronized void decrement() throws InterruptedException {
        //判断等待
        if(num == 0) {
            this.wait();
        }
        //业务
        num--;
        System.out.println(Thread.currentThread().getName() +"=>" + num);
        //通知唤醒
        this.notify();
    }
}

多个生产者和消费者

/*
线程之间的通信问题,生产者和消费者,等待唤醒,通知唤醒
线程交替执行,A、B操作同一变量 num=0
判断等待,业务、通知、
 */
public class Produtor {
    public static void main(String[] args) {
        Date date = new Date();
        new Thread(()->{ for(int i=0;i<10;i++)
            try {
                date.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A" ).start();
        new Thread(()->{for(int i=0;i<10;i++)
            try {
                date.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B" ).start();
        new Thread(()->{for(int i=0;i<10;i++)
            try {
                date.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C" ).start();
        new Thread(()->{for(int i=0;i<10;i++)
            try {
                date.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"D" ).start();



    }

}
class Date{
    private int number = 0;
    public synchronized  void increment() throws InterruptedException {
       //if判断一次
       //原先是if,现在改成while,这样消费者线程从冻结状态醒来时,还会再次判断
        while(number != 0) {
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>"+number);
        this.notifyAll();
    }
    public synchronized  void decrement() throws InterruptedException {
       while(number == 0) {
            this.wait();
        }
        number --;
        //通知其他线程,我完毕了
        System.out.println(Thread.currentThread().getName() + "=>"+number);
        this.notifyAll();//用notifyAll(),这样消费者线程消费完一个商品后可以将等待中的生产者线程唤醒,防止出现所有生产者和消费者都在wait()的情况。

    }
}

/*
A执行完调用B,B执行完调用C
*/

public class OrderConsumer {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for(int i=0;i<10;i++) {
                data.printlnA();
            }
        },"A").start();
        new Thread(() -> {
            for(int i=0;i<10;i++) {
                data.printlnB();
            }
        },"B").start();
        new Thread(() -> {
            for(int i=0;i<10;i++) {
                data.printlnC();
            }
        },"C").start();
    }
}
class Data {
    private Lock lock  = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1;

    public void printlnA() {
        //上锁
        lock.lock();
        //判断->执行
        try {
            while ( num != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>AAAAAAAA");
            //唤醒
            num = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    public void printlnB() {
        //上锁
        lock.lock();
        try {
            //判断-> 执行
            while( num != 2) {
                condition2.await();
            }
            //业务
            System.out.println(Thread.currentThread().getName() + "=> BBBBBB");
            //唤醒
            num = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    public void printlnC() {
        //上锁
        lock.lock();
        try {
            //判断执行
            while( num != 3) {
                condition3.await();
            }
            //业务
            System.out.println(Thread.currentThread().getName()+ "=> CCCCCC");
            //唤醒
            num = 1;
            condition1.signal();
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
}

sleep和wait的区别
1、来自不同的类
wait => Object
sleep => Thread
2、关于锁的释放
wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放!
3、使用的范围是不同的
wait 必须在同步代码块中
sleep 可以再任何地方睡

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值