资讯 小学 初中 高中 语言 会计职称 学历提升 法考 计算机考试 医护考试 建工考试 教育百科
栏目分类:
子分类:
返回
空麓网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
空麓网 > 计算机考试 > 软件开发 > 后端开发 > Java

JavaSE高级 02

Java 更新时间: 发布时间: 计算机考试归档 最新发布

JavaSE高级 02

一、目前存在的问题

public class MyTest1 {
    public static void main(String[] args) {
		for(int i = 0; i < 100; i++) {
			System.out.println("吃饭");
			System.out.println("喝酒");
			System.out.println("聊天");
		}
    }
}

程序同一时刻只能做一件事情。

#二、基本概念

#2.1、程序、进程、线程

程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。

  • 运行中的QQ,运行中的MP3播放器;

  • 程序是静态的,进程是动态的;

  • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。

线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。

  • 若一个进程同一时间并行执行多个线程,就是支持多线程的;
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小;
  • 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患;
  • 每个Java程序都有一个隐含的主线程:main方法。

#2.2、多核CPU

单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。

如果是多核的话,才能更好的发挥多线程的效率,现在的服务器都是多核的。

#2.3、多线程应用场合

  • 程序需要同时执行两个或多个任务;
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等;
  • 需要一些后台运行的程序时。

#三、线程的创建和使用

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。

Thread类的特性:

  • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体;
  • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。

构造器:

  • Thread():创建新的Thread对象;
  • Thread(String threadname):创建线程并指定线程实例名;
  • Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法;
  • Thread(Runnable target, String name):创建新的Thread对象。

JDK5之前创建线程有两种方式:

  • 继承Thread类的方式;
  • 实现Runnable接口的方式。

#3.1、继承Thread类的

步骤:

  1. 定义子类继承Thread类;
  2. 子类重写Thread类中的run方法;
  3. 创建Thread子类对象,即创建线程对象;
  4. 调用线程对象start方法(启动线程,调用run方法)。
public class ChatThread extends Thread{
	@Override
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println("聊天");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class DrinkThread extends Thread {
	@Override
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println("喝酒");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class EatThread extends Thread{
	@Override
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println("吃饭");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class MyTest2 {
    public static void main(String[] args) {
        //创建线程
        ChatThread chat = new ChatThread();
		DrinkThread drink = new DrinkThread();
		EatThread drink = new EatThread();
		//启动线程
		chat.start();
		drink.start();
		drink.start();
    }
}

#3.2、实现Runnable接口

步骤:

  1. 定义子类实现Runnable接口;
  2. 子类中重写Runnable接口中的run方法;
  3. 通过Thread类构造方法创建线程对象;
  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中;
  5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
public class ChatThread implements Runnable {
	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			System.out.println("聊天");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class DrinkThread implements Runnable {
	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			System.out.println("喝酒");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class EatThread implements Runnable {
	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			System.out.println("吃饭");
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class MyTest2 {
    public static void main(String[] args) {
        Thread chat = new Thread(new ChatThread());
        Thread drink = new Thread(new DrinkThread());
        Thread eat = new Thread(new EatThread());

        chat.start();
        drink.start();
        eat.start();
    }
}

优势:

  • 避免了单继承的局限性;
  • 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

#3.3、Thread类相关方法

相关方法:

  • start():启动线程,并执行对象的run()方法;
  • run():线程在被调用时执行的操作;
  • getName():返回线程的名称;
  • setName():设置线程的名称;
  • currentThread():返回当前线程。
public class MyThread extends Thread{
    //打印100行*****
    @Override
    public void run() {
        for(int i = 0; i < 100; i++) {
            //返回线程的名称
            System.out.println("*****" + Thread.currentThread().getName() + ":" + i);
            try {
                //延时100ms
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class MyTest3 {
    public static void main(String[] args) throws InterruptedException {
        //设置主线程名称
        Thread.currentThread().setName("主线程");

        MyThread mythread = new MyThread();
        //设置自定义线程名称
        mythread.setName("子线程");
        //启动自定义线程
        mythread.start();
        //打印100行%%%%%
        for (int i = 0; i < 30; i++) {
            System.out.println("%%%%%" + Thread.currentThread().getName() + ":" + i);
            try {
                //延时100ms
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

#四、线程的调度和生命周期

#4.1、线程的调度

调度策略:

  • 基于时间片轮转;
  • 抢占式:高优先级的线程抢占CPU。

调度方法:

  • 同优先级线程组成先进先出队列,使用时间片策略
  • 对高优先级,使用优先调度的抢占式策略
  • Java中线程优先级的范围是1~10,默认的优先级是5
    • MAX_PRIORITY(10)
    • MIN_PRIORITY(1)
    • NORM_PRIORITY(5)

涉及的方法:

  • setPriority(int newPriority):设置线程的优先级
  • getPriority():获取线程的优先级
  • yield():线程让步
    • 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
    • 若队列中没有同优先级的线程,忽略此方法
  • join():
    • 当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止
    • 低优先级的线程也可以获得执行
  • sleep(long millis):令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队
  • stop():强制线程生命期结束
  • isAlive():判断线程是否还活着
public class MyTest3 {
    public static void main(String[] args) throws InterruptedException {
        Thread.currentThread().setName("主线程");

        MyThread mythread = new MyThread();
        mythread.setName("子线程");
        mythread.start();
        //设置线程的优先级
        mythread.setPriority(Thread.MAX_PRIORITY);
        //打印100行%%%%%
        for (int i = 0; i < 30; i++) {
            System.out.println("%%%%%" + Thread.currentThread().getName() + ":" + i);
            if(i == 2) {
                //Thread.currentThread().yield();
                //mythread.stop();
                mythread.join();
                System.out.println(mythread.isAlive());
            }
            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

#4.2、线程的生命周期

新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态;

就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件;

运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能;

阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态;

死亡:线程完成了它的全部工作或线程被提前强制性地中止。

#五、线程同步

public class Window implements Runnable {
    private int count = 100;
    @Override
    public void run() {
        while(true) {
            if(count > 0) {
                try {
                    Thread.currentThread().sleep(6);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
            } else {
                break;
            }
        }
    }
}

public class MyTest4 {
    public static void main(String[] args) {
        Window window = new Window();
        Thread window1 = new Thread(window);
        Thread window2 = new Thread(window);
        Thread window3 = new Thread(window);

        window1.start();
        window2.start();
        window3.start();
    }
}

现象:

  1. 多个窗口卖同一张票;
  2. 票的编号出现了0或者负值。

问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

解决思路:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

Java对于多线程的安全问题提供了专业的解决方式:同步机制。

#5.1、synchronized代码块

public class Window1 implements Runnable {
	private int count = 100;
	@Override
	public void run() {
		while(true) {
			//只剩一张票
			
			synchronized (this) {
				if(count > 0) {
					System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
				} else {
					break;
				}
			}
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

#5.2、synchronized方法

//所有窗口共用100张票
public class Window2 implements Runnable {
	private int count = 100;
	@Override
	public void run() {
		while(true) {
			sale();
			if(count <= 0) {
				break;
			}
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	//同步方法  该方法运行完毕之后,其他的线程才会运行
	public synchronized void sale() {
		if(count > 0) {
			System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
		}
	}
}

#5.3、Lock锁

从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock类实现了Lock ,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

import java.util.concurrent.locks.ReentrantLock;

public class Window3 implements Runnable {
    private int count = 100;
    private ReentrantLock lock = new ReentrantLock(true);
    @Override
    public void run() {
        while(true) {
            try {
                lock.lock();
                if(count > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }

            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

#5.4、线程安全的单例模式懒汉式

//synchronized代码块方式
public class SingleObject {
    private static SingleObject obj = null;
    
    private SingleObject() {
        
    }
    
    public static SingleObject getInstance() {
        synchronized(SingleObject.class) {
            if(obj == null) {
                obj = new SingleObject();
            }
        }
        return obj;
    }
}

//synchronized方法方式
public class SingleObject {
    private static SingleObject obj = null;
    
    private SingleObject() {
        
    }
    
    public static synchronized SingleObject getInstance() {
        if(obj == null) {
            obj = new SingleObject();
        }
        return obj;
    }
}

//Lock方式
public class SingleObject {
    private static SingleObject obj = null;
    private ReentrantLock lock = new ReentrantLock(true);
    
    private SingleObject() {
        
    }
    
    public static SingleObject getInstance() {
        try {
            lock.lock();
            if(obj == null) {
                obj = new SingleObject();
            }
        } finally {
            lock.unlock();
        }
        return obj;
    }
}

#5.5、死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

public class Lock {
	static Object objA = new Object();
	static Object objB = new Object();
}

public class MyThreadA extends Thread {
	@Override
	public void run() {
		while(true) {
			synchronized (Lock.objA) {
				synchronized (Lock.objB) {
					System.out.println("MyThreadA...");
				}
			}
		}
	}
}

public class MyThreadB extends Thread {
	@Override
	public void run() {
		while(true) {
			synchronized (Lock.objB) {
				synchronized (Lock.objA) {
					System.out.println("MyThreadB...");
				}
			}
		}
	}
}

public class MyTest5 {
	public static void main(String[] args) {
		MyThreadA a = new MyThreadA();
		MyThreadB b = new MyThreadB();
		a.setPriority(Thread.MAX_PRIORITY);
		b.setPriority(Thread.MIN_PRIORITY);
		a.start();
		b.start();
	}
}

解决方法:

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步

#六、线程通信

相关方法:

  • wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行;
  • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待;
  • notifyAll():唤醒正在排队等待资源的所有线程结束等待。

以上三个方法只有在synchronized方法或synchronized代码块中才能使用。

两个线程,交替打印1~100。

public class PrintNum implements Runnable {
    int i = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (Object.class) {
                //唤醒正在排队等待同步资源的线程中优先级最高者结束等待
                Object.class.notify();
                if (i <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + i++);
                } else
                    break;
                try {
                    //令当前线程挂起并放弃CPU、同步资源并等待
                    Object.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

public class MyTest6 {
    public static void main(String[] args) {
        PrintNum printNum = new PrintNum();

        Thread th1 = new Thread(printNum);
        Thread th2 = new Thread(printNum);

        th1.start();
        th2.start();
    }
}

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个线程——即所谓的生产者和消费者——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。

import java.util.ArrayList;
import java.util.List;

//仓库
public class Factory {
	//最大容量
	public static final int MAX_SIZE = 100;
	private static List list = new ArrayList();
	
	public static List getList() {
		return list;
	}
	
	public static void setList(List list) {
		Factory.list = list;
	}
}

public class Producer extends Thread {
	@Override
	public void run() {
		while(true) {
			synchronized (Factory.getList()) {
				//仓库没有满
				if(Factory.getList().size() < Factory.MAX_SIZE) {
					//生产
					Factory.getList().add(new Object());
					System.out.println(Thread.currentThread().getName() + ":生产t" + "总数量:" + Factory.getList().size());
					Factory.getList().notifyAll();
					try {
						Thread.sleep(2);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} else {//仓库已满
					try {
						System.out.println(Thread.currentThread().getName() + ":仓库已满,取消生产任务t" + "总数量:" + Factory.getList().size());
						Factory.getList().wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
}

public class Consumer extends Thread {
	@Override
	public void run() {
		while(true) {
			synchronized (Factory.getList()) {
				Factory.getList().notifyAll();
				//有商品
				if(Factory.getList().size() > 0) {
					//消费商品
					Factory.getList().remove(0);
					System.out.println(Thread.currentThread().getName() + ":消费t" + "总数量:" + Factory.getList().size());
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} else { //无商品
					try {
						System.out.println(Thread.currentThread().getName() + ":仓库已空,取消消费任务t" + "总数量:" + Factory.getList().size());
						Factory.getList().wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
}

public class MyTest7 {
	public static void main(String[] args) {
		Producer p = new Producer();
		p.start();
		Consumer c1 = new Consumer();
		Consumer c2 = new Consumer();
		c1.start();
		c2.start();
	}
}
 

#七、JDK5.0新增线程创建方式

#7.1、实现Callable接口

与使用Runnable相比, Callable功能更强大些

  • 相比run()方法,可以有返回值;
  • 方法可以抛出异常;
  • 支持泛型的返回值;
  • 需要借助FutureTask类,比如获取返回结果。

关于Future接口

  • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等;
  • FutrueTask是Futrue接口的唯一的实现类;
  • FutureTask同时实现了Runnable、Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
import java.util.concurrent.Callable;


public class NumThread implements Callable {

    //重写call方法
    @Override
    public Integer call() throws Exception {
        //1+2+3+4+...+100
        int sum = 0;
        for(int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

public class MyTest8 {
    public static void main(String[] args) {
        NumThread numThread = new NumThread();
        FutureTask task = new FutureTask<>(numThread);
        //创建线程
        Thread thread = new Thread(task);
        //启动线程
        thread.start();
        //设置线程优先级
        thread.setPriority(Thread.MAX_PRIORITY);

        try {
            Integer sum = task.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

#7.2、使用线程池

#7.2.1、存在问题及解决思路

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。

好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理:
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

#7.2.2、线程池相关API

JDK 5.0起提供了线程池相关API:ExecutorService和Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

  • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
  • Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
  • void shutdown():关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

  • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
  • Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
  • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
  • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
public class NumberThread implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class NumberThread1 implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if(i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}


public class MyTest9{
    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        NumberThread numberThread = new NumberThread();
        NumberThread1 numberThread1 = new NumberThread1();

        //执行线程
        service.execute(numberThread);
        service.execute(numberThread1);

        //关闭线程
        service.shutdown();
    }
}
转载请注明:文章转载自 http://www.konglu.com/
免责声明:

我们致力于保护作者版权,注重分享,被刊用文章【JavaSE高级 02】因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理,本文部分文字与图片资源来自于网络,转载此文是出于传递更多信息之目的,若有来源标注错误或侵犯了您的合法权益,请立即通知我们,情况属实,我们会第一时间予以删除,并同时向您表示歉意,谢谢!

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2023 成都空麓科技有限公司

ICP备案号:蜀ICP备2023000828号-2