1.创建线程的方法:
案例:计算1-1000的整数和
实现Runnable接口
步骤:
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start() ① 启动线程 ②调用当前线程的run()–>调用了Runnable类型的target的run()
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题public static AtomicLong result = new AtomicLong(0);static int count = 100000;public static CountDownLatch countDownLatch = new CountDownLatch(count);public static ExecutorService executor=Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException, ExecutionException {long start = System.currentTimeMillis();Thread[] threads = new Thread[count];for (int i = 0; i < count; i++) {MyThread myThread = new MyThread(100);threads[i] = new Thread(myThread);threads[i].start();}countDownLatch.await();System.out.println(result.get()); // 使用 get 方法获取 AtomicLong 的值System.out.println(System.currentTimeMillis() - start);}
}
class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法进行原子更新ThreadLearn.result.addAndGet(sum);ThreadLearn.countDownLatch.countDown();}
}
继承Thread类
步骤:
1.创建一个继承于Thread类的子类
2.重写Thread类的run() --> 将此线程执行的操作声明在run()中
3.创建Thread类的子类的对象
4.通过此对象调用start()执行线程
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题public static AtomicLong result = new AtomicLong(0);static int count = 100000;public static CountDownLatch countDownLatch = new CountDownLatch(count);public static ExecutorService executor=Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException, ExecutionException {long start = System.currentTimeMillis();Thread[] threads = new Thread[count];for (int i = 0; i < count; i++) {MyThread1 myThread = new MyThread1(100);threads[i] = new Thread(myThread);threads[i].start();}countDownLatch.await();System.out.println(result.get()); // 使用 get 方法获取 AtomicLong 的值System.out.println(System.currentTimeMillis() - start);}
}
class MyThread1 extends Thread {private long count;MyThread1(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法进行原子更新ThreadLearn.result.addAndGet(sum);ThreadLearn.countDownLatch.countDown();}
}
实现Callable接口
步骤:
1.创建一个实现Callable的实现类
2.实现call方法,将此线程需要执行的操作声明在call()中
3.创建Callable接口实现类的对象
4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
6.获取Callable中call方法的返回值
package Reflection;import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题public static AtomicLong result = new AtomicLong(0);static int count = 100000;public static CountDownLatch countDownLatch = new CountDownLatch(count);public static void main(String[] args) throws InterruptedException, ExecutionException {
// long start = System.currentTimeMillis();
// Thread[] threads = new Thread[count];
// for (int i = 0; i < count; i++) {
// MyThread1 myThread = new MyThread1(100);
// threads[i] = new Thread(myThread);
// threads[i].start();
// }
// countDownLatch.await();
// System.out.println(result.get()); // 使用 get 方法获取 AtomicLong 的值
// System.out.println(System.currentTimeMillis() - start);FutureTask<Integer> futureTask=new FutureTask<>(new MyThread2(100));Thread thread=new Thread(futureTask);thread.start();Integer o = futureTask.get();System.out.println(o);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法进行原子更新ThreadLearn.result.addAndGet(sum);ThreadLearn.countDownLatch.countDown();}
}class MyThread1 extends Thread {private long count;MyThread1(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法进行原子更新ThreadLearn.result.addAndGet(sum);ThreadLearn.countDownLatch.countDown();}
}class MyThread2 implements Callable<Integer> {private int count;public MyThread2(int count) {this.count=count;}@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}return sum;}
}
使用线程池
package Reflection;import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题public static AtomicLong result = new AtomicLong(0);static int count = 100000;public static CountDownLatch countDownLatch = new CountDownLatch(count);//线程池public static ExecutorService executor=Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException, ExecutionException {Future<Integer> submit = executor.submit(new MyThread2(100));Integer i=submit.get();System.out.println(i);}
}class MyThread2 implements Callable<Integer> {private int count;public MyThread2(int count) {this.count=count;}@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}return sum;}
}
2.统计多线程程序的运行时间
如果要统计多线程程序的执行时间,主线程等待所有子线程完成,那么总时间可能接近实际所有线程执行完毕的时间。所以可能需要确保主线程在所有子线程结束后才结束,然后计算整个程序的运行时间。
使用join方法
使用thread.join
,` 让一个线程等待另一个线程执行完毕。在多线程编程中,有时我们需要确保某些线程在其他线程完成特定任务后再继续执行,这时就可以使用 join() 方法来实现线程间的同步。
package Reflection;import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题public static AtomicLong result = new AtomicLong(0);public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();int count = 10000; // 将线程数量和数组长度统一为 10000Thread[] threads = new Thread[count];for (int i = 0; i < count; i++) {MyThread myThread = new MyThread(100);threads[i] = new Thread(myThread);threads[i].start();}for (Thread thread : threads) {thread.join();}System.out.println(result.get()); // 使用 get 方法获取 AtomicLong 的值System.out.println(System.currentTimeMillis() - start);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法进行原子更新ThreadLearn.result.addAndGet(sum);}
}
使用CountDownLatch
CountDownLatch
是 Java 并发包 java.util.concurrent
中的一个同步辅助类,主要用于协调多个线程之间的执行顺序。它可以让一个或多个线程等待其他一组线程完成它们的操作后再继续执行,从而确保程序的执行逻辑按照预期进行,避免出现数据不一致或逻辑错误的问题。
package Reflection;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题public static AtomicLong result = new AtomicLong(0);static int count = 100000;public static CountDownLatch countDownLatch = new CountDownLatch(count);public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();Thread[] threads = new Thread[count];for (int i = 0; i < count; i++) {MyThread myThread = new MyThread(100);threads[i] = new Thread(myThread);threads[i].start();}countDownLatch.await();System.out.println(result.get()); // 使用 get 方法获取 AtomicLong 的值System.out.println(System.currentTimeMillis() - start);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法进行原子更新ThreadLearn.result.addAndGet(sum);ThreadLearn.countDownLatch.countDown();}
}
执行结果:
CountDownLatch 的执行过程
1. 初始化阶段
- 首先,需要创建一个
CountDownLatch
实例,在创建时要传入一个初始计数值。这个计数值代表了需要等待完成的操作数量。例如在代码中CountDownLatch countDownLatch = new CountDownLatch(count);
,这里的count
就是初始计数值,它表明有count
个任务需要完成,CountDownLatch
会将这个值存储在内部作为计数器的初始状态。
2. 任务线程启动阶段
- 通常会有一个或多个任务线程被创建并启动去执行特定的任务。这些线程可能会并发地执行各自的任务,就像代码里通过循环创建并启动多个
Thread
实例,每个线程都去执行MyThread
类的run
方法中的任务。
3. 等待阶段
- 有一个或多个线程(通常是主线程)会调用
CountDownLatch
的await()
方法。当调用这个方法时,调用线程会进入阻塞状态,它会一直等待,直到CountDownLatch
的内部计数器值变为 0。这意味着它在等待所有任务都完成。
4. 任务完成与计数器递减阶段
- 每个任务线程在完成自己的任务后,会调用
CountDownLatch
的countDown()
方法。这个方法的作用是将CountDownLatch
的内部计数器值减 1。随着越来越多的任务线程完成任务并调用countDown()
方法,计数器的值会不断减小。
5. 唤醒等待线程阶段
- 当
CountDownLatch
的内部计数器值减到 0 时,所有之前调用await()
方法并处于阻塞状态的线程会被唤醒。这些线程会从await()
方法处继续执行后续的代码逻辑。
6. 后续处理阶段
- 被唤醒的线程可以继续执行后续的操作,比如代码中主线程在被唤醒后会打印最终的计算结果以及程序执行所花费的时间。
主要方法
CountDownLatch(int count)
构造方法:用于创建一个CountDownLatch
实例,并初始化其内部计数器的值为count
。这个count
表示需要等待完成的操作数量,必须是一个正整数。void await()
方法:调用该方法的线程会进入阻塞状态,直到CountDownLatch
的内部计数器值变为 0。如果在等待过程中当前线程被中断,会抛出InterruptedException
异常。boolean await(long timeout, TimeUnit unit)
方法:调用该方法的线程会等待一段时间,最多等待timeout
时间(由unit
指定时间单位)。如果在这段时间内计数器值变为 0,则线程会被唤醒并返回true
;如果超过指定时间计数器值仍不为 0,则线程会被唤醒并返回false
。同样,如果在等待过程中线程被中断,会抛出InterruptedException
异常。void countDown()
方法:该方法会将CountDownLatch
的内部计数器值减 1。当计数器值减到 0 时,所有正在等待的线程会被唤醒。如果计数器值已经为 0,调用该方法不会产生任何效果。long getCount()
方法:该方法用于返回CountDownLatch
当前的计数器值。可以通过这个方法来查看还有多少个任务未完成。
3.如何解决多线程对共享变量操作的线程安全问题
package Reflection;public class ThreadLearn {public static long result ;public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10000; i++) {MyThread myThread = new MyThread(1000);Thread thread = new Thread(myThread);thread.start();}Thread.sleep(10*1000);System.out.println(result);}}
class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum+=i;}ThreadLearn.result+=sum;System.out.println(sum);}
}
执行结果:
代码功能概述
这段 Java 代码主要的功能是创建 10000 个线程,每个线程会计算从 0 到 1000 的整数之和,然后将这个和累加到 ThreadLearn
类的静态变量 result
中。最后,主线程等待 10 秒后输出 result
的值。
代码存在的问题
- 线程安全问题:
ThreadLearn.result
是一个共享变量,多个线程同时对其进行写操作(ThreadLearn.result += sum;
),这会导致数据竞争(Data Race)问题,最终的result
值可能是错误的。 - 等待时间不确定性:使用
Thread.sleep(10 * 1000)
来等待所有子线程完成,这种方式不够可靠,因为不同的机器性能不同,可能会导致有些线程还未执行完,主线程就已经输出结果。
你提供的代码在多线程环境下存在线程安全问题,主要是因为多个线程同时对静态变量 ThreadLearn.result
进行写操作,这可能会导致数据竞争和不一致的结果。下面为你介绍几种解决该问题的方法。
方法一:使用 synchronized 关键字
synchronized
关键字可以用来修饰方法或代码块,保证同一时刻只有一个线程能够访问被修饰的代码,从而避免多线程对共享资源的并发访问问题。
package Reflection;public class ThreadLearn {public static long result;public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10000; i++) {MyThread myThread = new MyThread(1000);Thread thread = new Thread(myThread);thread.start();}Thread.sleep(10 * 1000);System.out.println(result);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 synchronized 块保证线程安全synchronized (ThreadLearn.class) {ThreadLearn.result += sum;}System.out.println(sum);}
}
解释:在 run
方法中,使用 synchronized (ThreadLearn.class)
对 ThreadLearn.result += sum;
这行代码进行同步,这样同一时刻只有一个线程能够执行该代码块,从而保证了对 result
变量的线程安全访问。
执行结果:
方法二:使用 AtomicLong 类
AtomicLong
是 Java 提供的一个原子类,它提供了一些原子操作方法,可以保证对长整型变量的原子性更新,避免了使用 synchronized
带来的性能开销。
package Reflection;import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 代替 longpublic static AtomicLong result = new AtomicLong(0);public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10000; i++) {MyThread myThread = new MyThread(1000);Thread thread = new Thread(myThread);thread.start();}Thread.sleep(10 * 1000);System.out.println(result.get());}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法进行原子更新result.addAndGet(sum);System.out.println(sum);}
}
解释:将 ThreadLearn
类中的 result
变量类型改为 AtomicLong
,并使用 addAndGet
方法来更新 result
的值。addAndGet
方法是原子操作,能够保证在多线程环境下对 result
的更新是线程安全的。
执行结果:
。