本篇博客主要通过以下几个方面向大家阐述JAVA中的多线程内容,可能篇幅比较长,希望大家能有耐心的读下去。
在实际应用中, 多线程非常有用。例如, 一个浏览器可以同时下载几幅图片。一个 Web服务器需要同时处理几个并发的请求。
程序 、进程、线程分别是什么,及其他们的包含关系
Java实现多线程三种方式
线程的状态和方法
线程基本信息和优先级
线程的同步和死锁问题
生产者消费者模式
任务调度
线程概念:
程序(指令集):Program,是一个静态的概念
进程(调度程序):Process,是一个动态的概念,操作系统调度程序
- 进程是程序的一次动态的执行过程,占用特定的地址空间。
- 每个进程都是独立的,由3部分组成cpu,data,code
- 缺点:内存的浪费,cpu的负担。
线程(进程内多条执行路径):Thread,是进程中一个“单一的连续控制流程“(a single sequential flow of control)/执行路径
- 线程又被称为轻量级进程(lightweight process)
- Threads run at the same time,independently of one another.
- 一个进程可拥有多个并行的线程
- 一个进程中的线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且他们从同一堆中分配对象,通信、数据交换、同步操作
- 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简单而且信息传递速度也更快。
线程和进程的区别:
- 根本区别:进程作为资源分配的单位。线程作为调度和执行的单位。
- 开销:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器,线程切换开销较小。
- 所处环境:进程在操作系统中能同时运行多个任务(程序)。线程在同一应用程序总有多个顺序流同时执行。
- 分配内存:系统在运行的时候会为每个进程分配不同的内存区域。线程除了CPU之外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。
- 包含关系:没有线程的进程是可以被看做单线程的,如果一个进程内拥有多个线程,则执行过程不是一条直线的,而是多条线(线程)共同完成。线程是进程的一部分,所以线程有的时候被称为是轻量级进程或轻权进程。
如果有想更加深入的了解进程和CPU原理的的可以查看相关操作系统原理的书籍。
静态代理设计模式
在介绍创建线程的方法之前,首先给大家介绍一个设计模式-------静态代理设计模式。可能很多人都用过这个设计模式,但是仅是自己不知道而已。其实在线程的创建过程中,就用到了这个设计模式。任何一个学过JAVA的人可能对线程的创建及使用都不会陌生。
我首先给大家介绍一下静态代理设计模式,此模式由三部分组成,分别是真实角色,代理角色,和两者共同实现的接口。该模式并不是很难理解。打个比方说,我们找房子的时候,大部分时候需要通过中介公司找到适合自己房子,其实中介就扮演着一个代理的角色,与房东进行沟通。再比方说某人结婚的时候,也是需要通过婚庆公司完成婚前婚后一些事物的处理等等,而婚庆公司就是一个代理。现在大家应该对静态代理设计模式在概念上有了一个初步认识与理解。下面我将从代码给大家详细的解释什么静态代理模式。
首先我们需要定义一个接口Marry,并且在接口中定义marry()方法。分别创建扮演真实角色的类和扮演代理角色的类并且都是要实现刚才定义的接口,重写marry()方法。然后在代理类中再增加一些你需要的方法即可。那么静态代理模式就基本上写好了。没错,就是这么简单。下面是具体的代码,如有什么疑问,请给我留言吧。
/**
* 静态代理设计模式
* 1. 真是角色
* 2. 代理角色:持有真实角色的引用
* 3. 二者实现相同的接口
*/
public class StaticProxy {
public static void main(String[] args) {
You you=new You();
WeddingCompany weddingCompany = new WeddingCompany(you);
weddingCompany.marry();
}
}
//接口
interface Marry{
void marry();
}
//真实角色
class You implements Marry{
@Override
public void marry() {
System.out.println("you and 嫦娥结婚了。。。。");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry you;
public WeddingCompany() {
}
public WeddingCompany(Marry you) {
this.you = you;
}
private void before(){
System.out.println("布置洞房....");
}
private void after(){
System.out.println("闹洞房.....");
}
@Override
public void marry() {
before();
you.marry();
after();
}
}
在JAVA中实现多线程的方式有三种,第一种是继承Thread类;第二种是实现Runnable接口,第三种实现Callable接口。我认为最常用的方式是第二种。java语言中是单继承,多实现,所以第一种实现多线程方式的弊端就显而易见了,如果某类继承了Thread类就不能再继承任何其他类了。第三种方式实现多线比较的繁琐,但是call()方法可以抛异常,并且有返回值。这是前两种所没有的优势吧。
在java中负责线程的这个功能的是java.lang.Thread这个类,而且这个类也是继承Runnable接口的。
- 可以通过创建Thread的实例来创建新的线程
- 每个线程都是通过某特定Threa对象所对应的方法run()来完成其操作的,所以方法run()称为线程体
- 通过调用Thread类的start()方法来启动一个线程
- 继承Threa类方式的缺点:那就是如果我们的类已经从一个类继承,则无法再继承Threa类
通过Runnab接口实现多线程(推荐)
- 优点:可以同时实现继承。实现Runnable接口方式更通用一些,避免了单继承
- 方便共享资源,同一份资源多个代理访问
- 使用方法:类实现Runnable接口,重写run()方法(真实角色类)
启动多线程,使用静态代理
创建真实角色
创建代理角色+真实角色引用
调用start()启动线程
具体代码
public class ProgrammerApp {
public static void main(String[] args) {
Programmer programmer=new Programmer();
Thread t = new Thread(programmer);
t.start();
for (int i=0;i<1000;i++){
System.out.println("聊天............");
}
}
}
public class Programmer implements Runnable {
@Override
public void run() {
for (int i=0;i<1000;i++){
System.out.println("写代码...........");
}
}
}
上面代码是通过实现Runnable接口来创建线程的,而且你会感觉到你在不知不觉中使用了------静态代理的设计模式。为什么这么说呢?请大家仔细看上面的代码,Programmer实现了Runnable接口,刚才在上面我已经说过Thread类也实现了Runnable接口,这就符合静态代理的第一个要求-----实现同一个接口。在main()方法中我们将Programmer的实例作为参数传入到了Thread类中,所以我们的Programmer扮演真实角色,Thread扮演代理角色。最后调用Thread类的start()方法启动线程。
这里所说的启动并非一运行代码,该线程就开始运行了。因为线程是不是运行,要看CPU什么时候能调到它,并且给它分配时间片,这时它才会运行run()方法中的任务代码,当所分配的给某线程的时间用完了,该线程就进入了非活动状态
通过Callable接口实现多线程
优点:可以获取返回值
缺点:繁琐
Callable和Future接口
Callable是类似与Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务;
Callable和Runnable有几点不同:
- Callable规定的方法是call(),而Runnable规定实现的方法是run(0;
- Call()方法可以抛出异常,而run()方法不能抛出异常
- Callable的任务执行后可返回值,运行Callable任务可拿到一个Future对象,而Runnable的任务是不能返回值的。Future表示异步计算的结果,它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可连接任务执行的情况,可取消任务的执行,还可获取任务执行的结果
具体思路:
- 创建Callable实现类+重写call()方法
- 借助执行调度服务 ExecutorService,获取Future对象
ExecutorService ser = Executors.newFixedThreadPool(2);
Future<Integer> result=ser.submit(tortoise);
int num=result.get();
ser.shutdownNow();
具体代码如下
public class Call {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程
ExecutorService ser = Executors.newFixedThreadPool(2);
Race tortoise=new Race("乌龟",1000);
Race rabbit = new Race("兔子", 500);
//获取值
Future<Integer> result=ser.submit(tortoise);
Future<Integer> result2=ser.submit(rabbit);
Thread.sleep(5000);
//停止线程体循环
tortoise.setFlag(false);
rabbit.setFlag(false);
int num=result.get();
int num2=result2.get();
System.out.println(num);
System.out.println(num2);
//停止服务
ser.shutdownNow();
}
}
class Race implements Callable<Integer>{
private String name;
private long time;
private boolean flag=true;
private int step=0;
public Race() {
}
public Race(String name) {
this.name = name;
}
public Race(String name, long time) {
this.name = name;
this.time = time;
}
@Override
public Integer call() throws Exception {
while (flag){
Thread.sleep(time);
step++;
}
return step;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
}
线程的状态
- 新生状态----New
- 就绪状态----Runnable
- 运行状态
- 阻塞状态----Blocked
阻塞线程的方式有如下三种
(1)join()等待该线程终止
/**
* join合并线程
*/
public class JoinDemo extends Thread {
public static void main(String[] args) throws InterruptedException {
JoinDemo demo = new JoinDemo();
Thread t = new Thread(demo);//新生
t.start();//就绪
//cpu调度
for (int i=0;i<100;i++){
if (50==i){
//阻塞主线程
t.join();
}
System.out.println("Main....."+i);
}
}
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("Join.....");
}
}
}
(2)yield()暂停当前正在执行的线程对象,并执行其他线程。(静态方法)
public class YieldDemo extends Thread{
public static void main(String[] args) {
YieldDemo demo=new YieldDemo();
Thread t = new Thread(demo);
t.start();
for (int i=0;i<1000;i++){
if (i%20==0){
//暂停本线程
Thread.yield();
}
System.out.println("main..."+i);
}
}
@Override
public void run() {
for (int i=0;i<1000;i++){
System.out.println("Yield........"+i);
}
}
}
(3)sleep()在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。而且休眠,并不释放锁
5. 死亡状态------Terminated
具体详细解释
线程的基本信息
- isAlive(),判断线程是否还是活动状态,即线程是否还未终止
- getPriority(),获得线程优先级的数值
- setPriority(),设置线程优先级数值
- setName(),给线程一个名字
- getName(),取得线程的名字
- currentThread(),取得当前正在运行的线程对象也就是取得自己本身
线程的优先级:优先级并不代表执行的顺序,而是代表的是概率,不是绝对的优先级,只是概率大的执行的多一点。
线程的同步(并发): 多个线程访问同一份资源,所以要确保资源的安全------线程安全
Synchronized-->同步
1. 同步块
Synchronized(引用类型|this|类.class){
}
//同步块
public void test3() {
synchronized (this) {
if (num <= 0) {
flag = false;
return;
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
System.out.println("wpn");
}
}
具体代码
2. 同步方法
//同步方法
public synchronized void test2(){
if (num <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
}
完整代码
public class SyncDemo {
public static void main(String[] args) {
//真实角色
Web12306 web = new Web12306();
//代理
Thread t1 = new Thread(web,"路人甲");
Thread t2 = new Thread(web,"黄牛乙");
Thread t3 = new Thread(web,"攻城狮");
t1.start();
t2.start();
t3.start();
}
}
public class Web12306 implements Runnable{
private int num=1000;
private boolean flag=true;
@Override
public void run() {
while (flag){
test2();
}
}
//同步块
public void test3() {
synchronized (this) {
if (num <= 0) {
flag = false;
return;
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
System.out.println("wpn");
}
}
//同步方法
public synchronized void test2(){
if (num <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
}
}
3. 死锁:过多的同步会造成死锁,
public class SyncDemo2 {
public static void main(String[] args) {
Object g=new Object();
Object m=new Object();
Test t1=new Test(g,m);
Test2 t2 = new Test2(g,m);
Thread t = new Thread(t1);
Thread t3 = new Thread(t2);
t.start();
t3.start();
}
}
class Test implements Runnable{
Object goods;
Object money;
public Test(Object goods, Object money) {
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while (true){
test();
}
}
public void test(){
synchronized (goods){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (money){
}
}
System.out.println("一手交货");
}
}
class Test2 implements Runnable{
Object goods;
Object money;
public Test2(Object goods, Object money) {
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while (true){
test();
}
}
public void test(){
synchronized (money){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (goods){
}
}
System.out.println("一手交钱");
}
}
生产者/消费者模式简介用来干嘛的?生产者/消费者模式的产生主要目的就是为了解决非同步的生产与消费之间的问题。什么是非同步呢?比如我刚刚生产了某个产品,而此时你正在打游戏,没空来取,要打完游戏来取,这就导致了我生产产品和你取产品是两个非同步的动作,你不知道我什么时候生产完产品,而我也不知道你什么时候来取。而生产者/消费者模式就是解决这个非同步问题的。
生产者消费者模式
public class App {
public static void main(String[] args) {
//共同的资源
Movie m=new Movie();
//多线程
Player p = new Player(m);
Watcher w = new Watcher(m);
new Thread(p).start();
new Thread(w).start();
}
}
/**
* 共同的资源
* 生产者消费者信号灯法
* wait:等待,释放锁
* sleep:不释放锁
* notify()/notifyAll() 唤醒
*/
public class Movie {
private String pic;
//信号灯
//flag==true,生产者生产,消费者等待,生产完成后通知消费者
//flag==false,消费者消费生产者等待,消费完成后通知生产
private boolean flag=true;
//播放
public synchronized void play(String pic){
if (!flag){//生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始生产
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产了:"+pic);
//生产完毕
this.pic=pic;
//通知消费
this.notify();
//生产者停下
this.flag=false;
}
//查看
public synchronized void watch(){
if (flag){//消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始消费
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费了:"+pic);
//消费完毕
//通知生产
this.notifyAll();
//消费停止
this.flag=true;
}
}
/**
* 生产者
*/
public class Player implements Runnable {
private Movie movie;
public Player(Movie movie) {
this.movie = movie;
}
@Override
public void run() {
for (int i=0;i<20;i++){
if (0==i%2){
movie.play("左青龙");
}else {
movie.play("右白虎");
}
}
}
}
/**
* 消费者
*/
public class Watcher implements Runnable {
private Movie movie;
public Watcher(Movie movie) {
this.movie = movie;
}
@Override
public void run() {
for (int i=0;i<20;i++){
movie.watch();
}
}
}
线程的任务调度:
Timer定时器类
TimerTask任务类
在Spring的任务调度就是通过这两个类来实现的
在这种实现方式中,Timer类实现的是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程。其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其他线程的。而TimerTask类是一个抽象类,该类实现了Runnable接口,所以该类具备多线程的能力。
在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行
在实际使用时,一个Timer可以启动任意多个TimerTask实现多线程,但是多个线程之间会阻塞。所以如果多个线程之间如果需要完全独立运行的化,最好还是一个Timer启动一个TimerTask实现
import java.util.Timer;
import java.util.TimerTask;
public class schedual {
//delay:表示执行时间
//period:表示每隔多少秒执行一次
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("很容易....");
}
},2000,200);
}
}