18.多线程(2)

1.线程中常用的方法

      (1)static Thread currentThread() 得到当前正在运行的线程对象

      (2)void start() 启动线程

      (3)String getName()返回该线程的名称。

            1.当没有设置线程名称的时候,系统会赋予线程一个默认的名称“Thread-0,Thread-1......”

            2.主线程【主方法的执行线程】的名称默认是“main”

      (4)void   setName(String name)设置线程名称

例:

public class MyThread implements Runnable{

   public void run() {

      for(int i=1;i<=10;i++){

          String name = Thread.currentThread().getName();

          System.out.println(name+",i=="+i);

      }    

   } 

}

public class main {

   public static void main(String[] args) {

      //主方法执行线程的名称

      String name = Thread.currentThread().getName();

      System.out.println("主方法线程名称"+name);

      MyThread myThread = new MyThread();

      Thread thread1 = new Thread(myThread);

      Thread thread2 = new Thread(myThread);

      thread1.setName("线程1");

      thread2.setName("线程2");

      thread1.start();

      thread2.start();

   }

}

 

2.线程的优先级

      线程的优先级---就是线程的执行先后,默认情况下所有线程的优先级都是一样,都是等级5。

      通过void    setPriority(int newPriority) 更改线程的优先级。

(1)线程的优先级有10个级别,分别使用整数1~~10来表示,数字越大优先级越高。

(2)为了方便操作,java将10个级别规定成3个级别,分别是最低的优先级中等优先级最高的优先级,并且将这3个级别封装成了静态常量。

  1. static int      MAX_PRIORITY 线程可以具有的最高优先级---10
  2. static int      NORM_PRIORITY分配给线程的默认优先级---5
  3. static int      MIN_PRIORITY线程可以具有的最低优先级---1
  4. int  getPriority() 返回线程的优先级

(3)设置线程的优先级的时候,数字越大优先级越高,数字越小优先级越低。优先级越高并不代表一定会优先执行,只是被优先执行的几率增大,因此不要试图通过控制线程的优先级,来保证某一个线程,总是第一个执行。

例:

public class main {

   public static void main(String[] args) {

      //主方法执行线程的名称

      String name = Thread.currentThread().getName();

      int priority = Thread.currentThread().getPriority();

      System.out.println("主方法线程名称"+name+",主方法优先级=="+priority);

      MyThread myThread = new MyThread();

      Thread thread1 = new Thread(myThread);

      Thread thread2 = new Thread(myThread);

      thread1.setName("线程1");

      thread2.setName("线程2");

      System.out.println("线程1优先级=="+thread1.getPriority());

      System.out.println("线程2优先级=="+thread2.getPriority());

      thread1.setPriority(10);

      thread2.setPriority(Thread.MIN_PRIORITY);

      System.out.println("设置后线程1优先级=="+thread1.getPriority());

      System.out.println("设置后线程2优先级=="+thread2.getPriority());

      thread1.start();

      thread2.start();

   }

}

 

3.用户线程和守护线程

      用户线程----通常情况之下我们所创建的线程都是普通线程,非守护线程,也叫用户线程。

      守护线程----也叫精灵线程,当所有用户线程都执行完毕以后,自动结束运行的线程就是守护线程。【共死】特征:当所有用户线程都执行完毕以后,无论守护线程能否可以继续运行,都要立刻停止运行。

      方法:

      1.boolean   isDaemon() 测试该线程是否为守护线程。

      2.void   setDaemon(boolean on) 将该线程标记为守护线程。

public class main {

   public static void main(String[] args) {

      MyThread myThread = new MyThread();

      Thread thread1 = new Thread(myThread,"用户线程1");

      Thread thread2 = new Thread(myThread,"用户线程2");

      Thread thread3 = new Thread(myThread,"守护线程");

      thread3.setDaemon(true);

      thread1.start();

      thread2.start();

      thread3.start();

   }

}

 

4.线程休眠、中断、强制执行

(1)static  void    sleep(long millis) 设置线程休眠【暂停】指定的时间【毫秒】

例:

public class MyThread implements Runnable{



   public void run() {

      //得到当前线程的名称

     

      String name = Thread.currentThread().getName();

      System.out.println(name+"开始听讲");

      //static  void  sleep(long millis) 设置线程休眠【暂停】指定的时间【毫秒】

      try {

          Thread.sleep(3000);//休眠可能会被打断,会进入异常,try...catch

      } catch (InterruptedException e) {

          e.printStackTrace();

      }

      System.out.println(name+"开始睡着");

      try {

          Thread.sleep(10000);

      } catch (InterruptedException e) {

          System.out.println(name+"被老师一巴掌扇醒");

          System.out.println(name+"又开始听讲");

      }

      try {

          Thread.sleep(5000);

      } catch (InterruptedException e1) {

          e1.printStackTrace();

      }

      System.out.println("下课了");

   } 

}



public class main {

   public static void main(String[] args) {

      System.out.println("上课铃响");

      MyThread myThread = new MyThread();

      Thread thread1 = new Thread(myThread,"小明");

      thread1.start();

      System.out.println("老师开始上课");

      try {

          Thread.sleep(5000);

      } catch (InterruptedException e) {

          e.printStackTrace();

      }

      System.out.println("老师发现小明睡着了");

      System.out.println("老师走到小明面前,扇了一巴掌");

      thread1.interrupt();

   }

}

        

上课铃响

老师开始上课

小明开始听讲

小明开始睡着

老师发现小明睡着了

老师走到小明面前,扇了一巴掌

小明被老师一巴掌扇醒

小明又开始听讲

下课了

(2)void   interrupt() 中断线程休眠【暂停】。会进入异常【InterruptedException】

例:闹钟程序

import java.io.BufferedReader;

import java.io.InputStreamReader;

import java.text.SimpleDateFormat;

import java.util.Date;

public class NaoZhong {

   public static void main(String[] args) throws Exception {

      System.out.println("设置一个闹钟时间:");

      BufferedReader input=new BufferedReader(new InputStreamReader(System.in));

      String naozhongtime = input.readLine();

      SimpleDateFormat  sdf=new  SimpleDateFormat("HH:mm:ss");

      boolean flag=true;

      while(flag){

      String nowtime=sdf.format(new Date());

      if(nowtime.equals(naozhongtime)){

          flag=false;

          }

      System.out.println(nowtime);

      Thread.sleep(1000);

      }

      System.out.println("闹钟响起");

   }



}

输出:

设置一个闹钟时间:

00:32:20

00:32:14

00:32:15

00:32:16

00:32:17

00:32:18

00:32:19

00:32:20

闹钟响起

(3)void   join(long millis)【强制线程执行】强制该线程执行millis 毫秒。如果不写参数时间,就是强制该线程执行完毕。

例:

public class MyThread implements Runnable{

   public void run() {

      for(int i=1;i<=10;i++){

          String name = Thread.currentThread().getName();

          System.out.println(name+",i=="+i);

      }    

   } 

}



public class main {

   public static void main(String[] args) {

      MyThread myThread = new MyThread();

      Thread thread1 = new Thread(myThread);

      thread1.setName("线程1");

      thread1.start();

      for(int i=1;i<=10;i++){

          String name = Thread.currentThread().getName();

          System.out.println(name+",i=="+i);

          if(i==3){

             try {

                thread1.join();

             } catch (InterruptedException e) {

                e.printStackTrace();

             }

          } 

      }

   }

}

 

5.线程的生命周期

      线程的生命周期就是线程从一开始创建,到run方法执行完毕以后的状态变化。【状态之间的切换】

线程的生命周期5种状态【新建状态 、就绪状态 、运行状态 、阻塞状态 、死亡状态】

5.1 新建状态

      通过new的方式创建出线程对象,此时线程就进入到创建状态【新建状态】。新建状态的线程是不能运行。新建状态的线程调用start方法,就进入就绪状态。

5.2 就绪状态

线程具备运行能力,只差操作系统【CPU】分配给他运行时间片【万事具备,只欠时间片】,得到操作系统【CPU】分配给他运行时间片,此时开始执行run方法,进入运行状态。

5.3 运行状态

线程运行run方法。

(1)回到就绪状态

操作系统【CPU】分配给他运行时间片使用完毕,回到就绪状态

(2)进入阻塞状态

  1. 运行状态的线程执行了sleep方法,进入阻塞状态
  2. 运行状态的线程执行了wait方法,进入阻塞状态
  3. 运行状态的线程执行输入/输出动作,进入阻塞状态

(3)进入死亡状态

        运行状态的线程run方法执行完毕,进入死亡状态

        运行状态的线程调用stop()/destroy() ,进入死亡状态

5.4 阻塞状态

        线程暂停运行。

       阻塞状态结束后【结束了造成阻塞的原因】,此时线程进入就绪状态,得到操作系统【CPU】分配给他运行时间片就可以进入运行状态。

       结束阻塞状态:

  1. 运行状态的线程执行了sleep方法,进入阻塞状态,休眠时间结束interrupt,进入就绪状态。
  2. 运行状态的线程执行了wait方法,进入阻塞状态,调用notify/notifyAll,进入就绪状态。
  3. 运行状态的线程执行输入/输出动作,进入阻塞状态,输入/输出结束,进入就绪状态。

5.5 死亡状态

      线程运行结束,释放运行资源。

      死亡状态的线程是不能运行,除非再一次使用start方法重新启动运行。

 

6. 线程同步【线程安全】

public class MyThread implements Runnable{

   private int piao=5;

   public void run() {

      //得到线程名称

      String name = Thread.currentThread().getName();

      //持续买票

      boolean flag=true;

      while(flag){

          //判断有没有可以卖的票

          if(piao>0){

             //收钱-找钱-打印票,用休眠时间代替

             try {

                Thread.sleep(2000);

             } catch (InterruptedException e) {

                e.printStackTrace();

             }

             System.out.println(name+",卖出1张票,还剩"+(--piao)+"张");

          }else{

             flag=false;

          }

      }

   }

}

public class Main {

   public static void main(String[] args) {

      MyThread myThread = new MyThread();

      Thread thread1 = new Thread(myThread,"窗口1");

      Thread thread2 = new Thread(myThread,"窗口2");

      Thread thread3 = new Thread(myThread,"窗口3");

      thread1.start();

      thread2.start();

      thread3.start();

   }

}

窗口1,卖出1张票,还剩4

窗口2,卖出1张票,还剩3

窗口3,卖出1张票,还剩4

窗口3,卖出1张票,还剩1

窗口1,卖出1张票,还剩2

窗口2,卖出1张票,还剩2

窗口3,卖出1张票,还剩0

窗口1,卖出1张票,还剩-2

窗口2,卖出1张票,还剩-1

        为什么会出现负数呢?分析:当窗口3卖最后一张票的时候,在收钱打印票的时候,还没来得及对票数进行减1,线程就切换给了窗口1,此时窗口1认为还有1张票,窗口1就收钱打印票,但也是还没来得及对票数进行减1,线程又切换给了窗口2,此时窗口2依然认为还有1张票,窗口2就收钱打印票,线程又切给了窗口3,减票剩0,切给了窗口2,减票剩-1,切给了窗口1,减票剩-2。

        经过上面运行程序的分析,我得到的结果是:当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况

        为了解决这种数据不一致的错误情况,引入线程同步

6.1 线程同步定义

      线程同步也叫线程安全,当多条线程,同时访问同一个资源的时候,每一次只能由多条线程中的其中一条访问公共资源,当这一条线程访问公共资源的时候,其他的线程都处于等待状态,不能访问公共资源,当这一条线程访问完了公共资源以后,其他线程中的一条线程才能访问公共资源,剩下的线程继续等待,等待当前线程访问结束,实现这个过程就是线程同步。【排队访问资源】

6.2 线程同步实现方式

6.2.1 Synchronized关键字---同步代码块

      格式:synchronized(同步对象){

}

  synchronized (this) {

             // 判断有没有可以卖的票

             if (piao > 0) {

                // 收钱-找钱-打印票,用休眠时间代替

                try {

                   Thread.sleep(1000);

                } catch (InterruptedException e) {

                   e.printStackTrace();

                }

                System.out.println(name + ",卖出1张票,还剩" + (--piao) + "张");

             } else {

                flag = false;

             }

          }

使用synchronized (this)将买票代码包裹住。

窗口1,卖出1张票,还剩4

窗口1,卖出1张票,还剩3

窗口1,卖出1张票,还剩2

窗口3,卖出1张票,还剩1

窗口3,卖出1张票,还剩0

        注意:同步代码块虽然可以实现买票的效果,但是它在使用的时候,需要设置一个同步对象,由于我们很多时候都不知道这个同步对象应该是谁,容易写错,造成死锁的情况。正是应为这个缺点,我们很少使用同步代码块来实现线程同步。

6.2.2 Synchronized关键字---同步方法

        同步方法的定义格式: 访问限制修饰符  synchronized  方法返回值类型 方法名称(){

}

public class MyThread2 implements Runnable {



   private int piao =5;

   // 持续买票

   boolean flag = true;



   public void run() {

      // 得到线程名称

      String name = Thread.currentThread().getName();

      while (flag) {

          sellpiao(name);

      }

   }

  

   //同步方法

   public synchronized void sellpiao(String name){

      // 判断有没有可以卖的票

      if (piao > 0) {

          // 收钱-找钱-打印票,用休眠时间代替

          try {

             Thread.sleep(1000);

          } catch (InterruptedException e) {

             e.printStackTrace();

          }

          System.out.println(name + ",卖出1张票,还剩" + (--piao) + "张");

      } else {

          flag = false;

      }

   }



}



6.2.3 通过Lock接口

      public interface Lock   是一个接口

      常用的接口方法:

      void     lock() 获得锁

      void     unlock() 释放锁。             

      由于上面的锁方法是Lock接口,我们要使用就得先创建出Lock接口对象,由于Lock是个接口不能new ,我们就得使用它的子类ReentrantLock来创建对象。

例:

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;



public class MyThread3 implements Runnable {



   private int piao = 5;

   private Lock mylock=new ReentrantLock();



   public void run() {

      // 得到线程名称

      String name = Thread.currentThread().getName();

      // 持续买票

      boolean flag = true;

      while (flag) {

          synchronized (this) {

             mylock.lock();

             // 判断有没有可以卖的票

             if (piao > 0) {

                // 收钱-找钱-打印票,用休眠时间代替

                try {

                   Thread.sleep(1000);

                } catch (InterruptedException e) {

                   e.printStackTrace();

                }

                System.out.println(name + ",卖出1张票,还剩" + (--piao) + "张");

             } else {

                flag = false;

             }

             mylock.unlock();

          }

      }

   }

}

6.3 Synchronized关键字与Lock接口的区别

Synchronized关键字

Lock接口

关键字

接口

自动锁定资源,不灵活

手动锁定资源,灵活

异常时会自动释放锁

异常时不会自动释放锁,所以需要在finally中实现释放锁

不能中断锁,必须等待线程执行完成释放锁

可以中断锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java-请多指教

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值