➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan
欢迎优秀的你点赞、️收藏、加❤️关注哦。
本文章CSDN首发,欢迎转载,要注明出处哦!
先感谢优秀的你能认真的看完本文,有问题欢迎评论区交流,都会认真回复!
在Java中,共有四种方式可以创建线程,分别是:
- 继承
Thread
类创建线程- 实现
Runnable
接门创建线程- 通过
Callable
和FutureTask
创建线程- 通过线程池创建线程
其实,归根结底最终就两种,一个是继承Thread类,一个是实现Runnable接口
,至于其他的。也是基于这两个方式实现的。但是有的时候面试官更关注的是实际写代码过程中,有几种方式可以实现所以一般说4种也没啥毛病。
Runnable接口
和 Callable接口
都可以用来创建新线程,实现Runnable的时候,需要实现run方法
;实现Callable接口的话,需要实现call方法
。
Runnable的run方法无返回值,Callable的call方法有返回值,类型为Object。
Callable中可以够抛出checked exception,而Runnable不可以。
Callable
和Runnable
都可以应用于executors
。而 Thread类
只支持Runnable
。
Future是一个接口,代表了一个异步执行的结果。接口中的方法用来检查执行是否完成、等待完成和得到执行的结果。当执行完成后,只能通过get()方法
得到结果,get方法
会阻塞直到结果准备好了。如果想取消,那么调用cancel()方法
。
FutureTask
是Future接口
的一个实现,它实现了一个可以提交给Executor
执行的任务,并且可以用来检查任务的执行状态和获取任务的执行结果。
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());
}
}
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()
方法中定义的代码。
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
线程安全的特性主要有以下几个方面:
以上就是线程安全的三大特性:原子性、可见性和有序性。这些特性可以确保多线程程序在并发执行时能够正确地处理共享数据,避免出现数据不一致、数据污染等问题。
以下是一个简单的示例:
/**
* @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
变量的可见性得到了保证,每个线程都能观察到最新的计数结果。此外,由于线程调度和指令重排序的原因,最终的计数结果可能会与预期的顺序不同,但最终的结果仍然是正确的。
原子性是指在计算机科学中,一个操作(或一组操作)不可被中断地执行完毕或不执行,具有原子性的操作不会受到其他并发操作的干扰,能够保证数据的一致性和正确性。通俗来说,原子性就是“一气呵成”,不可分割的意思。原子性确保多个操作是一个不可以分割的整体,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。在数据库事务中,原子性是非常重要的特性之一,它可以确保事务中的所有操作都被完整地执行或者都不被执行,从而保持数据的一致性。
要保证多线程同时操作数据不相互污染,可以采用以下几种方法:
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
变量,用于计数。我们还定义了两个线程类ThreadA
和ThreadB
,它们分别执行增加计数和获取计数的操作。在SharedData
类中,我们使用synchronized
关键字对increment()
和getCount()
方法进行了同步,确保一次只有一个线程能够访问共享数据。在main()
方法中,我们创建了两个线程对象,并启动它们。由于使用了同步机制,即使两个线程同时访问共享数据,也不会出现数据污染的问题。最终的计数结果将正确地累加并输出。
在Java中,Thread
类有几个构造函数,它们允许你创建和初始化线程。以下是Thread
类的构造函数:
Thread()
:这是一个默认构造函数,用于创建一个新的线程对象,但不会自动启动线程。Thread(Runnable target)
:这个构造函数接受一个实现了Runnable
接口的对象作为参数,该对象定义了线程要执行的代码。Thread(ThreadGroup group, Runnable target)
:这个构造函数接受一个ThreadGroup
对象和一个实现了Runnable
接口的对象作为参数。ThreadGroup
对象定义了线程所属的线程组。Thread(String name)
:这个构造函数接受一个字符串作为参数,用于为线程设置一个名称。Thread(ThreadGroup group, String name)
:这个构造函数接受一个ThreadGroup
对象和一个字符串作为参数。ThreadGroup
对象定义了线程所属的线程组,字符串用于为线程设置一个名称。Thread(Runnable target, String name)
:这个构造函数接受一个实现了Runnable
接口的对象和一个字符串作为参数。字符串用于为线程设置一个名称,而Runnable
对象定义了线程要执行的代码。Thread(ThreadGroup group, Runnable target, String name)
:这个构造函数接受一个ThreadGroup
对象、一个实现了Runnable
接口的对象和一个字符串作为参数。这些参数允许你设置线程的名称、定义线程要执行的代码以及设置线程所属的线程组。注意:当你使用这些构造函数创建线程时,它们只是创建了线程对象,并不会自动启动线程。要启动线程,你需要调用线程对象的start()
方法。
原子性和并行操作是计算机科学中的两个重要概念,它们之间存在一定的关系。
原子性是指一个操作或者一组操作不可分割地执行,要么全部执行成功,要么全部执行失败。原子性主要应用于多线程编程和数据库事务管理中,用于保证多个线程或者事务中的操作的一致性和正确性。在多线程编程中,原子性可以避免多个线程同时访问共享数据时出现数据竞争和不一致的问题。在数据库事务管理中,原子性可以保证一系列操作要么全部成功,要么全部失败回滚,从而保持数据的一致性。
并行操作是指多个操作同时执行,以提高程序的执行效率。并行操作通常应用于多核处理器和分布式计算中,通过将多个任务分配给多个处理器或者计算机节点,同时执行这些任务,可以加快程序的执行速度。
原子性和并行操作之间存在一定的关系。在并行操作中,为了保持数据的一致性和正确性,需要确保每个操作都是原子的,即不可分割地执行。同时,原子性也可以帮助避免并行操作中可能出现的数据竞争和不一致问题。在实现并行操作时,可以通过加锁、事务管理等方式来保证操作的原子性,从而保证数据的一致性和正确性。
注意:原子性和并行操作之间存在一定的矛盾和权衡。过于强调原子性可能会影响并行操作的效率,而过于强调并行操作可能会增加数据竞争和不一致的风险。因此,在实际应用中,需要根据具体场景和需求来平衡原子性和并行操作的关系,以实现最佳的性能和正确性。
代码的示例:
/**
* @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次计数器的递增操作。通过使用原子性计数器,我们可以确保最终的计数器值是正确的,即等于线程数量乘以每个线程的递增值。如果没有原子性保证,可能会出现数据竞争和不一致的问题,导致最终的计数器值不正确。
示例说明了原子性和并行操作之间的关系。在并行操作中,原子性可以保证多个操作的一致性和正确性,避免数据竞争和不一致的问题。同时,通过并行操作可以提高程序的执行效率。在实际应用中,需要根据具体场景和需求来平衡原子性和并行操作的关系,以实现最佳的性能和正确性。
要保证原子性操作不被干扰,可以采用以下几种方法:
总之,要保证原子性操作不被干扰,需要结合具体的应用场景和需求,选择合适的并发控制策略和工具。
线程安全是多线程编程中使用的概念,用于保证多个线程之间数据的安全性和一致性。线程安全涉及到多个线程对共享数据的访问和修改,需要采取措施来避免数据竞争和不一致的问题。而锁是实现线程安全的一种机制,它可以控制对共享资源的访问,确保一次只有一个线程能够执行某个代码块或方法,从而防止其他线程的干扰。锁机制包括synchronized关键字、Lock接口、乐观锁和悲观锁等。因此,线程安全是一个更广泛的概念,而锁是实现线程安全的一种具体手段。
线程安全的优点主要包括:
线程安全缺点:
因此,在实现线程安全时需要权衡利弊,根据具体的应用场景和需求选择合适的线程安全策略,以实现最佳的性能和正确性。