【昕宝爸爸小模块】浅谈之创建线程的几种方式

这篇具有很好参考价值的文章主要介绍了【昕宝爸爸小模块】浅谈之创建线程的几种方式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【昕宝爸爸小模块】浅谈之创建线程的几种方式,# Java并发,java,开发语言,线程安全,并发,并行

➡️博客首页       https://blog.csdn.net/Java_Yangxiaoyuan


       欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。


       本文章CSDN首发,欢迎转载,要注明出处哦!


       先感谢优秀的你能认真的看完本文,有问题欢迎评论区交流,都会认真回复!



一、✅典型解析


在Java中,共有四种方式可以创建线程,分别是:


  • 继承Thread类创建线程
  • 实现Runnable接门创建线程
  • 通过CallableFutureTask 创建线程
  • 通过线程池创建线程




    其实,归根结底最终就两种,一个是继承Thread类,一个是实现 Runnable接口 ,至于其他的。也是基于这两个方式实现的。但是有的时候面试官更关注的是实际写代码过程中,有几种方式可以实现所以一般说4种也没啥毛病。

1.1✅Runnable和Callable区别


Runnable接口Callable接口 都可以用来创建新线程,实现Runnable的时候,需要实现run方法;实现Callable接口的话,需要实现call方法


Runnable的run方法无返回值,Callable的call方法有返回值,类型为Object。


Callable中可以够抛出checked exception,而Runnable不可以。


CallableRunnable都可以应用于executors 。而 Thread类 只支持Runnable


1.2✅Future


Future是一个接口,代表了一个异步执行的结果。接口中的方法用来检查执行是否完成、等待完成和得到执行的结果。当执行完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。如果想取消,那么调用cancel()方法


FutureTaskFuture接口的一个实现,它实现了一个可以提交给Executor执行的任务,并且可以用来检查任务的执行状态和获取任务的执行结果。


1.3✅FutureTask和Callable示例


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
* @author xinbaobaba
* FutureTask和Callable示例
*/
public class FutureAndCallableExample {
	
	public static void main(Stringl] args) throws InterruptedException, ExecutionException {
		Callable<String> callable = () -> {
			System.out.println("Entered Callable");
			Thread.sleep(2000);
			return "Hello from Callable";
		});
	
		FutureTask<String> futureTask = new FutureTask<>(callable);
		Thread thread = new Thread(futureTask);
		thread.start();


		System.out.println("Do something else while callable is getting executed");
		System.out.println("Retrieved: " + futureTask.get());
	}
}

二、✅拓展知识仓


2.1✅Runnable接口是什么


Runnable接口是Java中用于定义线程任务的接口。在Java中,可以通过实现Runnable接口来定义线程任务,实现Runnable接口的类必须实现run()方法,该方法定义了线程要执行的具体操作。通过创建Runnable对象并将其传递给Thread类的构造函数,可以创建一个新的线程,并启动该线程执行任务。


一个简单的示例:

/**
* @author xinbaobaba
* 使用Java实现Runnable接口来创建线程
*/

public class MyRunnableTask implements Runnable {
    @Override
    public void run() {
        // 线程执行的代码
        System.out.println("Hello from the thread!");
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建Runnable对象
        MyRunnableTask task = new MyRunnableTask();
        
        // 创建Thread对象,并传递Runnable对象作为参数
        Thread thread = new Thread(task);
        
        // 启动线程
        thread.start();
    }
}

代码解析:定义了一个名为MyRunnableTask的类,它实现了Runnable接口并重写了run()方法。在run()方法中,我们简单地打印了一条消息。然后,在main()方法中,我们创建了MyRunnableTask的实例,并使用该实例创建了一个新的线程对象。最后,我们调用start()方法启动了线程。当线程启动后,它将自动执行我们在run()方法中定义的代码。


2.2✅线程安全有哪些特性


线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。


线程安全的特性主要有以下几个方面


  1. 原子性:一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。原子性可以确保多个线程之间的操作不会相互干扰,从而保证数据的一致性。
  2. 可见性:一个线程对主内存的修改可以及时的被其他线程所观察到。可见性可以保证多个线程之间共享数据的实时更新,确保每个线程都能获取到最新的数据状态。
  3. 有序性:一个线程观察其他线程的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。有序性可以保证代码的执行顺序按照程序员的预期进行,避免出现意外的结果。

以上就是线程安全的三大特性:原子性、可见性和有序性。这些特性可以确保多线程程序在并发执行时能够正确地处理共享数据,避免出现数据不一致、数据污染等问题

以下是一个简单的示例:



/**
* @author xinbaobaba
* 线程安全的基本特性ceshidemo
*/
public class ThreadSafeCounter {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class CounterThread extends Thread {
    private ThreadSafeCounter counter;

    public CounterThread(ThreadSafeCounter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            counter.increment();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        ThreadSafeCounter counter = new ThreadSafeCounter();
        CounterThread thread1 = new CounterThread(counter);
        CounterThread thread2 = new CounterThread(counter);
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Final count: " + counter.getCount());
    }
}

代码解析:定义了一个ThreadSafeCounter类,它有一个共享的count变量,用于计数。我们还定义了一个CounterThread类,它继承了Thread类,用于创建新的线程。每个线程都会执行1000次increment()操作,即增加计数。在main()方法中,我们创建了两个CounterThread线程,并启动它们。最后,我们等待两个线程执行完毕,并打印出最终的计数结果。


示例演示了线程安全的基本特性:原子性、可见性和有序性。由于increment()操作是原子的,即不会被其他线程打断,因此最终的计数结果是正确的。由于count变量的可见性得到了保证,每个线程都能观察到最新的计数结果。此外,由于线程调度和指令重排序的原因,最终的计数结果可能会与预期的顺序不同,但最终的结果仍然是正确的。


2.3✅什么是原子性


原子性是指在计算机科学中,一个操作(或一组操作)不可被中断地执行完毕或不执行,具有原子性的操作不会受到其他并发操作的干扰,能够保证数据的一致性和正确性。通俗来说,原子性就是“一气呵成”,不可分割的意思。原子性确保多个操作是一个不可以分割的整体,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。在数据库事务中,原子性是非常重要的特性之一,它可以确保事务中的所有操作都被完整地执行或者都不被执行,从而保持数据的一致性。


2.4✅如何保证多线程同时操作数据不相互污染


要保证多线程同时操作数据不相互污染,可以采用以下几种方法


1. 使用锁机制:可以使用Java中的synchronized关键字或者Lock对象来实现对共享数据的同步访问,确保一次只有一个线程能够访问共享数据,避免多个线程同时操作数据导致数据污染。


2. 使用读写锁:读写锁允许多个线程同时读取共享数据,但在写入数据时只允许一个线程访问,这样可以提高并发性能。


3. 使用volatile关键字:volatile关键字可以确保共享数据的可见性,当一个线程修改了共享数据后,其他线程可以立即看到修改后的数据。


4. 使用事务:数据库事务可以确保一系列操作要么全部成功,要么全部失败回滚,从而避免数据的不一致性。在Java中,可以使用JDBC或者ORM框架(如Hibernate)来管理数据库事务。


5. 使用线程安全的数据结构:Java提供了很多线程安全的数据结构,如Vector、Hashtable、CopyOnWriteArrayList等,这些数据结构内部已经实现了同步机制,可以保证多个线程同时访问时的正确性。


6. 避免共享状态:尽可能地减少共享状态,让每个线程都有自己的数据副本,这样可以避免多个线程之间的数据竞争和相互污染。


7. 使用消息队列:通过消息队列可以将多个线程之间的通信解耦,每个线程将需要操作的数据发送到队列中,另一个线程从队列中获取数据进行处理,这样可以避免直接访问共享数据。


为了保证多线程同时操作数据不相互污染,可以采用多种方法来保证数据的原子性、可见性和有序性。在具体实现时,需要根据实际情况选择适合的方法来保证数据的正确性和一致性


代码示例:


/**
* @author xinbaobaba
* 如何使用synchronized关键字来保证多线程同时操作数据不相互污染
*/
public class SharedData {
    private int count = 0;

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

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

public class ThreadA extends Thread {
    private final SharedData sharedData;

    public ThreadA(SharedData sharedData) {
        this.sharedData = sharedData;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            sharedData.increment();
        }
    }
}

public class ThreadB extends Thread {
    private final SharedData sharedData;

    public ThreadB(SharedData sharedData) {
        this.sharedData = sharedData;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(sharedData.getCount());
        }
    }
}

public class Main {
    public static void main(String[] args) {
        SharedData sharedData = new SharedData();
        ThreadA threadA = new ThreadA(sharedData);
        ThreadB threadB = new ThreadB(sharedData);
        threadA.start();
        threadB.start();
        try {
            threadA.join();
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

代码解析:定义了一个SharedData类,它有一个共享的count变量,用于计数。我们还定义了两个线程类ThreadAThreadB,它们分别执行增加计数和获取计数的操作。在SharedData类中,我们使用synchronized关键字对increment()getCount()方法进行了同步,确保一次只有一个线程能够访问共享数据。在main()方法中,我们创建了两个线程对象,并启动它们。由于使用了同步机制,即使两个线程同时访问共享数据,也不会出现数据污染的问题。最终的计数结果将正确地累加并输出。


2.5✅Thread类有哪些构造函数


在Java中,Thread类有几个构造函数,它们允许你创建和初始化线程。以下是Thread类的构造函数:


  1. Thread():这是一个默认构造函数,用于创建一个新的线程对象,但不会自动启动线程。
  2. Thread(Runnable target):这个构造函数接受一个实现了Runnable接口的对象作为参数,该对象定义了线程要执行的代码。
  3. Thread(ThreadGroup group, Runnable target):这个构造函数接受一个ThreadGroup对象和一个实现了Runnable接口的对象作为参数。ThreadGroup对象定义了线程所属的线程组。
  4. Thread(String name):这个构造函数接受一个字符串作为参数,用于为线程设置一个名称。
  5. Thread(ThreadGroup group, String name):这个构造函数接受一个ThreadGroup对象和一个字符串作为参数。ThreadGroup对象定义了线程所属的线程组,字符串用于为线程设置一个名称。
  6. Thread(Runnable target, String name):这个构造函数接受一个实现了Runnable接口的对象和一个字符串作为参数。字符串用于为线程设置一个名称,而Runnable对象定义了线程要执行的代码。
  7. Thread(ThreadGroup group, Runnable target, String name):这个构造函数接受一个ThreadGroup对象、一个实现了Runnable接口的对象和一个字符串作为参数。这些参数允许你设置线程的名称、定义线程要执行的代码以及设置线程所属的线程组。

注意:当你使用这些构造函数创建线程时,它们只是创建了线程对象,并不会自动启动线程。要启动线程,你需要调用线程对象的start()方法。


2.6✅原子性和并行操作有什么关系


原子性和并行操作是计算机科学中的两个重要概念,它们之间存在一定的关系。


原子性是指一个操作或者一组操作不可分割地执行,要么全部执行成功,要么全部执行失败。原子性主要应用于多线程编程和数据库事务管理中,用于保证多个线程或者事务中的操作的一致性和正确性。在多线程编程中,原子性可以避免多个线程同时访问共享数据时出现数据竞争和不一致的问题。在数据库事务管理中,原子性可以保证一系列操作要么全部成功,要么全部失败回滚,从而保持数据的一致性。


并行操作是指多个操作同时执行,以提高程序的执行效率。并行操作通常应用于多核处理器和分布式计算中,通过将多个任务分配给多个处理器或者计算机节点,同时执行这些任务,可以加快程序的执行速度。


原子性和并行操作之间存在一定的关系。在并行操作中,为了保持数据的一致性和正确性,需要确保每个操作都是原子的,即不可分割地执行。同时,原子性也可以帮助避免并行操作中可能出现的数据竞争和不一致问题。在实现并行操作时,可以通过加锁、事务管理等方式来保证操作的原子性,从而保证数据的一致性和正确性。


注意:原子性和并行操作之间存在一定的矛盾和权衡。过于强调原子性可能会影响并行操作的效率,而过于强调并行操作可能会增加数据竞争和不一致的风险。因此,在实际应用中,需要根据具体场景和需求来平衡原子性和并行操作的关系,以实现最佳的性能和正确性。


代码的示例:


/**
* @author xinbaobaba
* 原子性和并行操作之间的关系示例
*/
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicityAndParallelism {
    public static void main(String[] args) {
        AtomicInteger atomicCounter = new AtomicInteger(0);
        int numThreads = 10;
        Thread[] threads = new Thread[numThreads];

        // 创建并启动多个线程,每个线程递增计数器
        for (int i = 0; i < numThreads; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    atomicCounter.incrementAndGet(); // 原子性操作
                }
            });
            threads[i].start();
        }

        // 等待所有线程执行完毕
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 输出最终的计数器值,应该等于线程数量乘以每个线程的递增值
        System.out.println("Final counter value: " + atomicCounter.get());
    }
}

代码解析:使用了AtomicInteger类来创建一个原子性计数器。AtomicInteger类中的incrementAndGet()方法是一个原子性操作,它可以保证在多线程环境下对计数器的递增操作是原子的,即不可分割地执行。这样可以避免多个线程同时访问计数器时出现数据竞争和不一致的问题。


我们创建了10个线程,每个线程都执行1000次计数器的递增操作。通过使用原子性计数器,我们可以确保最终的计数器值是正确的,即等于线程数量乘以每个线程的递增值。如果没有原子性保证,可能会出现数据竞争和不一致的问题,导致最终的计数器值不正确。


示例说明了原子性和并行操作之间的关系。在并行操作中,原子性可以保证多个操作的一致性和正确性,避免数据竞争和不一致的问题。同时,通过并行操作可以提高程序的执行效率。在实际应用中,需要根据具体场景和需求来平衡原子性和并行操作的关系,以实现最佳的性能和正确性。


2.7✅如何保证原子性操作不被干扰


要保证原子性操作不被干扰,可以采用以下几种方法:


  1. 使用锁机制:Java中的synchronized关键字或者Lock接口可以用来控制对共享资源的访问,确保一次只有一个线程能够执行某个代码块或方法,从而防止其他线程的干扰。
  2. 使用并发容器:Java提供了很多并发容器,如ConcurrentHashMap、CopyOnWriteArrayList等,这些容器内部已经实现了线程安全,可以避免多线程环境下的数据不一致问题。
  3. 使用事务:在数据库操作中,可以使用事务来保证原子性。事务是一系列的操作,要么全部成功,要么全部失败回滚,这样可以避免数据的不一致。
  4. 使用乐观锁或悲观锁:在并发控制中,乐观锁和悲观锁也是常用的手段。乐观锁基于数据版本记录机制,通过版本号或时间戳等方式来控制并发操作时的数据一致性;悲观锁则是通过数据库的行级锁或表级锁来限制并发操作。
  5. 避免共享状态:尽可能地减少共享状态,每个线程处理自己的数据副本,不与其他线程共享数据。这样自然就不存在数据不一致的问题。
  6. 使用无锁数据结构:无锁数据结构(Lock-free data structures)是另一种解决方案,它使用原子操作来更新数据,而不需要显式的锁机制。
  7. 避免长时间持有锁:尽量减少持有锁的时间,以减少其他线程的等待时间,提高并发性能。
  8. 使用分布式系统解决方案:对于大规模的分布式系统,可以考虑使用分布式事务、两阶段提交、三阶段提交等解决方案来保证操作的原子性。

总之,要保证原子性操作不被干扰,需要结合具体的应用场景和需求,选择合适的并发控制策略和工具。


2.8✅线程安全和锁有啥区别


线程安全是多线程编程中使用的概念,用于保证多个线程之间数据的安全性和一致性。线程安全涉及到多个线程对共享数据的访问和修改,需要采取措施来避免数据竞争和不一致的问题。而锁是实现线程安全的一种机制,它可以控制对共享资源的访问,确保一次只有一个线程能够执行某个代码块或方法,从而防止其他线程的干扰。锁机制包括synchronized关键字、Lock接口、乐观锁和悲观锁等。因此,线程安全是一个更广泛的概念,而锁是实现线程安全的一种具体手段。


2.9✅线程安全的优缺点是什么


线程安全的优点主要包括:


  1. 保证数据完整性:线程安全可以保证多线程环境下的数据完整性和一致性,避免数据竞争和不一致的问题。
  2. 提高系统稳定性:线程安全有助于提高系统的稳定性和可靠性,减少因数据错误或不一致导致的问题。
  3. 充分利用多核资源:线程安全可以利用多核处理器的资源,通过并行计算提高程序的执行效率。

线程安全缺点


  1. 性能开销:线程安全的实现需要引入额外的机制来控制线程的访问和同步,这可能导致一定的性能开销。
  2. 编程难度增加:线程安全的实现需要更全面深入地考虑多线程的同步和协调问题,增加了编程的复杂度和难度。
  3. 死锁和活锁风险:线程安全需要合理地设计锁机制和控制线程的执行顺序,否则可能导致死锁或活锁的问题。
  4. 资源竞争:在多线程环境下,资源竞争可能导致线程阻塞或等待,影响程序的执行效率和响应性。

因此,在实现线程安全时需要权衡利弊,根据具体的应用场景和需求选择合适的线程安全策略,以实现最佳的性能和正确性。文章来源地址https://www.toymoban.com/news/detail-800729.html


到了这里,关于【昕宝爸爸小模块】浅谈之创建线程的几种方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【昕宝爸爸小模块】HashMap用在并发场景存在的问题

    这是一个非常典型的问题,但是只会出现在1.7及以前的版本,1.8之后就被修复了。 虽然JDK 1.8修复了某些多线程对HashMap进行操作的问题,但在并发场景下,HashMap仍然存在一些问题。 如: 虽然JDK 1.8修复了多线程同时对HashMap扩容时可能引起的链表死循环问题 , 但在JDK 1.8版本

    2024年01月24日
    浏览(34)
  • 【昕宝爸爸小模块】日志系列之什么是分布式日志系统

    ➡️博客首页       https://blog.csdn.net/Java_Yangxiaoyuan        欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。        本文章CSDN首发,欢迎转载,要注明出处哦!        先感谢优秀的你能认真的看完本文,有问题欢迎评论区交流,都会认真回复! 现在,很多应

    2024年02月20日
    浏览(29)
  • 【昕宝爸爸小模块】深入浅出之Java 8中的 Stream

    Stream 使用一种类似用 SOL 语句从数据库查询数据的直观方式来提供一种对Java 集合运算和表达的高阶抽象。 Stream API 可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。 这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节

    2024年02月02日
    浏览(49)
  • 【昕宝爸爸小模块】Java中Timer实现定时调度的原理(解析)

    Java中的 Timer 类是用于计划执行一项任务一次或重复固定延迟执行的简单工具。它使用一个名为 TaskQueue 的内部类来存储要执行的任务,这些任务被封装为 TimerTask 对象。 Timer 实现定时调度的基本原理: 创建 Timer 对象 :当你创建一个 Timer 对象时,它会实例化一个线程(不是

    2024年01月16日
    浏览(36)
  • 【昕宝爸爸小模块】深入浅出之针对大Excel做文件读取问题

    ➡️博客首页       https://blog.csdn.net/Java_Yangxiaoyuan        欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。        本文章CSDN首发,欢迎转载,要注明出处哦!        先感谢优秀的你能认真的看完本文,有问题欢迎评论区交流,都会认真回复! 在POI中,提供

    2024年01月18日
    浏览(34)
  • 浅谈之Java多线程

    Java多线程是Java语言中一个非常重要的特性,它允许程序同时执行多个任务。通过多线程,程序可以同时处理多项任务,从而缩短程序的执行时间。另外,多线程也有助于利用多核处理器,更好地发挥计算机硬件的性能。 那我们在Java中如何去实现多线程呢? 今天我们就从最

    2024年02月01日
    浏览(36)
  • 创建线程的几种方式

    线程和进程的区别: 进程是操作系统进行资源分配的最小单元。 线程是操作系统进行任务分配的最小单元,线程隶属于进程。 如何开启线程? 1、继承Thread类,重写run方法。 2、实现Runnable接口,实现run方法。 3、实现Callable接口,实现call方法。通过FutureTask创建一个线程,获

    2024年02月03日
    浏览(44)
  • C#针对VS线程间操作提示:程间操作无效: 从不是创建控件“”的线程访问它的几种解决方法

    转载请标明出处:Python Excellent的博客 此为最基础方法 (入门级) 运行效果如图所示 * 先在按钮事件中创建一个Test1()线程 * 在测试1中有两种方法可以访问窗体线程(首推荐) public SynchronizationContext UiContext //第一步全局声明 UiContext = SynchronizationContext.Current; //第二部在public For

    2024年02月08日
    浏览(34)
  • Java 终止线程的几种方式

    所谓正常运行结束,就是程序正常运行结束,线程自动结束。 一般run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。他们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:最直接的方法就是设

    2024年02月07日
    浏览(32)
  • 线程间实现通信的几种方式

    线程间通信的模型有两种:共享内存和消息传递,下面介绍的都是围绕这两个来实现 有两个线程A和B,B线程向一个集合里面依次添加元素“abc”字符串,一共添加10次,当添加到第五次的时候,希望线程A能够收到线程B的通知,然后B线程执行相关的业务操作 Object类提供了线程

    2024年02月15日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包