1、计算一亿个数字之和
使用单线程 、多线程测试计算苏联
public class ThreadF {
//定义1亿长度的数组
private static double[] nums = new double[1_0000_0000];
private static Random random = new Random();
private static double result = 0.0,result1 = 0.0,result2 = 0.0;
//静态代码块 启动先执行 只执行一次
static {
for (int i = 0; i < nums.length ; i++) {
nums[i] = random.nextDouble();
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("初始化长度:"+ nums.length);
ThreadF.simpleWay();
ThreadF.doubleWay();
}
//使用单线程计算
private static void simpleWay(){
long startTime = System.currentTimeMillis();
for (int i = 0; i < nums.length ; i++) {
result += nums[i];
}
System.out.println("单线程结果:"+ result);
long endTime = System.currentTimeMillis();
long time = endTime - startTime ;
System.out.println("单线程耗时:"+ time );
}
//使用多线程计算
private static void doubleWay() throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i < nums.length/2 ; i++) {
result1 += nums[i];
}
});
Thread thread2 = new Thread(()->{
for (int i = nums.length/2; i < nums.length ; i++) {
result2 += nums[i];
}
});
long startTime = System.currentTimeMillis();
thread1.start();
thread2.start();
//保持顺序 线程1线程2排序
thread1.join();
double res = result1 + result2;
System.out.println("多线程结果:" + res);
long endTime = System.currentTimeMillis();
long time = endTime - startTime;
System.out.println("多线程耗时:" + time);
}
}
运行结果:
2、多线程实现卖票小程序
某电影院正在上映某大片,共5张票,而现在有3个窗口售卖,请设计一个程序模拟该电影院售票.
public class ThreadD01 extends Thread{
//售卖的电影票数
private int ticket = 5;
//重写run方法
public void run() {
while (true){
System.out.println("当前线程名:" + Thread.currentThread() + "电影剩余票数:" +ticket--);
if (ticket < 0){
break;
}
}
}
public static void main(String[] args) {
ThreadD01 one = new ThreadD01();
ThreadD01 two = new ThreadD01();
ThreadD01 three = new ThreadD01();
one.start();
two.start();
three.start();
}
}
结果分析:从下图输出结果中可以分析,使用继承Thread类实现卖票,导致每个窗口都卖了五张票,而这个电影院总共才五张票,多线程出现了超卖现象。原因是继承Thread方式,是多线程多实例,无法实现资源的共享。
2.1 、优化一
实现Runnable接口进行卖票,电影院总共才五张票,多线程卖票正常。原因是实现Runnable方式,是多线程单实例,可实现资源的共享。
public class ThreadD01 extends Thread{
//售卖的电影票数
private static int ticket = 5;
//通过Runnable 方式实现多线程
static class RunnableDemo implements Runnable{
@Override
public void run() {
while (ticket >0) {
show();
}
}
//实现售卖方法
public void show(){
if (ticket>0){
System.out.println("当前售票名称:" + Thread.currentThread() + "剩余票数:" + ticket-- + "张");
}
}
}
public static void main(String[] args) {
//创建两个线程
RunnableDemo r = new RunnableDemo();
new Thread(r).start();
new Thread(r).start();
}
}
程序结果:
2.2、优化二
细心的小伙伴可能会发现,怎么在2.1输出打印的票数不是从大到小排序的,这跟现实中的卖票场景不一样呐。如果想解决这样的问题,就必须使用同步,所谓的同步就是指多个操作在同一个时间段内只有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。
//实现售卖方法
public synchronized void show(){
if (ticket>0){
System.out.println("当前售票名称:" + Thread.currentThread() + "剩余票数:" + ticket-- + "张");
}
}
程序结果:
3、线程池应用
公用代码类,为下面测试使用
public class MyThread extends Thread {
private int i;
public MyThread(int in) {
this.i = in;
}
//重写方法
public void run() {
try {
this.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(currentThread().getName()+ "正在打印:" + i);
}
}
3.1、newSingleThreadExecutor
单线程化的线程池
说明:初始化只有一个线程的线程池,内部使用LinkedBlockingQueue作为阻塞队列。
特点:相当于单线程串行执行所有任务如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
executorService.execute(new MyThread(i));
}
//关闭线程:停止接收新任务,原来的任务继续执行
executorService.shutdown();
}
程序结果:
3.2、newFixedThreadPool
定长线程池
说明:初始化一个指定线程数的线程池,其中corePoolSize == maxiPoolSize,使用LinkedBlockingQuene作为阻塞队列
特点:每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,即使当线程池没有可执行任务时,也不会释放线程。如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadText {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executorService.execute(new MyThread(i));
}
executorService.shutdown();
}
}
程序结果:
3.3、newCachedThreadPool
缓存线程池
说明:初始化一个可以缓存线程的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;
特点:在没有任务执行时,当线程的空闲时间超过keepAliveTime,默认为60s,会自动释放线程资源;当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;
因此,使用时要注意控制并发的任务数,防止因创建大量的线程导致而降低性能。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadText {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
executorService.execute(new MyThread(i));
}
executorService.shutdown();
}
}
程序结果:
3.4、newScheduledThreadPool
定时任务线程池
特定:初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。
总结:除了newScheduledThreadPool的内部实现特殊一点之外,其它线程池内部都是基于ThreadPoolExecutor类(Executor的子类)实现的。
import java.text.SimpleDateFormat;
import java.util.Date;
public class ScheduledThread extends Thread {
private int i;
public ScheduledThread(int in) {
this.i = in;
}
@Override
public void run() {
while (true) {
try {
this.sleep(2000);
} catch (InterruptedException E) {
E.printStackTrace();
}
//SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println(currentThread().getName()+"打印编号:"+i+"======>"+date);//答应当前时间
}
}
}
编写验证的主程序,设置线程池有5个线程可用,:
import java.util.concurrent.ScheduledThreadPoolExecutor;
public class ScheduledThreadTest {
public static void main(String[] args) {
ScheduledThreadPoolExecutor atpe = new ScheduledThreadPoolExecutor(5);//设置线程个数
for (int i = 0; i < 5; i++) {
atpe.execute(new ScheduledThread(i));//普通的提交方式,只提交一次,执行结束,线程不会退出。
}
}
}
通过实验我们发现,本实验例程需要创建的线程数应小于等于线程池的线程容量,否则线程不会回收。具体表现在,当 for (int i = 0; i < 5; i++) 修改为 for (int i = 0; i <10 i++) 以后,仍然只有前5个线程执行,因为线程循环执行,会一直占用线程池的资源。
为了验证这一猜想我们将程序修改如下
import java.util.Date;
public class ScheduledThread extends Thread {
private int i;
public ScheduledThread(int in) {
this.i = in;
}
@Override
public void run() {
Date date = new Date();
System.out.println(currentThread().getName()+"打印编号:"+i+"======>"+date);//答应当前时间
}
}
//主程序如下:
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadTest {
public static void main(String[] args) {
ScheduledThreadPoolExecutor atpe = new ScheduledThreadPoolExecutor(4);//设置线程个数
for (int i = 0; i < 5; i++) {
atpe.execute(new ScheduledThread(i));
}
}
}
得到如下结果:
程序正常结束,且线程3被重复利用,并没达到线程池的最大容量4。
我们可以这样认为,newScheduledThreadPool这线程池可以使只执行一遍的线程以一定速率循环执行,但是如果以execute方式提交线程则不会重复执行。
我们对程序作出如下修改,使线程只执行一次:
import java.util.Date;
public class ScheduledThread extends Thread {
private int i;
public ScheduledThread(int in) {
this.i = in;
}
@Override
public void run() {
Date date = new Date();
System.out.println(currentThread().getName()+"打印编号:"+i+"======>"+date);//答应当前时间
}
}
同时主程序修改为:
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadTest {
public static void main(String[] args) {
ScheduledThreadPoolExecutor atpe = new ScheduledThreadPoolExecutor(4);//设置线程个数
for (int i = 0; i < 5; i++) {
//参数1:initialDelay表示首次执行任务的延迟时间,参数2:period表示每次执行任务的间隔时间,参数3:TimeUnit.MILLISECONDS执行的时间间隔数值单位
atpe.scheduleAtFixedRate(new ScheduledThread(i),1000,2000,TimeUnit.MILLISECONDS);//以固定频率重复执行线程
}
}
}
可以得到类似的结果:
我们可以发现线程2实现了重复利用,虽然创建的线程是一次执行,但却实现了重复执行的效果,这就是该线程池最大的特点。