JAVA多线程入门

Runnable接口的实现

Runnable 接口是 Java 中定义在 java.lang 包中的一个功能性接口,用于表示一个可以被线程执行的任务。它通常用于定义那些希望在某个线程中运行的代码块。Runnable 接口只有一个抽象方法:

public interface Runnable {
    void run();
}

为了使用这个接口,我们需要实现这个接口并提供run方法的具体实现,然后可以将其传递给一个Thread对象来启动一个新的线程。

定义一个类实现Runnable接口

定义一个类实现Runnable接口,并且覆写run方法。
实现接口的关键字:implement(英文意思:使生效)
(插一句:继承是extends关键字)
覆写方法的关键字:@override

示例:

// 实现 Runnable 接口
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程正在运行...");
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建一个 Runnable 实例
        Runnable runnable = new MyRunnable();
        
        // 创建一个线程并启动它
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

使用匿名内部类实现Runnable

合理使用匿名内部类可以起到简化代码的作用,滥用可能会得到适得其反的效果。

在这里多插一嘴,匿名内部类同上用于创建只需要使用一次的类,在每次需要相同的功能时,可以重新定义并实例化它们。

一般匿名内部类的框架(在此用接口举例子):

// 需要被实现的接口
interface 接口名称 { 
	函数返回类型 函数名(形参类型);
}
public class Main {
    public static void main(String[] args) {
        // 创建一个匿名内部类实现接口
        接口名称 anonymous = new 接口名称() {
            @Override
            在此覆写接口中需要实现函数
        };
    }
}

下面是一个匿名内部类的具体例子:

interface Greeting {
    void greet();
}

public class Main {
    public static void main(String[] args) {
        // 创建一个匿名内部类实现Greeting接口
        Greeting anonymousGreeting = new Greeting() {
            @Override
            public void greet() {
                System.out.println("Hello from the anonymous class!");
            }
        };

        // 调用匿名内部类的方法
        anonymousGreeting.greet();
    }
}

我们再回到用匿名内部类实现Runnable接口的问题上,直接举一个例子:

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程正在运行...");
            }
        });
        thread.start();
    }
}

使用 Lambda 表达式实现 Runnable

Lambda 表达式是 Java 8 引入的一种新特性,它提供了一种简洁、清晰的方式来表示单个方法接口(即函数式接口)的实现。

Lambda 表达式的基本语法如下:
(parameters) -> expression
或者
(parameters) -> { statements; }

下面直接举一个例子:

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println("线程正在运行..."));
        thread.start();
    }
}

上面的代码块等价于用下面用匿名内部类的实现:

public class test {  
    public static void main(String[] args) {  
        Thread thread = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                System.out.println("线程正在运行...");  
            }  
        });  
  
        thread.start();  
    }  
}

线程的创立

可以用Thread thread = new Thread(new Runnable),括号内需要向构造器传入一个Runnbale类型的对象,Runnable类型对象用于告诉实例化出来的线程对象thread你这个线程需要执行什么样的操作,也就是执行run函数。

然后我们开启线程,调用start方法:
thread.start();
这样就开启了一个线程。

我们也可以不实例化对象,直接使用new关键字创建并开启线程,像这样:

new Thread(() -> { 
	// 线程执行的代码 }
).start();

在Java中,创建对象时直接调用其方法是一种常见的操作,不仅限于 Thread 类。实际上,只要对象的生命周期管理不需要特别的注意,并且你不需要在后续代码中引用该对象,几乎任何类都可以这样使用。这种方式通常用于一些临时对象或一次性的操作。

多线程工作下会有线程间的通信问题,可能会出现多个线程调用同一个方法,为了实现线程同步,我们需要用到synchronized关键字。

synchronized关键字


如果我们不做线程间的同步,就会出现竞态条件的问题,即:
一个多线程环境下对共享变量同一个的访问和修改,由于多个线程同时访问和修改同一个变量,导致不一致或错误的结果。

举个例子:

class Game{  
    //用final修饰变量,防止变量的值被修改  
    private static final int DELAY=500;  
    private int x;  
  
    public Game(){  
        x=0;  
    }  
  
    public void addX(int i){  
        try{  
            int y=x;  
            Thread.sleep((int)(DELAY*Math.random()));  
            y += 1;  
            x = y;  
            System.out.println("线程" + i + "正在使用addx方法");  
            System.out.println("x=" + x);  
        } catch (InterruptedException e){  
  
        }  
    }  
}  
  
public class Homework8 {  
    public static void main(String[] args) {  
        var game = new Game();  
        for(int i = 0; i < 2; i++){  
            int finalI = i;  
            new Thread(()->{  
                System.out.println("线程" + finalI + "正在执行");  
                for(int j = 0; j < 2; j++) {  
                    game.addX(finalI);  
                }  
  
            }).start();  
        }  
    }  
}

这个程序的输出结果是:
在这里插入图片描述

可以看到如果没有进行线程间的同步,就会出现一些”不一致或错误的结果“,为了解决这个问题,我们可以使用synchronized关键字进行线程间的同步,确保多个线程可以安全地访问共享资源。

为了解决上面的所举例子中出现的问题,我们可以在addX方法的前面加上synchronized关键字。当你在方法声明中使用 synchronized 关键字时,整个方法会被锁住。只有一个线程可以执行这个方法,直到该线程执行完毕并释放锁。

修改后的运行结果:
在这里插入图片描述
在这里插入图片描述

有时你不需要同步整个方法,只需同步其中的一部分。在这种情况下,你可以使用同步代码块来提高性能。同步代码块可以指定要锁定的对象。

示例:

public class Counter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

除了同步方法和代码块之外,我们也可以同步静态方法,但在此不作过多介绍。

注意事项

  1. 避免死锁:在多线程程序中,如果线程持有锁并等待其他线程持有的锁,则可能会导致死锁。
  2. 最小化同步范围:尽量只在必要的地方使用同步,以减少开销和提高性能。
  3. 避免脏读:如果一个线程正在写入变量而另一个线程在读取变量,可能会导致读取到不完整的数据状态。

ReentrantLock

ReentrantLock 是 Java 并发包 (java.util.concurrent.locks) 中提供的一种锁实现,它比 synchronized 关键字提供了更多的功能和灵活性。ReentrantLock 支持公平锁、非公平锁、可中断的锁获取、尝试加锁以及超时加锁等特性。

下面是一个多线程共用一个计数器的例子:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

在这个示例中,lock.lock() 获取锁,如果当前锁被其他线程持有,则当前线程会等待直到锁可用。lock.unlock()finally 块中确保锁始终会被释放,即使在执行过程中发生异常。

ReentrantLocksynchronized关键字还是有一定的区别:
synchronized关键字会对你要求的部分进行上锁,并且使用这个部分的线程结束后才会解锁,但是ReentrantLock不同,被上锁的部分被解锁后,如果当前锁被另一个线程所持有,那么这个线程就会使用这个部分,而不必等到该线程结束再后调用代码块。所以说,它比 synchronized 关键字提供了更多的功能和灵活性。

所以我们在讲synchronized关键字中所举的例子也可以用ReentrantLock来解决:

import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  
  
class Game{  
    //用final修饰变量,防止变量的值被修改  
    private static final int DELAY=500;  
    private int x;  
  
    private final Lock lock = new ReentrantLock();  
  
    public Game(){  
        x=0;  
    }  
  
    public void addX(int i){  
        try{  
            lock.lock();  
            int y=x;  
            Thread.sleep((int)(DELAY*Math.random()));  
            y += 1;  
            x = y;  
            System.out.println("线程" + i + "正在使用addx方法");  
            System.out.println("x=" + x);  
        } catch (InterruptedException e){  
  
        } finally {  
            lock.unlock();  
        }  
    }  
}  
  
public class Homework8 {  
    public static void main(String[] args) {  
        var game = new Game();  
        for(int i = 0; i < 2; i++){  
            int finalI = i;  
            new Thread(()->{  
                System.out.println("线程" + finalI + "正在执行");  
                for(int j = 0; j < 2; j++) {  
                    game.addX(finalI);  
                }  
  
            }).start();  
        }  
    }  
}

运行结果如下:
![[Pasted image 20240618213641.png]]

大概就介绍这些,因为作者目前也就会这么多了在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值