第12篇-JAVA 多线程
- 每篇一句 :不要只看到艰难,要看艰难后面的胜利
- 初学心得: 敢于尝试,就等于你已经向成功迈出了第一步
- (笔者:JEEP/711)[JAVA笔记 | 时间:2017-04-20| JAVA 多线程 ]
1.进程与线程
1.什么是进程
- 程序是指令和数据的有序的集合,其本身没有任何运行的含义,是一个静态的概念
- 进程是一个具有一定独立功能的程序,一个实体
- 几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程
- 当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程
2.进程的状态: 进程执行时的间断性,决定了进程可能具有多种状态,事实上,运行中的进程具有
- 以下三种基本状态:
- 1.就绪状态(Ready)
- 2.运行状态(Running)
- 3.阻塞状态(Blocked)
3.线程
- 线程实际上是进程基础之上的进一步划分,一个进程启动之后,里面的若干程序,又可以划分成若干个线程
- 线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换
- 并发执行,一个进程最少有一个线程(单线程程序)
4.线程实现的两种方式
- 在java中如果想要实现多线程操作,两种实现方法
- 1.一种是继承Thrcad类
- 2.另一种是实现Runnable接口
5.多线程编程的优势
- 进程间不能共享内存,但线程之间共享内存非常容易
- 系统创建进程需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高
- Java语言内置的多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编程
2.继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就是代表了线程需要完成的任务
因此,我们经常把run方法称为线程执行体
(2)创建Thread子类的实例,即创建了线程对象
(3)调用线程对象的start方法来启动该线程
(4)join线程:Thread提供了让一个线程等待另一个线程完成的方法:join() 方法
当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join方法加入的join线程完成为止
join()方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作
3.实现Runnable接口创建线程类
(1)定义Runnable接口的实现类,并重写该接口的run方法,该run方法的方法体同样是该线程的线程执行体
(2)创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
(3)调用线程对象的start方法来启动该线程
4.线程睡眠
如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态
则可以通过调用Thread类的静态sleep方法,sleep方法有两种重载的形式:
- static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度和准确度的影响
- static void sleep(long millis, int nanos):让当前正在执行的线程暂停millis毫秒加nanos毫微妙,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度和准确度的影响
5.两种线程方式的对比
采用实现Runnable接口方式的多线程:
- 线程类只是实现了Runnable接口,还可以可以继承其他类
- 在这种方式下,可以多个线程共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况
- 从而可以将CPU,代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想
- 劣势:编程稍稍复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法
采用继承Thread类方式的多线程:
- 劣势:因为线程类已经继承了Thread类,所以不能再继承其他父类
- 优势:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获得当前线程
6.线程生命周期
线程声明周期:
- 就绪状态
- 执行状态
- 阻塞状态
7.线程安全问题
多条线程并发修改共享资源就容易引发线程安全问题
使用同步解决多线程共享数据出现安全问题
多个线程有可能同时处理同一个资源
线程同步就是指多个线程在同一个时间段内只能有一个线程执行指定代码
其他线程等待此线程完成之后才可以继续执行
同步代码块synchronized(要同步对象){要同步的操作}
同步方法public synchronized void method(){要同步的操作}
同步代码会带来性能降低的问题,提高数据的安全性,牺牲性能来保证安全
同步代码块
Java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块
synchronized后括号里的obj就是同步监视器,上面代码的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定
同步方法
Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,则该方法成为同步方法
对于同步方法而言,无需显式指定同步监视器,同步方法的同步监视器是this,也就是该对象本身
线程死锁
过多的同步有可能死锁,死锁的操作一般是在程序运行的时候才有可能出现
多线程中要进行资源的共享,就需要同步,但同步过多,就可能造成死锁
8.线程创建启动实例
<br> package cn.jeep; <br> //继承Thread类 <br> public class XianChengDemo extends Thread{ <br> private int i;//私有成员属性 <br> //重写run方法,run方法的方法体就是线程执行体 <br> public void run(){ <br> for(;i<100;i++){ <br> //当线程类继承Thread类时,直接使用this.及格获取当前线程 <br> //Thread对象的getNname()返回当前线程名字 <br> //因此可以直接调用getName方法返回当前线程名字 <br> System.out.println(getName()+" "+i); <br> } <br> } <br> //主方法入口 <br> public static void main(String[] args) { <br> for(int i=0;i<100;i++){ <br> //调用Thread的currenTread方法获取当前线程 <br> System.out.println(Thread.currentThread().getName()+" "+i); <br> if(i == 20){ <br> //创建并启动第一个线程 <br> new XianChengDemo().start(); <br> //创建并启动第二个线程 <br> new XianChengDemo().start(); <br> } <br> } <br> } <br> } <br> ``` <br>``` <br> package cn.runnable; <br> //声明一个类 <br> public class RunnableDenmo implements Runnable{ <br> private int i;//私有属性 <br> //重写run方法 <br> public void run(){ <br> for(;i<100;i++){ <br> //当线程类实现Runnable接口时 <br> //如果想获取当前线程,只能用Thread.currentTread()方法 <br> System.out.println(Thread.currentThread().getName()+" "+i); <br> } <br> } <br> //主方法 <br> public static void main(String[] args) { <br> for(int i=0;i<100;i++){ <br> System.out.println(Thread.currentThread().getName()+" "+i); <br> if(i == 20){ <br> RunnableDenmo st = new RunnableDenmo(); <br> //通过new Thread(target,name)方法创建新线程 <br> new Thread(st,"新线程1").start(); <br> new Thread(st,"新线程2").start(); <br> } <br> } <br> } <br> } <br>``` <br>``` <br> package cn.callable; <br> import java.util.concurrent.Callable; <br> import java.util.concurrent.FutureTask; <br> public class CallableDemo { <br> public static void main(String[] args) { <br> //创建Callable对象 <br> @SuppressWarnings("unused") <br> CallableDemo cl = new CallableDemo(); <br> //先使用Lambda表达式创建Callable<Integer&对象 <br> //使用FutureTask来包装Callable对象 <br> FutureTask<Integer& task = new FutureTask<Integer&((Callable<Integer&)()-&{ <br> int i = 0; <br> for(;i<100;i++){ <br> System.out.println(Thread.currentThread().getName()+"的循环变量的i的值:"+i); <br> } <br> //call()方法可以有返回值 <br> return i; <br> }); <br> for(int i=0;i<100;i++){ <br> if(i==20){ <br> //实质还是以Callable对象来创建并启动线程 <br> new Thread(task,"有返回值得线程").start(); <br> } <br> } <br> try{ <br> //获取线程返回值 <br> System.out.println("子线程的返回值:"+task.get()); <br> }catch(Exception ex){ <br> ex.printStackTrace(); <br> } <br> } <br> } <br>```
9.线程同步实例
<br> package cn.tongbu; <br> public class TB { <br> public static void main(String[] args) { <br> TbDemo tb = new TbDemo(); <br> Thread t1 = new Thread(tb,"小明"); <br> Thread t2 = new Thread(tb,"小白"); <br> Thread t3 = new Thread(tb,"小红"); <br> Thread t4 = new Thread(tb,"小黑"); <br> Thread t5 = new Thread(tb,"小华"); <br> t1.start(); <br> t2.start(); <br> t3.start(); <br> t4.start(); <br> t5.start(); <br> } <br> } <br> class TbDemo implements Runnable{ <br> Object obj = new Object();//同步得标记 <br> @Override <br> public void run() { <br> //线程同步代码块 <br> //synchronized(obj){ <br> //} <br> say();//调用同步方法 <br> } <br> /** <br> * 同步方法 <br> */ <br> public synchronized void say(){ <br> System.out.println(Thread.currentThread().getName()+"正在通话中...."); <br> try { <br> Thread.sleep(2000);//2秒 <br> } catch (InterruptedException e) { <br> e.printStackTrace(); <br> } <br> System.out.println(Thread.currentThread().getName()+"通话结束!"); <br> } <br> } <br> ```
10.线程死锁实例
<br> package cn.sisuo; <br> public class SiSuoDemo { <br> public static void main(String[] args) { <br> new Ss(); <br> } <br> } <br> //警 <br> class XingJing{ <br> //同步方法 <br> public synchronized void say(FeiTu f){ <br> System.out.println("警方说先放人质,在实行抓捕"); <br> f.dos(); <br> } <br> public synchronized void dos(){ <br> System.out.println("同意匪徒要求,在实行抓捕"); <br> } <br> } <br> //匪徒 <br> class FeiTu{ <br> //同步方法 <br> public synchronized void say(XingJing x){ <br> System.out.println("匪徒说先找一辆车,在放人质"); <br> x.dos(); <br> } <br> public synchronized void dos(){ <br> System.out.println("先找一辆车,再放人质"); <br> } <br> } <br> /** <br> * 死锁线程 <br> * @author JEEP-711 <br> * <br> */ <br> class Ss implements Runnable{ <br> XingJing xj = new XingJing(); <br> FeiTu ft = new FeiTu(); <br> //模拟线程死锁构造方法 <br> public Ss(){ <br> new Thread(this).start();//为了快速实现 <br> ft.say(xj); <br> } <br> @Override <br> public void run() { <br> xj.say(ft); <br> } <br> } <br> ```
11.多线程同步银行取钱实例
<br> package cn.chinabank;//中国银行包 <br> //声明一个账户类 <br> public class Account { <br> private String name;//私有属性-姓名 <br> private double number=0.0;//私有属性-余额 <br> //构造方法并传值 <br> public Account(String name,double number){ <br> this.name = name;//当前姓名 <br> this.number = number;//当前金额 <br> } <br> //取得getName方法 <br> public String getName() { <br> return name; <br> } <br> //设置setName方法 <br> public void setName(String name) { <br> this.name = name; <br> } <br> //取得getNumber方法 <br> public double getNumber() { <br> return number; <br> } <br> //设置setNumber方法 <br> public void setNumber(double number) { <br> this.number = number; <br> } <br> } <br> ``` <br> ``` <br> package cn.chinabank;//中国银行包 <br> import java.util.Scanner; <br> //声明一个中国银行类 <br> public class ChinaBank { <br> //定义主方法 <br> public static void main(String[] args) { <br> @SuppressWarnings("resource") <br> Scanner sc = new Scanner(System.in);//接收控制台键盘输入 <br> System.out.println("----------------------"); <br> System.out.println("----欢迎您进入中国银行-----"); <br> System.out.println("---请选择您需要办理的业务---"); <br> System.out.println("1.存款"+"\t"+" 2.取款"+"\t"+"3.退出"); <br> System.out.println("----------------------"); <br> int sr = sc.nextInt();//接收一个数 <br> //接收用户输入 <br> switch(sr){ <br> //输入1则进入存款功能 <br> case 1: <br> System.out.println("请您输入存款金额:");//提示用户存款金额 <br> double number = sc.nextDouble();//接收用户输入金额 <br> @SuppressWarnings("unused") <br> Account account1 = new Account("asdd",number);//创建Accound对象 <br> System.out.println("请您将钞票叠整齐后放入存钞口..");//提示用户将钞票放入存钞口 <br> try{ <br> Thread.sleep(2000);//模仿现实银行存钞,设置睡眠时间两秒 <br> System.out.println("正在存钞,请稍后...");//将输出等待 <br> Thread.sleep(4000);//将其过程等待四秒 <br> System.out.println("存款成功!");//则输出存款成功 <br> System.out.println("您的存款金额为:"+number+"\t"+"当前账户余额为:"+number); <br> //输出存款金额 <br> System.out.println("1.是否打印凭条"+"\t"+"2.是否继续存款"+"\t"+"3.退出"); <br> //用户继续选择 <br> int sr1 = sc.nextInt();//接收用户输入 <br> switch(sr1){ <br> //接收用户是否打印凭条 <br> case 1: <br> System.out.println("dce"); <br> System.out.println("打印完成,退出打印"); <br> break; <br> //接收用户是否继续存款 <br> case 2: <br> System.out.println("继续存款"); <br> banking(); <br> break; <br> //接收用户退出 <br> case 3: <br> System.out.println("您已退出存款选项"); <br> break; <br> //防止随意输入 <br> default : <br> System.out.println("您输入有误,请重新输入"); <br> break; <br> } <br> }catch(Exception e){ <br> e.printStackTrace(); <br> } <br> break; <br> //输入2则进入取款功能 <br> case 2:System.out.println("请输出您的取款金额:"); <br> Account account2 = new Account("小明",7000);//创建Account对象并传参数 <br> DrawMoney ka = new DrawMoney(account2,3000);//创建银行卡取款3000元 <br> DrawMoney zhe = new DrawMoney(account2,4000);//创建存折取款2000元 <br> new Thread(ka).start();//开启银行卡线程 <br> new Thread(zhe).start();//开启存折线程 <br> break; <br> //输入3则退出银行系统 <br> case 3: <br> System.out.println("您已安全退出中国银行系统,感谢您的光临!"); <br> System.exit(0);//退出指令 <br> break; <br> //防止用户随意输入则进行友情提示 <br> default : <br> System.out.println("您输入有误,请重新输入"); <br> break; <br> } <br> } <br> //继承存款金额 <br> public static void banking(){ <br> @SuppressWarnings("resource") <br> Scanner sc2 = new Scanner(System.in);//接收控制台键盘输入 <br> System.out.println("请您输入存款金额:");//提示用户存款金额 <br> double number = sc2.nextDouble();//接收用户输入金额 <br> @SuppressWarnings("unused") <br> Account account2 = new Account("asdd",number);//创建Accound对象 <br> System.out.println("请您将钞票叠整齐后放入存钞口..");//提示用户将钞票放入存钞口 <br> try{ <br> Thread.sleep(2000);//模仿现实银行存钞,设置睡眠时间两秒 <br> System.out.println("正在存钞,请稍后...");//将输出等待 <br> Thread.sleep(4000);//将其过程等待四秒 <br> System.out.println("存款成功!");//则输出存款成功 <br> System.out.println("您的存款金额为:"+number+"\t"+"当前账户余额为:"+number); <br> //输出存款金额 <br> System.out.println("1.是否打印凭条"+"\t"+"\t"+"2.退出");//用户继续选择 <br> int sr3 = sc2.nextInt();//接收用户输入 <br> switch(sr3){ <br> //接收用户是否打印凭条 <br> case 1: <br> System.out.println("dce"); <br> System.out.println("打印完成,退出打印"); <br> break; <br> case 2: <br> System.out.println("您已退出存款选项"); <br> break; <br> //防止随意输入 <br> default : <br> System.out.println("您输入有误,请重新输入"); <br> break; <br> } <br> }catch(Exception e){ <br> e.printStackTrace(); <br> } <br> } <br> } <br> ``` <br> ``` <br> package cn.chinabank;//中国银行包 <br> //取钱类实现Runnable接口 <br> public class DrawMoney implements Runnable{ <br> private Account account;//私有属性-账户 <br> private double money;//私有属性-金额 <br> //构造方法并传值 <br> public DrawMoney(Account account,double money){ <br> this.account = account;//当前账户 <br> this.money = money;//当前金额 <br> } <br> @Override <br> public void run() { <br> while(true){ <br> //线程同步块 <br> //相当于将account队形锁住,只有执行完代码块,才可以释放,其他线程才能拿到 <br> //account必须是同一个 <br> synchronized(account){ <br> //如果账户金额大于等于金额 <br> if(account.getNumber()&=money){ <br> account.setNumber(account.getNumber()-money);//账户余额减去取款金额 <br> //输出取款成功,并显示当前账户剩余金额 <br> System.out.println("你已取款成功,您取款金额为:"+money+"元,剩余金额为:"+account.getNumber()+"元"); <br> //异常处理 <br> try{ <br> Thread.sleep(2000);//调用方法设置睡眠两秒 <br> }catch(InterruptedException e){ <br> e.printStackTrace(); <br> } <br> //否则提示取款不成功,显示当前账户余额 <br> }else{ <br> System.out.println("对不起,您的账户余额不足,您当前账户余额剩余"+account.getNumber()+"元");//输出账户金额 <br> } <br> } <br> break; <br> } <br> } <br> ```
12.多线程 - 生产者与消费者应用案例
多线程的开发中有一个最经典的操作案例,就是生产者-消费者
生产者不断生产产品,消费者不断取走产品
/**
* 生产者与消费者应用案例
* sleep与wait区别
* sleep让当前的线程进入休眠状态,让出cpu,让其他线程执行
* 如果用同步的话,有对象锁的时候,是不会释放的,只能等待此线程使用完,才可以使用
* wait会释放对象锁,必须等待其他线程唤醒
* @author JEEP-711
*
*/
public class ScXf {
public static void main(String[] args) {
Phones p = new Phones(null, null);//创建Phones对象
PhoneSc s = new PhoneSc(p);//创建PhoneSc对象
PhoneXf x = new PhoneXf(p);//创建PhoneXf对象
new Thread(s).start();//启动生产者线程
new Thread(x).start();//启动消费者线程
}
}
/**
* 手机生产者,单独的生产者,实现Runnable接口
* @author JEEP-711
*
*/
class PhoneSc implements Runnable{
private Phones phones;
public PhoneSc(Phones phones){
this.phones = phones;
}
@Override
public void run() {
//不断地生产20份,生产的过程
for (int i = 0; i < 50; i++) {
if(i%2==0){
phones.set("金立手机", "金立手机,中国造!");
}else{
phones.set("小米手机", "小米手机,为发烧而生!");
}
}
}
}
/**
* 手机消费者,顾客
* @author JEEP-711
*
*/
class PhoneXf implements Runnable{
private Phones phones;
public PhoneXf(Phones phones){
this.phones = phones;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
phones.get();//调用消费产品方法
}
}
}
/**
* 产品的对象,生产的手机
* @author JEEP-711
*
*/
class Phones{
@Override
public String toString() {
return "Phones [name=" + name + ", content=" + content + "]";
}
private String name;
private String content;
/**true表示可以生产,false表示可以消费
* 作为标记,如何flag等于true表示可以生产,如何flag等于false表示不可生产
* 如果flag等于false表示可以消费状态,可以取走,flag等于true表示不能取走
* 解决重复值得问题
*/
private boolean flag = true;//表示可以生产,false表示可以消费
//构造方法
public Phones(String name, String content) {
super();
this.name = name;
this.content = content;
}
//取得名称方法
public String getName() {
return name;
}
//设置名称方法
public void setName(String name) {
this.name = name;
}
//取得内容方法
public String getContent() {
return content;
}
//设置内容方法
public void setContent(String content) {
this.content = content;
}
/**
* 通过同步,解决了取值错误问题
* @param name
* @param content
*/
//生产制造同步方法
public synchronized void set(String name, String content){
if(!flag){
try {
//调用该方法,当前线程进入等待池等待状态,没有指定时间,
//需要其他线程唤醒,释放对象锁,让出cpu
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setName(name);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setContent(content);
flag = false;//表示可以消费,取走
this.notify();//唤醒在该监视器上的一个线程
}
//消费产品同步取值方法
public synchronized void get(){
if(flag){
try {
//调用该方法,当前线程进入等待池等待状态,没有指定时间,
//需要其他线程唤醒,释放对象锁,让出cpu
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+":"+this.getContent());
flag = true;
this.notify();
}
}
13.线程池
线程池是预先创建线程的一种技术,线程池在还没有任务到来之前,创建一定数量的线程,放入空闲队列中,然后对这些资源进行复用,减少频繁的创建和销毁对象
java里面线程池的顶级接口是Executor,是一个执行线程的工具
线程池接口是ExecutorServise
1.java.util.concurrent包:并发编程中很常用的实用工具包
2.Executor接口:执行已提交Runnable任务的对象
ExecutorService接口:
Executor提供了管理终止的方法,以及可能为跟踪一个或
多个异步任务执行状况而发生的Future的方法
Executors类:此包中所定义的Executor、ExecutorService等的工厂和实现方法
在Executors类里提供了一些静态工厂,生成一些常用的线程池
newSingleThreadExecutor:
创建一个单线程的线程池,这个线程池只有一个线程在工作,也就是相当于单线程
串行执行所有的任务,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行
newFixedThreadPool:
创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
newCacheadThreadPool:
创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说jvm)能够创建的最大线程大小
newSchediledThreadPool:
创建一个大小无限制的线程池,此线程池支持定时以及周期性执行任务需求
使用线程池的步骤:
- (1)调用Executors类的静态工厂方法创建一个ExecutorService对象或ScheduledExecutorService对象,其中前者代表简单的线程池,后者代表能以任务调度方式执行线程的线程池
- (2)创建Runnable实现类或Callable实现类的实例,作为线程执行任务
- (3)调用ExecutorService对象的submit方法来提交Runnable实例或Callable实例;或调用ScheduledExecutorService的schedule来执行线程
- (4)当不想提交任何任务时调用ExecutorService对象的shutdown方法来关闭线程池
初学(JAVA 多线程 高级阶段) 难点: ★★★★★
希望每一篇文章都能够对读者们提供帮助与提升,这乃是每一位笔者的初衷
感谢您的阅读 欢迎您的留言与建议
- FaceBook:JEEP SevenEleven
- Twitter:@JEEP7ll
- 新浪官方微博: @JEEP-711
- Github博客: <https://github.com/jeep711/jeep711.github.io>
- Blog Garden:<http://www.cnblogs.com/JEEP711/>
- W3C/Blog:<http://www.w3cschool.cn/jeep711blog/>
- CSDN/Blog:<http://blog.csdn.net/jeep911>
- 51CTO/Blog:<http://jeep711.blog.51cto.com/>
- 码云:<http://git.oschina.net/JEEP711/jeep711.github.io>
- 邮箱: <jeep711@qq.com>,<JEEP-711@outlook.com>