其他分享
首页 > 其他分享> > 多线程训练任务

多线程训练任务

作者:互联网

1、线程和进程

进程是指一个在内存中运行的应用程序,每一个进程的内存空间是独立的。可以理解成每个进程都有各自的堆、栈区,并且默认下它们的内存空间是不共享的。

线程是指进程中的执行路径,多线程指的是一个进程中的多条执行路径,线程之间的内容是可以共享的,线程之间可以自由切换、并发执行。值得注意的是,一个进程最少有一个线程。

线程可以看做是进程的一个更小划分,而线程的并发执行并不是说多个线程同时运行,而是由于 CPU 具备分时机制,所以每个线程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有线程好像是在同时运行一样。

线程的作用是可以让CPU同时进行多件事情,其执行依靠线程调度。有:

分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间;

抢占式调度:优先让优先级高的线程使用CPU,Java使用的就是这种调度,但并不是说优先级最高的一定先执行,只能说优先级高的线程优先执行的概率更大。

多线程并不是提高了CPU的执行速度,而是提高了CPU的使用效率。

2、同步与异步

直白理解同步是排队进行,异步是同时进行,而同步效率低但是安全,异步效率高但是不安全。

并发是指多个事件在指定时间段内发生,并行是指多个事件在同一时刻发生。

3、创建线程

有三种方式

1)方式1:继承Thread方法

第一步:声明子线程类并继承Thread方法,重写run方法,在run方法体中给定要执行的任务;

第二步:new子线程对象,创建子线程任务;

第三步:调用start方法启动线程任务。

代码示例:

 public class ThreadDemo {
​     public static void main(String[] args) {
​         //创建线程任务,不指定线程名
​         MyThread m1 = new MyThread();
​         //设置线程名,也可以不设置
​         m1.setName("这是指定的字线程名");
​         //启动子线程
​         m1.start();
​         //主线程循环
​         for  (int i = 0; i < 5; i++) {
​             //获取当前线程名并输出对应数字
​             System.out.println(Thread.currentThread().getName()+i);
​             try {
​                 Thread.sleep(200);
​             }catch (InterruptedException e) {
​                 e.printStackTrace();
​             }
​         }
​     }
 }
class MyThread extends Thread{
​     @Override
​     public void run()  {
​         for (int i = 0; i < 5; i++) {
​             System.out.println(Thread.currentThread().getName()+i);
​             try {
​                 //等待200毫秒
​                 Thread.sleep(200);
​             } catch (InterruptedException e) {
​                 e.printStackTrace();
​             }
​         }
​     }
 }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRnh5VFh-1631103914043)(file:///C:\Users\Dell\AppData\Local\Temp\msohtmlclip1\01\clip_image002.jpg)]

2)方式2:实现Runnable接口(常用)

第一步:声明子线程类并实现Runnable接口,重写run方法,在run方法体中说明要执行的任务;

第二步:new子线程对象,创建任务;

第三步:创建Threa线程对象,并传入任务,同时可给定子线程名称;

第四步:调用start方法启动线程任务。

public class ThreadDemo {
​     public static void main(String[] args) {
​       //创建线程任务
​        MyThread m1 = new MyThread();
​       //创建线程对象,传入任务并设置线程名,也可以不设置线程名
​       Thread t1 = new Thread(m1,"子线程名");
​       //启动线程
​       t1.start();
​       //主线程循环
​       for (int i = 0; i < 5; i++) {
​          //获取当前线程名并输出对应数字
​          System.out.println(Thread.currentThread().getName()+i);
​          try {
​           Thread.sleep(200);
​          }catch (InterruptedException e) {
​           e.printStackTrace();
​          }
​         }
​     }
 }
class MyThread implements Runnable{
​     @Override
​     public void run() {
​         for (int i = 0; i < 5; i++) {
​             System.out.println(Thread.currentThread().getName()+i);
​             try {
​                 //等待200毫秒
​                 Thread.sleep(200);
​             } catch (InterruptedException e) {
​                 e.printStackTrace();
​             }
​         }
​     }
 }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cvLeoqSw-1631103914053)(file:///C:\Users\Dell\AppData\Local\Temp\msohtmlclip1\01\clip_image004.jpg)]

3)方式3:带返回值的callable(基本不用)

第一步:定义callable实现类并重写call方法;

第二步:使用FutureTask类创建任务并传入callable实现类对象;

第三步:创建Thread线程并传入任务;

第四步:启动子线程

 import java.util.concurrent.*;
 public class CallaDemo {
​     public static void main(String[] args) {
​         Callable<Integer> c = new Callable<Integer>() {
​             @Override
​             public Integer call() throws Exception {
​                 Thread.sleep(1000);
​                 System.out.println("子线程在执行");
​                 return 100;
​             }
​         };
​         FutureTask<Integer> task = new FutureTask<>(c);
​         new Thread(task).start();
​          try {
​             try{
​              int num = task.get(2000, TimeUnit.MILLISECONDS);
​             } catch (TimeoutException e) {
​                 e.printStackTrace();
​              }
​         } catch (InterruptedException e) {
​             e.printStackTrace();
​         } catch (ExecutionException e) {
​             e.printStackTrace();
​         }
​         System.out.println("主线程在执行");
​     }
 }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3bEPe90D-1631103914056)(file:///C:\Users\Dell\AppData\Local\Temp\msohtmlclip1\01\clip_image006.jpg)]

在main方法中,如果要接受callable的返回值,那么主线程就会等待子线程执行完或者返回异常才继续往下执行。

如果主线程需要子线程返回值又不想等待,则可以调用isDone方法来判断子线程是否结束。

4)继承Threa类和实现Runnable的区别

二者本质都是通过调用start方法启动线程。然后自动执行run方法。但是继承只能是单继承,不利于扩展,而实现接口可以实现多个接口,更加利于扩展。

4、线程锁

线程锁主要是为了解决多线程公用的堆内存的数据错乱问题。

分为显示锁和隐式锁,显示锁通过类ReentrantLock实现,在所要同步的代码块首尾分别调用lock() 和unlock() 方法进行上锁和解锁。而隐式锁则是使用synchronized来进行同步,下面对其重点学习。

一个典型的卖票案例,不使用线程锁时,代码为:

public class LockDemo {
   public static void main(String[] args) {
​     Myrun m1 = new Myrun();
​     new Thread(m1,"买票通道1").start();
​     new Thread(m1,"买票通道2").start();
​     new Thread(m1,"买票通道3").start();
   }
 }
 class Myrun implements Runnable{
   int ticket = 5;
   @Override
   public void run() {
​     for (int i=0;i<10;i++) {
​       if(ticket > 0) {
​         System.out.println(Thread.currentThread().getName() + "正在准备出票,请稍后.......");
​         try {
​           Thread.sleep(1000);
​         } catch (InterruptedException e) {
​           e.printStackTrace();
​         }
    	  System.out.println(Thread.currentThread().getName() + "出票成功!余票:" + --ticket);
​       }
​     }
   }
 }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6J66yDxT-1631103914061)(file:///C:\Users\Dell\AppData\Local\Temp\msohtmlclip1\01\clip_image008.jpg)]

由结果可以看到,即使有对票数的判断,但依然出现了余票为负的情况,原因便是多线程中某个线程还未完成,其其他线程已进行,从而导致对数据的读取错误。解决该问题的一个好办法就是加上线程锁。

就是将需要同步的代码加上锁,使得只有一个线程可以进入对应代码中。

同样有两种方法,如下:

1) 使用同步代码块:

public class LockDemo {
   public static void main(String[] args) {
​     Myrun m1 = new Myrun();
​     new Thread(m1,"买票通道1").start();
​     new Thread(m1,"买票通道2").start();
​     new Thread(m1,"买票通道3").start();
   }
 }
 class Myrun implements Runnable{
   private int ticket = 6;
   @Override
   public void run() {
​     while(true) {
​       synchronized (this) {
​         if (this.ticket > 0) {
​           try {
​             Thread.sleep(500);
​           } catch (InterruptedException e) {
​             e.printStackTrace();
​           }
   			System.out.println(Thread.currentThread().getName() + "出票成功!余票:" + --ticket);
​         }
​         else{
​           break;
​         }
​       }
​     }
   }
 } 

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qk6mGevb-1631103914064)(file:///C:\Users\Dell\AppData\Local\Temp\msohtmlclip1\01\clip_image010.jpg)]

2)使用同步方法

该方法实际上就是将同步代码块中的代码封装在一个方法中,如下:

public class LockDemo {   public static void main(String[] args) {
​     Myrun m1 = new Myrun();
​     Thread t1 = new Thread(m1,"买票通道1");
​     Thread t2 = new Thread(m1,"买票通道2");
​     Thread t3 = new Thread(m1,"买票通道3");
​     //设置线程优先级
​     t1.setPriority(Thread.MIN_PRIORITY);
​     t2.setPriority(Thread.MAX_PRIORITY);
​     t3.setPriority(Thread.MAX_PRIORITY);
​     t3.start();
​     t1.start();
​     t2.start();
   }
 }
 class Myrun implements Runnable{
   private int ticket = 6;
   boolean flag = true;
   @Override
   public void run() {
​     while(flag) {
​       flag = sale();
​     }
   }
 public synchronized boolean sale(){
​     if (this.ticket > 0) {
​       try {
​         Thread.sleep(500);
​       }catch (InterruptedException e) {
​         e.printStackTrace();
​       }
​       System.out.println(Thread.currentThread().getName() + "出票成功!余票:" + --ticket);
​       return true;
​     }else{
​       return false;
​     }
   }
 }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ADoFp9bI-1631103914067)(file:///C:\Users\Dell\AppData\Local\Temp\msohtmlclip1\01\clip_image012.jpg)]

3)死锁

死锁是指两个或两个以上的线程在执行同步代码时,同时需要等待对方的同步代码块结束,这样线程就被堵住了,不能再继续往下执行。

5、线程状态

创建(new Thread) -> 就绪(线程已启动,将进入线程队列排队,等待CPU服务)->运行(线程被调用并获得处理器资源)->堵塞(线程被挂起,将染出CPU并暂时终止自己的执行,如调用suspend()、wait()、sleep()方法时,线程堵塞时,不能进入排队队列,只有当堵塞的原因被清楚后才可以转入就绪状态,如被唤醒: notify();notifyAll();)->终止(线程运行结束,不可再继续运行)。

6、线程的中断

过时的中断方法为stop,但是现在不采用该方法,原因是如果外部直接中断某线程,可能会导致该线程所占用的资源来不及释放,甚至引发异常。中断线程的正确操作是给线程加入一个中断标记,interrupt();然后可以在异常捕捉代码块中加入处理,可以结束线程或者进行其它操作,代码示例:

public class ThreadDemo {
public static void main(String[] args) {
​    //创建线程任务
​    MyThread m1 = new MyThread();
​    //创建线程对象,传入任务并设置线程名,也可以不设置线程名
​    Thread t1 = new Thread(m1,"子线程名");
​    //启动线程
​    t1.start();
​    //主线程循环
​     for (int i = 0; i < 5; i++) {
​      //获取当前线程名并输出对应数字
​      System.out.println(Thread.currentThread().getName()+i);
​      try {
​        Thread.sleep(200);
​      } catch (InterruptedException e) {
​        e.printStackTrace();
​      }
​    }
​    //给线程t1加入中断标记
​    t1.interrupt();
  }
 }
 class MyThread implements Runnable{
  @Override
     public void run() {
​    for (int i = 0; i < 10; i++) {
​      System.out.println(Thread.currentThread().getName()+i);
​      try {
​        //等待200毫秒
​        Thread.sleep(200);
​      } catch (InterruptedException e) {
​        System.out.println("发现异常标记,线程结束");
​        break;
​      }
​    }
  }
 }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rmSkJheJ-1631103914068)(file:///C:\Users\Dell\AppData\Local\Temp\msohtmlclip1\01\clip_image014.jpg)]

7、线程池

Java的线程池有4种:缓存线程池、定长线程池、单线程池、周期定长线程池

1)缓存线程池:

长度可以变,先判断线程池是否存在空闲线程,存在则使用,不存在则创建:

 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 public class ThreadPoolDemo {
   public static void main(String[] args) {
​     ExecutorService ecs = Executors.newCachedThreadPool();
​     ecs.execute(new Runnable() {
​       @Override
​       public void run() {
​         System.out.println(Thread.currentThread().getName()+"天王盖地虎");
​       }
​     });
​     ecs.execute(new Runnable() {
​       @Override
​       public void run() {
​         System.out.println(Thread.currentThread().getName()+"宝塔镇河妖");
​       }
​     });
​     try {
​       Thread.sleep(1000);
​     }catch (InterruptedException e) {
​       e.printStackTrace();
​     }
​     ecs.execute(new Runnable() {
​       @Override
​       public void run() {
​         System.out.println(Thread.currentThread().getName()+"清明上河图");
​       }
​     });
   }
 }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hda3vKVV-1631103914070)(file:///C:\Users\Dell\AppData\Local\Temp\msohtmlclip1\01\clip_image016.jpg)]

如结果,在执行第三个线程任务时,使用的是线程池中原有的pool-1-thread-2线程。

2)定长线程池:

线程池长度固定,执行任务时,有空闲线程则执行,否则等待。

创建方法为:

ExecutorService ecs = Executors.newFixedThreadPool(2);

3)单线程线程池:

线程池中只有一个线程,创建方法为:

ExecutorService ecs = Executors.newSingleThreadExecutor();

4)周期定长线程池

有两种模式,一种是定时执行一次,一种是周期性执行不停任务,如下:

import java.util.concurrent.*;
 public class ThreadPoolDemo {
   public static void main(String[] args) {
​     ScheduledExecutorService secs = Executors.newScheduledThreadPool(2);
​     /**定时执行一次:
​     \* 参数1:定时执行的任务
​     \* 参数2:延时时间数
​     \* 参数3:时间单位
​     */
​     secs.schedule(new Runnable() {
​       @Override
​       public void run() {
​         System.out.println("天王盖地虎");
​       }
​     },3,TimeUnit.SECONDS);
​     /**周期执行任务
​     \* 参数1:任务
​     \* 参数2:第1次执行时延迟时间
​     \* 参数3:周期时间数量
​     \* 参数4:时间单位
​     */
​     secs.scheduleAtFixedRate(new Runnable() {
​       @Override
​       public void run() {
​         System.out.println("清明上河图");
​       }
​     },5,1,TimeUnit.SECONDS);
   }
 }

*/
​ secs.schedule(new Runnable() {
​ @Override
​ public void run() {
​ System.out.println(“天王盖地虎”);
​ }
​ },3,TimeUnit.SECONDS);
​ /**周期执行任务
​ * 参数1:任务
​ * 参数2:第1次执行时延迟时间
​ * 参数3:周期时间数量
​ * 参数4:时间单位
​ */
​ secs.scheduleAtFixedRate(new Runnable() {
​ @Override
​ public void run() {
​ System.out.println(“清明上河图”);
​ }
​ },5,1,TimeUnit.SECONDS);
}
}


标签:训练任务,Thread,void,线程,println,new,多线程,public
来源: https://blog.csdn.net/weixin_42250963/article/details/120188083