Java--多线程

Java–多线程

1.对多线程的基础理解

        线程是进程执行的一个路线。在同一个进程中的,线程共享进程的内存空间,线程间可以自由切换,并发执行。在Java开发的过程中,设计的应用程序,往往都是多个线程同时工作的,这样能够提高程序的执行效率,但是也带来了编程的复杂,需要解决多线程之间资源分配的问题。在本篇的博客中,主要记录的是Java实现多线程的几个方式,以及基础的概念和常用的方法操作。

2.创建线程的方式

  1. 继承Thread类,重写run方法

  2. 实现Runnable接口,编写run方法

  3. 实现Callable接口,编写call方法

    1) 继承Threead的方式,参考例子如下:

    // 定义MyThread继承Thread类型,并重写run方法,在run方法内编写创建的线程要执行的任务。 public class MyThread extends Thread {     // 线程要执行的方法     // 通过thread对象的start方法执行,是一条新的执行路径     @Override     public void run() {         for (int i=0;i<100;i++){             System.out.println("窗前明月光"+i);         }     } } 
    // 创建MyThread类,并调用start方法,然后线程会开始执行run方法 public static void main(String[] args) {     MyThread myThread = new MyThread();     myThread.start();     for (int i=0;i<100;i++){				         System.out.println("疑是地上霜"+i);     } } 

    通过上面运行的程序可以观察到,myThread执行的任务和主线程打印任务是有交叉执行的,说明Java中线程之间的运行是抢占式的,线程抢到资源后,可以执行,没有抢到足够资源的线程需要等待下一次的抢占。

    2)实现Runnable的方式,可以参考下面的例子

    // 定义MyRunnable实现Runnable接口,并实现run方法,run方法内编写线程执行的任务。 public class MyRunnable implements Runnable {     @Override     public void run() {         for (int i=0;i<20;i++){             System.out.println("锄禾日当午"+i);         }     } } 
    // 将MyRunnable对象作为创建Thread对象的参数,然后调用start方法执行线程 public static void main(String[] args) {     MyRunnable myRunnable = new MyRunnable();     Thread thread = new Thread(myRunnable);     thread.start();     for (int i=0;i<20;i++){         System.out.println("汗滴禾下土"+i);     } } 

    特点:

    1. 通过创建任务,给线程分配任务,更适合多个线程同时执行相同的任务; 2. 可以避免单继承的局限性; 3. 任务和线程本身是分离,提高程序的健壮性 4. 线程池的操作,只支持实现Runnable接口的类型的任务,不接受继承Thread的线程 

3)实现Callable方法

public static void main(String[] args) throws ExecutionException, InterruptedException {     // 1. 创建MyCallable对象     MyCallable myCallable = new MyCallable();     // 2. 创建FutureTask对象,并将MyCallable对象作为创建对象的参数     FutureTask<Integer> futureTask = new FutureTask<>(myCallable);     // 3. 调用Thread对象的start方法执行线程     new Thread(futureTask).start();   // 执行线程     // 4. 可以通过get方法获取线程执行完成时的返回值     System.out.println(futureTask.get()); // 输出任务返回值 } 

特点: 可以携带线程任务执行完成后返回的参数

3. 线程的一些常用方法

  1. 设置和获取线程的名字

    // 设置当前线程的名字 Thread.currentThread().setName("线程1"); // 获取当前线程的名字 Thread.currentThread().getName(); 
  2. 线程休眠函数

    Thread.sleep(1000);   // 单位是ms 
  3. 线程中断

    // 终端线程执行可以调用interrupt()函数,通过中断可以终止执行的线程 public static void main(String[] args) throws InterruptedException {     Thread t1 = new Thread(new MyRunnable());     t1.start();     for (int i=0;i<5;i++){         System.out.println(Thread.currentThread().getName()+":"+i);         Thread.sleep(1000);     }     t1.interrupt();         // 中断t1线程,通知他进行其他操作,可以是终止自身线程,或者是进行其他的操作 }  static class MyRunnable implements Runnable{      @Override     public void run() {         for (int i=0;i<10;i++){             System.out.println(Thread.currentThread().getName()+":"+i);             try {                 Thread.sleep(1000);             } catch (InterruptedException e) {                 //   e.printStackTrace();                 // 接收到中断信号时,终止任务/或者开始其他的任务                 System.out.println("接收到中断信号时,终止任务");                 return;		// 终止任务             }         }     } } 
  4. 设置守护线程

    通过调用线程对象的setDaemon()方法,并传入true可以设置线程为守护线程

    Java中的线程可以分成用户线程和守护线程,下面是这两种线程的特点;
    用户线程,当进程中没有一个用户线程的时候,进程结束
    守护线程,守护用户线程,当最后一个用户线程结束时,所有的守护线程死亡

    举例如下:

    public static void main(String[] args) throws InterruptedException {         Thread t1 = new Thread(new MyRunnable());         t1.setDaemon(true);				// 设置t1为守护线程,如果所有的用户线程结束了,t1守护线程也会停止执行         t1.start();         for (int i=0;i<5;i++){			// 比较快结束             System.out.println(Thread.currentThread().getName()+":"+i);             Thread.sleep(1000);         }     }     static class MyRunnable implements Runnable{         @Override         public void run() {             for (int i=0;i<10;i++){		// 在所有用户线程结束时,作为守护线程的它也会结束                 System.out.println(Thread.currentThread().getName()+":"+i);             }         }     } 

4.线程同步

​ 在多线程的环境下,线程的执行往往是多样的,当这多个线程在同时对一个数据进行更新操作的时候,可能会因为同时操作的原因,导致数据出现错误,不满足实际的情况。在这种情况下,如果需要保证数据的安全可靠,就要是每个对数据进行更新的线程排队执行,这样才能保证数据不会超出合理的范围,保证每次操作的合理性。这种排队的行为是线程同步的一种具体解释.

Java中为了保证线程安全,使线程同步执行的方式有以下三种:

  1. 同步代码块

  2. 同步方法

  3. 显示锁Lock

下面详细介绍这三种的使用方法,并给出一些例子:

首先给出一个会出现线程不安全的情况,这个例子演示的是多个线程同时进行买票,最后会出现剩余票数是负数的情况,这是不合理的现象。示例如下:

// 模拟线程不安全的情景:买票线程 public static void main(String[] args) {     Ticekt ticekt = new Ticekt();	// 一个对象     new Thread(ticekt).start();		// 三个买票线程     new Thread(ticekt).start();     new Thread(ticekt).start(); } static class Ticekt implements Runnable{     private int num = 10;					// 余票数量     @Override     public void run() {         while (num>0){						// 票数>0,继续卖             System.out.println("准备买出一张票");             try {                 Thread.sleep(1000);             } catch (InterruptedException e) {                 e.printStackTrace();             }             System.out.println("出票成功!");             num--;             System.out.println("当前余票数:"+num);         }     } } 

1.同步代码块

同步代码块使用关键字// synchronize,利用锁对象, // 当要进入同一个锁的代码块的时候会先检查这个锁有没有被占用, // 被占用则等待,否则占用锁进入代码块 public static void main(String[] args) { Ticekt ticekt = new Ticekt(); new Thread(ticekt).start(); new Thread(ticekt).start(); new Thread(ticekt).start(); } static class Ticekt implements Runnable{ private int num = 10; private Object o =new Object(); // 锁对象 @Override public void run() { while (true){ synchronized (o){ // 同步代码块开始 if (num>0){ System.out.println("准备买出一张票"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("出票成功!"); num--; System.out.println(Thread.currentThread().getName()+":当前余票数:"+num); }else break; } // 同步代码块结束 } } }

  1. 同步方法

    同步方法的实现和上面的同步代码块类似,也使用synchronized,但是它修饰的对象变成了方法。当修饰的是普通方法的时候,锁对象是当前对象本身,即this;如果修饰的是静态方法的时候,锁对象则是这个类的字节码文件对象。

    下面只做修饰普通方法的举例,修饰静态方法的情景类似:

    public static void main(String[] args) {     Ticekt ticekt = new Ticekt();     new Thread(ticekt).start();     new Thread(ticekt).start();     new Thread(ticekt).start(); } static class Ticekt implements Runnable{     private int num = 10;     @Override     public void run() {         while (true){             boolean flag = sale();             if (!flag)                 break;         }     }     // 买票方法,同步执行,     // 不是静态方法,锁对象为this,即调用的对象     // 是静态方法,锁对象为Ticke.class,字节码文件对象     // 同一个类的对象会共用this这个锁,可以会影响其他同步方法的执行     public synchronized boolean sale(){         if (num>0){             System.out.println(Thread.currentThread().getName()+":准备买出一张票");             try {                 Thread.sleep(1000);             } catch (InterruptedException e) {                 e.printStackTrace();             }             System.out.println(Thread.currentThread().getName()+":出票成功!");             num--;             System.out.println(Thread.currentThread().getName()+":当前余票数:"+num);             return true;         }         return false;     } } 
    1. 使用显示锁Lock

      同步代码块和同步方法实现都是基于隐式锁。

      使用实现接口Lock的类ReentrantLock的方式是基于显示锁。

      显示锁比较适合自定义加锁和解锁,带来个性化的同时也可能会带来加锁后忘解锁的情况,在使用的过程中,需要充分考虑到加锁后在哪个地方再解锁,防止程序进入死锁,无法释放资源。下面是使用案例,也是基于买票程序的:

      public static void main(String[] args) {     Ticekt ticekt = new Ticekt();     new Thread(ticekt).start();     new Thread(ticekt).start();     new Thread(ticekt).start(); } static class Ticekt implements Runnable{     private int num = 10;     //  用实现接口Lock的类ReentrantLock创建对象     private Lock l= new ReentrantLock();     @Override     public void run() {         while (true){             l.lock();				// 上锁资源访问             if (num>0){                 System.out.println(Thread.currentThread().getName()+":准备买出一张票");                 try {                     Thread.sleep(1000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 System.out.println(Thread.currentThread().getName()+":出票成功!");                 num--;                 System.out.println(Thread.currentThread().getName()+":当前余票数:"+num);                 l.unlock();                  // 操作成功,解锁             }else{                 l.unlock();                 // 没有买票,break前需要释放锁,防止后面的线程被锁住不能释放                 break;             }         }     } } 

5.总结

        以上的介绍,总结了Java中使用多线程的基本内容,包括线程的创建的方式,线程常用的方法,实现线程同步的方法。在我们实际开发中,情景往往都是复杂的,可能会有大量的请求会同时发出,这个时候就需要合理的处理号这些请求,利用多线程的思想去解决这些问题。认识并使用多线程后,能够使自己开发的程序更加符合实际开发的需求,提高程序的执行效率。Java中的多线程的知识还很多,比如线程池。上面的介绍只是基础入门,想要深入学习的话,可以继续查找资料,或者找些项目实践。

版权声明:玥玥 发表于 2021-05-02 22:22:04。
转载请注明:Java--多线程 | 女黑客导航