今天主要学习JAVA多线程相关的知识,在介绍多线程之前还是先回顾一下线程相关的基础知识:
一、基本概念
关于进程和线程:
进程是指正在运行的程序,拥有独立的内存空间和系统资源
线程是进程内的执行单元,进程内的线程共享内存空间和系统资源,多线程不能提高程序的执行速度,但能够提高应用程序的使用率
进程与线程差异的核心在于是否共享内存空间和系统资源,且存在包含关系
关于并发和并行:
并行是指逻辑上同时发生,即某一时间内同时运行多个程序,并发指物理上同时发生,指在一个时间点同时运行多个程序
二、多线程创建方法
JAVA的多线程是通过调用C/C++底层的程序来实现的,实现方式有两种:
方式1、继承Thread父类,并重写Thread方法中的run函数
public class MyThread extends Thread {
public void run() {
System.out.println("这是多线程");
}
}
public static void main(String[] args) {
MyThread mt = new MyThread(); //直接创建线程,执行start()即可
mt.start();
}
方式2、创建类实现Runnable接口,并将该类作为线程的传入参数
public class MyRunnable implements Runnable {
public void run() {
System.out.println("方式2创建线程");
}
}
public static void main(String[] args) { MyRunnable mr = new MyRunnable(); Thread t = new Thread(mr); t.start(); }
方式2比方式1的优势在于:1、仅实现接口,解决了多继承难题
2、实现代码分离,面向对象的思想(如卖理财系统,一个runnable实现类即可实现多线程卖理财)
三、几个实用的线程方法:
3.1 基本函数
public final String getName()
获取线程名称,返回线程字符串
public final void setName(String)
设定线程名
Thread.currentThread().getName()
获取当前线程的线程名(静态函数的链式表示)
3.2 线程优先级设置
public final int getPriority()
获取当前线程优先级1-10
public final void setPriority(int )
更改当前线程优先级1-10
3.3 线程状态控制
public static void sleep(long millis)
线程休眠:以毫秒为单位暂停执行一段时间
public final void join()
线程加入,将线程设置为优先执行,join后的线程执行完后才会执行其他线程
public static void yield()
线程礼让,线程暂时让出,即瞬间礼让行为,此时仍然可能抢到资源
public final void setDaemon(Boolean on)
将线程标记为守护线程,守护线程的含义是
用于守护别人的线程,当进程只有守护线程存在时,守护线程直接死亡
public final void stop()
public final void interrupt()
中断线程,stop()让线程强制停止,已过时,现在常用interrupt(),通过跑出InterruptedException异常进行处理,可执行后续的catch代码和try代码块后的代码
3.4 线程同步控制
synchronized(Object o){}
1、同步代码块关键字,对象o作为同步锁,
需要使用相同的对象才能锁上,对象类型不限
2、可以直接放在方法前全局修饰符后直接修饰方法,
其同步方法锁对象为this,而静态方法的锁为当前类的class对象
Lock lock = new ReentrantLock()
1、JDK5的lock锁对象及其具体实现,比synchronized更明确加锁及放锁时机(synchronized也够用了)
2、解锁lock.unlock()步骤一般放在finally代码块中,确保释放锁,此时可以没有catch代码块
3.5 线程通信
虽然存在线程锁的机制保证数据的线程安全,但由于线程抢占资源是随机的,对于线性流程会造成同一步骤多次争抢资源导致浪费,所以需要线程间的通信以提高效率
以下方法与synchronized方法结合使用,通过改变使用相同锁的代码块的状态实现线程通信,这些方法均在Object类中
public void wait()
public void notify()
public void notifyall()
wait()方法表示让出自身线程的控制权,在不收到notify()指令时不再抢占系统资源
notify()方法表示自身执行完毕,通知其它线程不再进行等待(这时自身仍会抢占资源,所以需要在抢占到时执行wait()操作让出控制权)
notifyall()方法则会唤起所有该锁相关的等待线程,可以视具体情况进行使用
双线程时的流程图如下:
四、线程的生命周期:
正常的线程步骤有如下几种:
新建:创建线程对象
就绪:有执行资格,没有执行权
运行:有执行资格,有执行权
阻塞:没有执行资格、没有执行权,可激活进入就绪态
死亡:线程对象编程垃圾,等待被回收
线程的周期如下图所示:
五、线程组与线程池:
5.1线程组相关方法:
线程组的表示类为:TreadGroup,可以自行创建线程组,并统一设置优先级或守护线程属性等
Thread类包含以下线程方法
public String getThreadGroup()
public Thread(ThreadGroup tg, Runnable targer, String name)
将线程归类到特定组
5.2 线程池的方法:
线程池的创建,通过Excecutor工厂类执行以下静态方法
public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
上述方法只会区分线程池的线程数量差别,即无限制、设定数量、单一数量
线程池函数只能执行Runnable或Callable对象代表的线程,调用过程如下:
public <T> submit(Runnable) //or Callable
传入接口直接开始线程
public void shutdown()
结束线程池,如果不执行shutdown函数则程序不会自动退出
5.3 关于Callable:
和Runnable的差别在于存在返回值,使用Callable属于实现多线程的第三种方法,依赖线程池而存在,需要重写执行函数如下:
public Future<T> call()
Callable可以完全采用和Runnabl相同的方式建立对象,但当需要返回值时,需要建立带参构造函数,下面举例,求1到n的数的和:
public class AddCallableExample implements Callable<Integer> {
private int num; //求和的值
public AddCallableExample(int num){
this.num = num;
}
public Integer call() throws Exception {
int sum = 0;
for(int i = 1;i<=num;i++){
sum +=i;
}
return sum; //返回总和
}
}
这时,submit的返回值即为call函数的结果需要建立Future对象作为返回值的接收容器
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService ec = Executors.newFixedThreadPool(2);
Future<Integer> f = ec.submit(new AddCallableExample(100));
System.out.println(f.get()); //返回为5050
}
5.4 定时器
首先需要建立一个Timer类来进行触发,传入TimerTask抽象类,并通过重写TimerTask的run()方法实现:
public class TaskExample extends TimerTask {
private Timer t; //必须传入参数t,方便后续结束Timer
public TaskExample(Timer t){
this.t=t;
}
public void run() {
System.out.println("定时处理~~~");
t.cancel(); //完成操作,退出t
}
}
主要流程如下
Timer t = new Timer();
t.schedule(new TaskExample(),3000); //只执行一次
t.schedule(new TaskExample(),3000,2000); //执行多次,若执行多次,不能进行cancel操作