java之线程安全、线程同步、线程状态

目录

线程安全

线程同步

线程状态

等待唤醒案例

 带参的wait和notifyAll

 


线程安全

多线程产生的问题

图解

 

代码实现:

线程类:

public class RunnableImpl implements Runnable{

   private  int ticket=100;

    @Override

    public void run() {

       while (true)

           if (ticket>0) {

               System.out.println(Thread.currentThread().getName() + "正在买第" + ticket + "张票");

               ticket--;

           }

    }

测试方法

 public static void main(String[] args) {

        RunnableImpl runnable=new RunnableImpl();

        Thread thread0=new Thread(runnable);

        Thread thread1=new Thread(runnable);

        Thread thread2=new Thread(runnable);

        thread0.start();

        thread1.start();

        thread2.start();

    }

效果:

不同的线程卖出了相同的票

该线程安全问题的解释:

3个线程一起抢夺cpu执行权,同时在执行打印第100张票,这时ticket还没执行到ticket--

特别是当if后面设置了sleep后,线程同时进入睡眠,例如线程1打印第1张票前进入睡眠,ticket还未--,线程2、3也进入打印第1张票的睡眠,线程1先醒将票1打印且--,那么当线程2、3醒的时候就会打印票-1、-2

 

 

注意:

线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权让其他的线程只能等待,等待当前线程卖完票,其他线程在进行卖票

保证:始终一个线程在买票

线程同步

1、同步代码块

2、同步方法

3、锁机制

同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){

需要同步操作的代码

}

 

注意:

1、通过代码块中的锁对象,可以使用任意的对象

2、但是必须保证多个线程使用的锁对象是同一个

3、锁对象作用:

把同步代码块锁住,只让一个线程在同步代码块中执行

 

 

Object object=new Object();

    @Override

    public void run() {

        while (true)

           synchronized (object){

               if (ticket>0) {

                   try {

                       Thread.sleep(10);

                   } catch (InterruptedException e) {

                       e.printStackTrace();

                   }

                   System.out.println(Thread.currentThread().getName() + "正在买第" +  ticket-- + "张票");

               }

           }

 

 

 

 

 

同步技术原理:先抢到cpu执行权的线程会拿走锁对象,第二线程个执行到synchronized代码块的线程会等待第一个线程执行完毕归还锁对象(进入阻塞状态),再执行synchronized代码块

 

 

保证了只能有一个线程在同步中执行共享数据,保证了安全

程序的频繁判断、获取锁、释放锁,程序的效率会降低

 

 

 

同步方法:使用synchronized修饰的方法,叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着

格式:

public synchronized void method(){

可能会产生线程安全问题的代码

}

 

卖票演示:

 

 @Override

    public void run() {

        while (true){

            //调用同步方法

            payTicket();

            }

    }

    //定义同步方法

    public synchronized void payTicket() {

        if (ticket > 0) {

            try {

                Thread.sleep(10);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println(Thread.currentThread().getName() + "正在买第" + ticket-- + "张票");

        }

    }

 

 

原理:

同步方法也会把方法内部的代码锁住

只让一个线程执行

同步方法的锁对象是谁?

就是实现类对象 new RunnableImpl()

也就是this

 

实验:

通过在main方法中打印Runnable对象和run方法中打印this得出

他们的对象是一样的,所有同步方法锁住的对象就是Runnable的实现类对象

 

所以同步方法=如下写法

synchronized (this){if (ticket > 0) {

            try {

                Thread.sleep(10);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println(Thread.currentThread().getName() + "正在买第" + ticket-- + "张票");

        }

 

静态同步方法:

注意要将使用的变量声明为静态的

public static synchronized void payTicketStatic(){

    if (ticket > 0) {

        try {

            Thread.sleep(10);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println(Thread.currentThread().getName() + "正在买第" + ticket-- + "张票");

    }

    }

 

探究锁对象是谁

这时候就不能是this了

this是对象创建之后的,静态方法优先于对象

静态方法的锁对象是本类的class属性-->class文件对象(反射)

 

lock锁:

jdk1.5之后出现在java.util.concurrent.locks.lock下

提供了比synchronized代码块和synchronized方法更加广泛的锁操作

同步代码块/同步方法具有的功能Lock都有,除此之外更加面对对象

 

lock接口中的方法:

lock()获取锁

unlock()释放锁

 

使用步骤:

1、在成员位置创建一个lock的实现类叫ReentrantLock对象

2、在可能出现安全问题前的代码调用Lock接口中的方法lock获取锁

3、在安全代码后使用unlock释放锁

 

演示:

 private  int ticket=100;

   Lock lock=new ReentrantLock();

    @Override

    public void run() {

        while (true){

            lock.lock();

                if (ticket>0) {

                    try {

                        Thread.sleep(10);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }finally {

                        lock.unlock();

                    }

                    System.out.println(Thread.currentThread().getName() + "正在买第" +  ticket-- + "张票");

                }

                }

    }

效果:

 

线程状态

概述:线程被创建之后的生命周期内有多种状态

线程状态

导致状态发生的条件

NEW(新建)

线程刚被创建未调用start

Runnable(可运行)

线程可以在虚拟机中运行的状态,可能正在运行自己的代码也可能没有,这取决于操作系统处理器

Blocked(锁阻塞)

当一个线程试图获取一个对象锁,而对象锁被其他线程持有则进入Blocked,若持有对象锁则进入Runnable

Waiting(无限等待)

一个线程在等待另一个线程执行(唤醒)动作时,该线程进入Waiting,该状态不能自动唤醒,必须等待另一个线程调用notify方法或者notifyAll方法

TimedWaiting(计时等待)

同waiting状态,有几个方法有超时参数,调用他们进入TimedWaiting状态,这一状态一直到超时期满或被唤醒,常用方法有sleep

Terminated(死亡状态)

因为run方法正常退出或遇到未处理的异常,终止了run方法而死亡

 

注意:1、wait、notify和notifyAll都是Object类的方法,索引锁对象可以是任意对象

2、且notify通知后并不是立马恢复执行,需要等待再次获得锁

3、wait和notify需要由同一个锁对象调用,因为对应的锁对象可以通过notify来唤醒同一个锁下的wait线程

4、wait与notify必须要在同步代码块或是同步函数中使用,因为:必须要通过锁对象来调用这两个方法

 

等待唤醒案例

/*

等待唤醒案例:线程之间的通信

需求老板卖包子,顾客来买包子

顾客告知老板包子种类和数量,调用wait方法,放弃cpu执行进入waiting

老板线程花5s做包子,做好后调用notify唤醒顾客吃包子

注意:

    顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只有一个在执行

    同步使用的锁对象必须保证唯一

    只有锁对象才能调用wait和notify

*/

public class DemoWaitAndNotify {

    public static void main(String[] args) {

        //锁对象

        Object obj=new Object();

        //顾客线程

        new Thread(){

            @Override

            public void run() {

               while (true){

                   //同步代码块保证等待和唤醒的线程只有一个

                   synchronized (obj){

                       System.out.println("告知老板包子的种类和数量");

                       try {

                           obj.wait();

                       } catch (InterruptedException e) {

                           e.printStackTrace();

                       }

                       //唤醒之后的代码

                       System.out.println("包子已经做好了,开食");

                       System.out.println("-------------------");

                   }

               }

            }

        }.start();

        //老板线程

        new Thread(){

            @Override

            public void run() {

              while (true){

                  try {

                      Thread.sleep(5000);//5s做包子

                  } catch (InterruptedException e) {

                      e.printStackTrace();

                  }

                  synchronized (obj){

                      System.out.println("5s后告知顾客吃包子");

                      obj.notify();

                  }

              }

            }

        }.start();

    }

}

由这个案例学到的东西:synchronized代码的锁对象是保证同一时间只能有一个线程执行,当先执行的线程调用obj.wait之后该线程就进入了停止状态,老板线程就可以进行工作了老板线程再调用obj.notify后唤醒顾客线程,让顾客吃包子

 带参的wait和notifyAll

进入到timeWaiting(计时等待)有两种方式

1、使用sleep方法,在毫秒值结束后,线程睡醒进入到Runnable/Block状态

2、使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没被notify唤醒,就会自动醒

 

带参wait案例(上回的代码,删除了老板线程):

  //锁对象

        Object obj=new Object();

        //顾客线程

        new Thread(){

            @Override

            public void run() {

               while (true){

                   //同步代码块保证等待和唤醒的线程只有一个

                   synchronized (obj){

                       System.out.println("告知老板包子的种类和数量");

                       try {

                           obj.wait(5000); 5s后自动唤醒吃包子

                       } catch (InterruptedException e) {

                           e.printStackTrace();

                       }

                       //唤醒之后的代码

                       System.out.println("包子已经做好了,开食");

                       System.out.println("-------------------");

                   }

               }

            }

        }.start();

 

 

notifyAll()唤醒在此对象监视器上等待的所有线程

 

案例:

这次有两个顾客,老板还是上回的代码

使用notify方法唤醒

效果:每次只有一个顾客开食

换成notifyAll后

顾客1和顾客2同时开食

总结:notify是有多个线程时随机唤醒一个线程

notifyAll是唤醒全部

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值