1.Java多线程和并发编程
多线程和多进程简介
多进程概念
- 当前操作系统都是多任务OS
- 每个独立执行的任务就是一个进程
- OS将时间划分为多个时间片(时间很短)
- 每个时间片将CPU分配给某个任务,时间片结束,CPU将自动回收,再分配给另外任务。从外部来看,所有任务是同时在进行。但是在CPU上,任务是按照串行依次运行(单核CPU)。如果是多核,多个进程任务可以并行。单核上,多进程只能串行操作。
多进程的优点
- 可以同时运行多个任务
- 程序因IO堵塞时,可以释放CPU,让CPU为其他程序服务(提高CPU利用率),让CPU为其他程序提供服务
- 当系统有多个CPU时,可以为多个程序同时服务
- CPU的频率不再提高,而是提高核数
多进程缺点
- 太笨重,不好管理,不好切换
多线程概念
- 一个程序可以包括多个子任务,可串/并行
- 每个子任务可以称为一个线程
- 如果一个子任务阻塞,程序可以将CPU调度另外一个子任务进行工作。这样CPU还是保留在本程序中,而不是被调度到别的程序(进程)去,这样,提高本程序所获得CPU时间和利用率。
多进程和多线程的区别
- 线程共享数据
- 线程通讯更高效
- 线程更轻量级,更容易切换
- 多个线程更容易管理
2.Java多线程的实现
多线程的创建有两种方式
java.lang.Thread
线程继承Thread类,实现run方法
java.lang.Runnable
线程实现Runnable接口,实现run方法
Java的四个主要接口:
Clonable:用于对象克隆
Comparable:用于对象比较
Serializable:用于对象序列化
多线程启动
- star方法,会自动以新进程调用run方法
- 直接调用run方法,将会变成串行执行
- 同一个线程,多次star会报错,只执行一次star方法
- 多个线程启动,其启动顺序是随机的
- 线程无需关闭,只要其run方法执行结束后,自动关闭
- main函数(线程)可能早于新线程结束,整个程序并不终止
- 整个程序终止是等所有的线程都终止(包括main函数线程)
多线程实现对比
Thread和Runnable
- Thread占据了父类名额,不如Runnable方便
- Thread类实现Runnable
- Runnable启动时需要Thread类的支持
- Runnable更容易实现多线程资源共享
Runnable方式:
-
通过实现Runnable接口来创建线程
-
实现Runnable的对象必须包装在Thread类里面,才可以启用
- 通过start方法来启动线程的run方法
3.多线程信息共享
- 线程类
- 通过继承Thread或实现Runnable方法
- 通过start方法,调用run方法,run方法工作
- 线程run结束后,线程退出
- 粗粒度:子线程与子线程之间、和main线程之间缺乏交流
- 细粒度:线程之间有信息交流通讯
- 通过共享变量达到信息共享
- JDK原生库暂不支持发送消息(类似MPI并行库直接发送消息)
通过共享变量在多个线程中共享消息
static变量
同一个Runnable类的成员变量
多线程信息共享问题
-
工作缓存副本
-
关键步骤缺乏枷锁限制(在买票系统中卖出门票后票数减一就是关键步骤)
-
例如 i++,并非原子性操作
- 读取主存i(正本)到工作缓存中(副本)中
- 每个CPU执行(副本)i+1操作
- CPU将结果写入到缓存(副本)中
- 数据从工作缓存(副本)刷到主存(正本)中
线程只依赖于工作缓存
变量副本解决问题的方法
-采用volatile关键字修饰变量
-保证不同线程对共享变量操作时的可见性
关键步骤枷锁限制
-互斥:某一个线程运行一个代码段(关键区),其他线程不能同时运行这个代码段
-同步:多个线程运行,必须按照某一种规定的先后顺序来运行
-互斥时同步的一种特例
互斥的关键字时synchronized
-synchronized代码块/函数,只能一个线程进入
-synchronized加大性能负担,但是使用简便
volatile实例代码:
public class ThreadDemo2{
public static void main(String args[]) throws Exception{
TestThreath2 t = new TestThread2();
t.start();
Thread.sleep(2000);
t.flag = false;
System.out.println("main thread is exiting");
}
}
class TestThread2 extend Thread{
volatile boolean flag = ture; //用volatile修饰的变量可以及时在各线程里面通知
public void run(){
int i = 0;
while(true){
i++;
}
System.out.println("test thread3 is exiting");
}
}
volatile修饰的变量,一旦被修改,所有的线程是可以立刻发现的;
synchronized实例代码:
public class ThreadDemo3{
public static void main(String[] args){
TestThread3 t = new TestThread3();
new Thread(t,"Thread-0").start();
new Thread(t,"Thread-1").start();
new Thread(t,"Thread-2").start();
}
}
class TestThread3 implements Runnable{
private volatile int tickets = 100;//多个线程共享数据
String str = new String("");
public void run(){
while(true){
sale();
try{
Thread.sleep(100);
}catch(Exception e){
System.out.println(e.getMessage());
}
if(tickets <= 0){
break;
}
}
}
public synchronized void sale(){ //同步函数
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "is saling ticket" + tickets--);
}
}
}
synchronized加锁必须加在某一个对象上,只要变量不为空;可以修饰代码块,也可以修饰函数。
4. Java多线程管理
线程状态
NEW刚创建(new)
RUNNABLE就绪态(start)
RUNNING运行中(run)
BLOCK阻塞(sleep)
TERMINATED结束
- Thread的部分API已经废弃
- 暂停和恢复 suspend/resume
- 消亡 stop/destroy
- 线程阻塞/和唤醒
- sleep,时间一到,自己会醒来
- wait/notify/notifyAll,等待,需要别人来唤醒
- join,等待另外一个线程结束
- interrupt ,向另外一个线程发送中断信号,该线程接收到信号,会触发InterruptedException(可解除阻塞),并进行下一步处理
例:生产者消费者问题
线程被动地暂停和终止
-依靠别的线程来拯救自己
-没有及时释放资源
线程主动暂停和终止
-定期监测共享变量
-如果需要暂停或者终止,先释放资源,再主动动作
-暂停:Thread.sleep(),休眠
-终止:run方法结束,线程终止
interrupted()是Thread类的方法,用来测试当前线程是否收到一个INTERRUPT的信号。如果收到,该方法返回true,否则返回false;而interrupted默认值为false。
多线程死锁
-每个线程互相持有别人需要的锁
-预防死锁,对资源进行等级排序
//Time是JDK5引入的新类,位于java.util.concurrent包中。它提供了时间单位粒度和一些时间转换、计时和延迟等函数。
守护后台线程
-普通线程的结束,是run方法运行结束
-守护线程的结束,是run方法运行结束,或main函数结束
-守护线程永远不要访问资源,比如文件或者数据库等
总结
- 了解线程的多个状态
- 了解线程协作机制
- 线程协作机制尽量简单化,采用粗粒度协作
- 了解死锁和后台线程概念
- 使用jvisualvm查看线程情况
- 尽量使用定时监控变量的方式来进行自我状态控制