多线程

线程与进程

进程

是指一个内存中运行的应用程序,程序一旦运行就是进程。每个进程都有一个独立的内存空间。
进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。

线程

是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个进程。线程实际上是进程基础上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。线程与进程的一个主要区别是,统一进程内的一个主要区别是,同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存(一个进程无法直接访问另一进程的内存)。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写这些栈内存。
线程是进程的一个实体,是进程的一条执行路径。线程是进程的一个特定执行路径。当一个线程修改了进程的资源,它的兄弟线程可以立即看到这种变化。
多线程 - 图1

线程调度(分配机制)

Java使用的是抢占式调度,当cpu空闲以后,cpu会主动抛出时间片,由各个线程争抢执行。

分时调度

所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。

抢占式调度

优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的是抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在用一个时刻运行。其实,基于只有一个脑子的情况下,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。优先级越高,抢占概率高。
思考:假设装数据库的服务器,有上千个人同时去访问数据库,上千个程序去使用数据库,但数据库只有八个脑子(8个cpu),同时我们开启一千个链接,让一千个人同时用此八个脑子交替执行快还是一千个人用此八个脑子排队执行快?
A:后者快,原因为前者可能需要等超过一千次的时间,因为来回切换时需要时间,且执行的进度不定,所以排队执行会更快,执行完一个程序接着执行下一个程序,只需要花费一千次的时间。

同步与异步

同步指的是排队执行,效率低但是安全。 发送一个请求,等待返回,然后再发送下一个请求
异步是同时执行,效率高但数据不安全。 发送一个请求,不等待返回,随时可以再发送下一个请求
同步和异步最大的区别就在于。一个需要等待,一个不需要等待。
同步可以避免出现死锁,读脏数据的发生,一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。异步则是可以提高效率了,现在cpu都是双核,四核,异步处理的话可以同时做多项工作,当然必须保证是可以并发处理的。

并发与并行

并发指两个或多个事件在同一个时间段 内发生。 所有的并发处理都有排队等候,唤醒,执行至少三个这样的步骤
并行指的是两个或多个时间在同一时刻发生(同时发生)。
并发,是在同一个cpu上同时(不是真正的同时,而是看来是同时,因为cpu要在多个程序间切换)运行多个程序。
多线程 - 图2
并行,是每个cpu运行一个程序。
多线程 - 图3

继承Thread

多线程 - 图4
多线程 - 图5
两者抢占式调度,输出无一定规律。
实现结果
多线程 - 图6
时间顺序图:
多线程 - 图7
程序结束需要两条路径都执行完毕,任何一条路径没有执行完毕程度不会结束。当一个程序一个线程都没有了就会被关闭。
子线程所调用的方法都在子线程里运行
每个线程都有自己的栈空间,共用一份堆内存(如上述代码中会有main栈与mt栈)
由一个线程所调用的方法,此方法也会执行在此线程内。
Thread类是一个支持多线程的功能类,只要有一个子类他就可以实现多线程。
除此之外,所有程序的起点是main方法,所有线程也有一个自己的起点,run方法,多线程的每个主体类之中都必须重写Thread类中所提供的run()方法,这个方法没有返回值,表面线程一旦开始,无法停止。但调用run方法并没有真正启动多线程,真正启动多线程的是Thread类中的start()方法。
public void run()
但调用run方法并没有真正启动多线程,真正启动多线程的是Thread类中的start()方法。
public void start()
但是调用此方法执行的方法体是run()方法定义的。

实现Runnable

实现Runnable与继承Thread相比有如下优势:
通过创建任务然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。
可以避免单继承所带来的局限性。
任务与线程本身是分离的,提高了程序的健壮性。
线程池技术,接受Runnable类型的任务,而不接收Thread类型的线程。

Thread类

构造方法

public Thread()//通过new Thread来创建一个线程,此线程无任务
分配新的Thread对象。 此构造函数与Thread (null, null, gname)具有相同的效果,其中gname是新生成的名称。 自动生成的名称的格式为”Thread-“+ n ,其中n是整数。
public Thread (Runnable target)//传入Runnable来指定一个所要完成的任务
target - 启动此线程时调用其run方法的对象。 如果null ,这个类run方法什么都不做。
public Thread (String name)//给线程一个名称,可以通过get方法来获取
name - 新线程的名称
public Thread (Runnable target,String name)
分配新的Thread对象。 此构造函数与Thread (null, target, name)具有相同的效果。
target - 启动此线程时调用其run方法的对象。 如果是null ,则调用此线程的run方法。 name - 新线程的名称

普通方法与静态方法

多线程 - 图8
多线程 - 图9
public final void setName(String name)
将此线程的名称更改为等于参数name 。 首先调用此线程的checkAccess方法,不带参数。 这可能会导致投掷SecurityException 。
参数
name - 此主题的新名称。 异常
SecurityException - 如果当前线程无法修改此线程。
多线程 - 图10
public final void setPriority (int newPriority)
更改此线程的优先级。 首先调用此线程的checkAccess方法,不带参数。 这可能导致投掷SecurityException 。
否则,此线程的优先级设置为指定的newPriority较小的一个,以及线程的线程组的最大允许优先级。
参数
newPriority - 将此线程设置为的优先级 异常
(非法参数异常)IllegalArgumentException - 如果优先级不在 MINPRIORITY至 MAX_PRIORITY范围内。 SecurityException - 如果当前线程无法修改此线程。
多线程 - 图11
传入此三个静态修饰的常量来调整优先级,控制线程抢到时间片的几率。
stop方法不可以用于停止,此方法已过时,不安全;线程是一条单独执行的路径,有可能这个线程在做自己的事情,事情还未完成就调用stop把其关掉极有可能导致其正在使用某些资源而没有来得及释放,资源依然被占用。
通知线程终止,可以通过变量做标记的方式终止线程;在某一个地方定义一个变量,线程在执行过程中一直在观察这变量是否改变,变量改变则线程自身做一个合理的return,把run方法结束。
线程休眠方法sleep()
多线程 - 图12
拥有一个方法的重载,让线程休眠,暂时停止执行。线程中间出现sleep方法时就会停止,一参方法的参数表示毫秒,两参方法的参数分别是毫秒与纳秒,停止了约定的时长后再执行程序。
多线程 - 图13
标记是否是守护线程或用户线程
守护线程:
守护线程_是指为其他线程服务的线程。守护用户线程的一种守护线程;依附于用户线程,用户线程自己决定自己的死亡时机,而守护线程是当所有用户线程没有后自动死亡。【人在塔在】
main方法是主线程,其他再启动的是子线程;从入口为主,后面再开启为子的角度分析。
一个进程里可能包含n个线程,主线程启动了一个子线程,主线程死了子线程没死则程序不会结束;主线程与子线程都称为用户线程,所有的用户线程必须全部死亡程序才会结束。

设置和获取线程的名称

public class Demo3 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());//调用currentThread获取当前正在执行的线程对象,得到的一个类型是Thread线程
new Thread(new MyRunnable(),”子线程”).start();//方法1 //不命名也会有默认名称
new Thread(new MyRunnable()).start();
new Thread(new MyRunnable()).start();//抢占式调度,不保证顺序
Thread t = new Thread(new MyRunnable());//方法2
t.setName(“子线程”);
t.start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
//此方法为静态方法
System.out.println(Thread.currentThread().getName());
}
}
}
获取线程名称调用getName方法,设置线程名称可以在new的时候传入名称(如上名称:子线程),也可以通过获取对象返回值的方式,然后调用setName方法。

线程的休眠

public class Demo4 {
public static void main(String[] args) throws InterruptedException {
//线程的休眠 sleep
for (int i=0;i<10;i++){
System.out.println(i);
//Thread的静态方法,指定时间休眠。
Thread.sleep(1000);//限制一秒执行一个,1s=1000ms
}
}
}

线程的阻塞

线程阻塞 所有比较消耗时间的操作都称为线程阻塞,如文件读取、接受用户输入……

线程的中断

一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。线程启动后,可能会使用资源,涉及到很多需要释放的资源,如果由外部直接掐死,极有可能导致资源无法释放而一直占用,从而产生内存垃圾,并且此垃圾还是不能回收的垃圾,也有可能占用硬件资源,硬件资源无法释放可能导致其它软件无法使用。
一个线程是否关闭应该由线程本身来决定,我们通过stop可以让线程停止,但这样相当于从外部掐死此线程,导致线程所有的资源无法进行相应的释放操作。用stop方法会抛出异常,因为已过时。可以通过对线程对象打标记的方式打入一个中断标记,线程对象可以打标记,可以认为有一个属性,我们调用方法给此属性设置上了值就算打标记了,线程在某些特殊状态的时候会看此标记,如果发现有此标记会触发一个异常;程序员可以通过try-catch在catch块中由程序员在编写这段线程时决定如何关闭它或者不关闭它。
public class Demo5 {
public static void main(String[] args) {
//线程阻塞 所有比较消耗时间的操作都称为线程阻塞,如文件读取、接受用户输入……
//线程的中断 一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。
Thread t1 = new Thread(new MyRunnable());
t1.start();
for (int i = 0; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + “:” + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//给线程t1加入中断标记
t1.interrupt();
}
static class MyRunnable implements Runnable{
//在run方法里无法把异常抛出,因为(此为Runnable)父接口没有声明异常的抛出,子就不能声明比父更大范围的异常
@Override
public void run() {
for (int i=0;i<=10;i++){
System.out.println(Thread.currentThread().getName()+”:”+i);
try {
Thread.sleep(1000);
//线程中断的异常
} catch (InterruptedException e) {
// e.printStackTrace();
//System.out.println(“发现中断标记,但我们就是不死亡”);
System.out.println(“发现中断标记,线程自杀”);
return;//直接return,方法就直接结束了。
//当发现这样的异常以后,我们可以在异常处理的位置进行相应的资源释放。stop方法暴力,但不合理。
}
}
}
}
}

守护线程

public class Demo6 {
public static void main(String[] args) {
//守护线程 用于守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
//用户线程 当一个进程不包含任何的存货的用户线程时,进程结束。 直接创建的线程都是用户线程。
//线程:分为用户线程和守护线程
Thread t1 = new Thread(new MyRunnable());
//设置t1为守护线程
t1.setDaemon(true);
t1.start();
for (int i=1; i<=5; i++) {
System.out.println(Thread.currentThread().getName() + “:” + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class MyRunnable implements Runnable{
//在run方法里无法把异常抛出,因为(此为Runnable)父接口没有声明异常的抛出,子就不能声明比父更大范围的异常
@Override
public void run() {
for (int i=0;i<=10;i++){
System.out.println(Thread.currentThread().getName()+”:”+i);
try {
Thread.sleep(1000);
//线程中断的异常
} catch (InterruptedException e) {
e.printStackTrace();
return;//直接return,方法就直接结束了。
}
}
}
}
}

线程安全问题

多个线程同时运行时特别容易发生线程不安全问题
由于多个线程各顾各的执行,会出现不合理情况。
public class Demo7 {
public static void main(String[] args) {
//线程不安全问题
Runnable R = new Ticket();
new Thread(R).start();
new Thread(R).start();
new Thread(R).start();
}
static class Ticket implements Runnable{
//票数 此count也用于记录剩余票数
private int count = 10;
@Override
public void run() {
while(count>0){
//开始卖票
System.out.println(“正在准备卖票”);
try {
Thread.sleep(1000);//加入休眠便于将出错的情况放大
} catch (InterruptedException e) {
e.printStackTrace();
}
count—;
System.out.println(“出票成功,余票:”+count);
//单看逻辑永远不可能出现负数,因为只有当大于零的时候才会进入循环,但确实出现余票为-2的情况
}
}
}
}
实现结果:
正在准备卖票
正在准备卖票
正在准备卖票
出票成功,余票:9
正在准备卖票
出票成功,余票:8
正在准备卖票
出票成功,余票:7
正在准备卖票
出票成功,余票:6
正在准备卖票
出票成功,余票:5
正在准备卖票
出票成功,余票:4
正在准备卖票
出票成功,余票:3
正在准备卖票
出票成功,余票:2
正在准备卖票
出票成功,余票:1
正在准备卖票
出票成功,余票:0
出票成功,余票:-1
出票成功,余票:-2
多线程 - 图14
假设极限情况,当票数卖剩到count =1。
首先进行判断,由于在try-catch语句块中执行需要时间,若A在此过程中丢失了时间片(由于时间片丢失,还没来得及执行count—,此时count仍为1),B抢到了后判断大于零也往下执行;C同理,假设由于A未执行到count—处,三段逻辑同时都满足条件进入了循环。A抢到了时间片执行count—,此时1变0,余票为0,但因为B、C执行到count—位置时仍会执行,此时0—>-1,-1—>-2。
多线程 - 图15
线程不安全原因:多个线程同时执行,去争抢一个数据,同时操作一个数据,导致某个数据看到的与使用时不一样,可能是在此期间被其他部分的线程插足,把数据改变了,运行数据不符合预期。

线程安全1-同步代码块

观察锁对象是否被打上了标记,如果打上了表示正在有人执行,等待此标记是否消失,是否已经解锁了。某一线程执行解锁,会把锁解开,清楚标记。让其他线程去争抢锁,谁抢到就打上标记,由谁来执行这同步代码块。
假设有一百个线程,每一个线程都有自己的锁对象,这样显然是不合理的。一百个线程需要看同一把锁才能实现排队的效果。

线程安全2-同步方法

锁的力度是比较细的,它可以一行代码的加锁;而同步方法是以方法为单位进行加锁
线程同步可以在返回值变量前给方法加synchroniz关键字,就会排队执行。
同步方法也是用锁来进行控制的,锁为this;在方法内部谁操作this,则谁为锁对象。
若同步方法被静态修饰,则锁为类名.class。
同步代码块锁了一段代码,同步方法又锁了另一段代码;
假设有一同步代码块传入锁对象this,后面又有同步方法也是用this,若有一段线程在执行上面的代码,其他线程是执行不了这个方法的,因为两者使用的是用一把锁。
public class Demo8 {
public static void main(String[] args) {
//线程不安全问题
//解决方案2 同步方法
// Runnable R = new Ticket();
new Thread(new Ticket()).start();
new Thread(new Ticket()).start();
new Thread(new Ticket()).start();
//我们如果给它建立三个任务对象,每一个线程里面都是我们新new的任务,此处相当于卖30张票,每个线程10张
//每隔一秒会弹出三个打印,事实证明不是排队的;我们排队的对象就是创建的对象,就是执行方法的这个对象(this)
}
static class Ticket implements Runnable{
//票数 此count也用于记录剩余票数
private int count = 10;
@Override
public void run() {
/
synchronized (this){
//在此处写了一段同步代码块,传的锁对象是this,同步代码块使用的是this, 下面的方法是用的也是this
//若有一段线程在执行这里的代码,其他线程是执行不了的;两者看的是同一把锁
}
/
while(true){
boolean flag = sale();
if(!flag){
break;
}
}
}
//假设在这类里面我有十个同步方法,十个都是使用this这把锁,那么此时其中一个方法执行,其它方法也都执行不了(看的是同一把锁)
public synchronized boolean sale(){//没锁住线程是因为我们new了三个R
//this 非静态时
//Ticket.class 静态时
if(count>0){
//开始卖票
System.out.println(“正在准备卖票”);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count—;
System.out.println(Thread.currentThread().getName()+”出票成功,余票:”+count);
return true;//正确卖票返回true
}
return false;//卖票失败返回false
}
}
}

线程安全3-显式锁Lock

显式锁:自己创建锁对象,自己锁自己解锁;lock上锁,unlock解锁。
假设A线程在锁的时候,其它线程在执行这段代码时会检查是否被锁住,锁住则不会被执行,会排队等待,直到不是锁住状态才会去执行。
/*
同步代码块和同步方法都属于隐式锁
线程同步:Lock
具体怎么锁不用管,其内部自己锁自己解开,这叫隐藏的锁的方式
*/
public class Demo9 {
public static void main(String[] args) {
Object o = new Object();
//线程不安全问题
// 解决方案3 显式锁 Lock 子类 ReentrantLock
Runnable R = new Ticket();
new Thread(R).start();
new Thread(R).start();
new Thread(R).start();
}
static class Ticket implements Runnable{
//票数 此count也用于记录剩余票数
private int count = 10;
//显式锁 l 因为只创建了一个Ticket对象,三个线程都在使用着一个对象
//此时三个线程操作一个对象里面的一个属性l,用的是同一把显式锁
// 显式锁比隐式锁更能体现锁的概念
private Lock l = new ReentrantLock();//new锁对象
@Override
public void run() {
while(true) {
l.lock();//上锁
if (count > 0) {
//开始卖票
System.out.println(“正在准备卖票”);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count—;
System.out.println(Thread.currentThread().getName()+”出票成功,余票:” + count);
}else {
break;
}
l.unlock();//解锁
}
}
}
}

公平锁与非公平锁

公平锁 先来先到 排队执行的流程;非公平锁 大家一块抢。
Java中默认我们上述学到的三种处理方式都是非公平锁,大家一块抢,谁抢到谁去执行。
如何实现公平锁?显式锁有一个有一个构造方法可以传递布尔类型的值/参数fair,默认是false,也可以传入true,传true表示公平锁。谁先来谁先得到这把锁的公平排队机制。
Lock l = new ReentrantLock(true);//false为非公平锁,true代表公平锁,默认false。

线程死锁

public class Demo10 {
public static void main(String[] args) {
//线程死锁
Culprit c = new Culprit();
Police p = new Police();
new MyThread(c,p).start();
/*
什么时候能不锁?
主线程在执行时调用方法时子线程还未启动,子线程没启动就不会锁住此方法
等执行完以后子线程再启动就不会导致阻塞
*/
c.say(p);
//罪犯等待警察回应,警察回应这方法必须得执行完say,但警察要等罪犯执行完它的say。
//罪犯在等警察执行完say以后调用警察的fun方法/回应方法,警察在等待罪犯执行完say方法后调用罪犯的fun方法
//两人都卡在这,出现线程死锁的情况 在开发时要尽量避免此类情况发生
//在任何有可能导致锁产生的方法里不要再调用另一个方法让另外一个锁产生。否则极有可能造成死锁问题(从根源上)
}
static class MyThread extends Thread{
private Culprit c;
private Police p;
public MyThread(Culprit c,Police p){
this.c=c;
this.p=p;
}

  1. @Override<br /> public void run() {<br /> p.say(c);<br /> }<br /> }
  2. //罪犯<br /> static class Culprit{<br /> //当罪犯说话时传一个警察对象进来,对象说完这话后调用警察的方法<br /> public synchronized void say(Police p){<br /> System.out.println("罪犯:你放了我,我放人质");<br /> p.fun();<br /> }<br /> public synchronized void fun(){<br /> System.out.println("罪犯被放走了,罪犯也放了人质");<br /> }<br /> }<br /> //警察<br /> static class Police{<br /> public synchronized void say(Culprit c){<br /> System.out.println("警察:你放人质,我放过你");<br /> c.fun();<br /> }<br /> public synchronized void fun(){<br /> System.out.println("警察救了人质,但是罪犯跑了");<br /> }
  3. }<br />}

特殊实现结果:
罪犯:你放了我,我放人质
警察救了人质,但是罪犯跑了
警察:你放人质,我放过你
罪犯被放走了,罪犯也放了人质

一般实现结果:
罪犯:你放了我,我放人质
警察:你放人质,我放过你

多线程通信问题

如何实现多线程交互问题?
多线程 - 图16
多线程 - 图17
调用等待方法会导致当前线程等待直到它被唤醒
可以传入相应的毫秒或毫秒+纳秒,可以指定时间,就一直等待;或者如果没有人通知或唤醒,那么此时等待只能到一定时间后自己醒。与sleep不一样。
某一个线程调用此wait方法,该线程就会立马睡过去,直到它被唤醒或者知道时间到达。
若想把线程唤醒,要么等时间到,要么其它的线程调用该对象,必须是同一个对象;比如A线程通过对象haha睡着了需要被唤醒,其它线程想要唤醒A线程,就需要调用haha对象的notify或notifyAll。notify是随机唤醒一个在调用此对象的睡眠方法睡着的一个线程;notifyAll则是把通过这个调用该对象的睡眠方法的所有线程都唤醒,全部进入运行状态。
/*
多线程通信问题,生产者与消费者问题
就比如A线程要到网上下载音乐,B线程要等待播放。
那么A是生产数据,B则需要拿到这个数据以后进行消费,进行播放,音乐没有下载完不能播放。
A线程在执行下载时,B线程可以进行休眠;B线程进行播放时,A线程可以休眠。
能确保生产者在生产时消费者没有在消费,消费者在消费时生产者没有在生产(搭配机制确保数据安全) 确保数据安全,确保流程没有问题。
*/

生产者与消费者

出现数据错乱问题的原因:两个线程协同合作不协调
处理方法:加synchronized关键字不能解决问题,会造成别的问题。A线程执行完此方法,按理来说应该到B线程执行,但其实更有可能A线程“回首掏”再次执行。我们可以通过A线程执行时B线程休眠,B线程执行时A线程休眠;能确保生产者在生产时消费者没有在消费,消费者在消费时生产者没有在生产。
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();//直接创建线程,start启动
new Waiter(f).start();
}
//厨师———>生产者
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}

  1. @Override<br /> public void run() {<br /> for (int i=0;i<100;i++){<br /> if(i%2==0){<br /> f.setNameAndTaste("老干妈小米粥","香辣味");<br /> }else {<br /> f.setNameAndTaste("煎饼果子","甜辣味");<br /> }<br /> }<br /> }<br /> }<br /> //服务生———>消费者<br /> static class Waiter extends Thread{<br /> private Food f;<br /> public Waiter(Food f) {<br /> this.f = f;<br /> }
  2. @Override<br /> public void run() {<br /> for (int i=0;i<100;i++){<br /> try {<br /> Thread.sleep(100);<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> f.get();<br /> //为了合理地生成一个取出一个,不加休眠会导致循环瞬间结束了<br /> }<br /> }<br /> }<br /> //食物<br /> static class Food{<br /> private String name;<br /> private String taste;<br />//true表示可以生产<br /> private boolean flag = true;
  3. public synchronized void setNameAndTaste(String name,String taste){<br /> if(flag) {<br /> this.name = name;<br /> try {<br /> Thread.sleep(100);<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> this.taste = taste;<br /> flag = false;<br /> this.notifyAll();//唤醒<br /> try {<br /> this.wait();<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> }<br /> //可能会发生时间片丢失的情况,抢到一次时间片,不足以执行下一段代码<br /> }<br /> public synchronized void get() {<br /> if (!flag) {<br /> System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);<br /> flag = true;<br /> this.notifyAll();<br /> try {<br /> this.wait();<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> }<br /> }<br /> }

实现结果:(此时没有出现数据错乱并且在交替执行)
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味
服务员端走的菜的名称是:老干妈小米粥,味道:香辣味
服务员端走的菜的名称是:煎饼果子,味道:甜辣味

线程的六种状态

  • 线程状态。线程可以处于以下状态之一:
    • NEW (已创建但未启动,没有调用start()方法)尚未启动的线程处于此状态。
    • RUNNABLE Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。在Java虚拟机中执行的线程处于此状态。
    • BLOCKED (当一个线程与其它线程排队时就是被阻塞状态,表示线程阻塞于锁。)被阻塞等待监视器锁定的线程处于此状态。
    • 无限等待(没有知道休眠的时间,直到某一个线程唤醒其)无限期等待另一个线程执行特定操作的线程处于此状态。
      处于等待状态的线程正在等待另一个线程执行特定操作。 例如,一个已调用线程Object.wait()的对象上正在等待另一个线程来调用Object.notify()Object.notifyAll()该对象上。 调用Thread.join()线程正在等待指定的线程终止。
    • TIMED_WAITING 计时等待 (该状态不同于WAITING,它可以在指定的时间后自行返回。)正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
    • TERMINATED 已退出的线程处于此状态。
  • 线程在给定时间点只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。

在运行内,有可能会因为自己抢不到时间片而导致阻塞。
多线程 - 图18
多线程 - 图19
多线程 - 图20

  1. 初始状态

实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。
2.1. 就绪状态
就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
调用线程的start()方法,此线程进入就绪状态。
当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
锁池里的线程拿到对象锁后,进入就绪状态。
2.2. 运行中状态
线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

  1. 阻塞状态

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

  1. 等待

处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

  1. 超时等待

处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。

  1. 终止状态

当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

带返回值的线程Callable

public interface Callable//这是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。

  • 返回结果并可能抛出异常的任务。实现者定义一个没有参数的单个方法,名为call
    Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类而设计的。 但是, Runnable不会返回结果,也不能抛出已检查的异常。
    Executors类包含的实用方法,从其他普通形式转换为Callable类。

通过继承Thread,实现Runnable,最终再通过Thread启动的方式;这两种方式我们就像在创建一条新的执行路径就开始执行了,它不影响主线程。而Callable更像是主线程指派给了它一个任务,执行完毕后会有一个结果,此结果主线程可得到。

Runnable Callable

接口定义
//Callable接口
public interface Callable {
V call() throws Exception;//call()也是所要执行的内容
}
//Runnable接口
public interface Runnable {
public abstract void run(); //任务执行的方法
}

Callable使用步骤
  1. 编写类实现Callable接口 , 实现call方法
    class XXX implements Callable {
    @Override
    public call() throws Exception {
    return T; //需指定泛型并return
    }
    }
    2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象 //创建任务对象
    FutureTask future = new FutureTask<>(callable);

public class FutureTask extends Object implements RunnableFuture

  1. public V get() throws InterruptedException, ExecutionException
  2. get方法获取线程执行的结果,若调用此get方法,比如说主线程启动了线程AA需要花十秒钟完成某件事情并返回结果,那么此时主线程没有调用get方法,就不会等那十秒等线程A产生结果,而调用了get方法就会导致主线程等待十秒,等待线程A执行完毕,主线程获取结果后再往下执行。
  3. public V get (long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException<br /> <br />传入相应的秒数代表超时的时间,根据超时的时间内,如果没有获取到结果就不要了<br /> <br />3. 通过Thread,启动线程 <br /> new Thread(future).start();//以任务的形式去启动

Runnable Callable的相同点

都是接口
都可以编写多线程程序
都采用Thread.start()启动线程

Runnable Callable的不同点

Runnable没有返回值;Callable可以返回执行结果
Callable接口的call()允许抛出异常;Runnable的run()不能抛出

Callable获取返回值

Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
Java中实现多线程的第三种方式
public class Demo12 {
//判断线程是否执行完毕,然后再来决定是否继续等待或者取消
public static void main(String[] args) throws ExecutionException, InterruptedException {
//先创建此类型对象
Callable c = new MyCallable();
//创建任务,给上泛型
FutureTask task = new FutureTask(c);
task.isDone();//判断线程任务是否执行完毕
task.cancel(true);//true表示取消其,任务未完成
//取消成功是通常任务未执行完就干掉了,返回false指的是在绝大多数情况下取消成功了,或是代表此任务执行完毕执行成功了。
new Thread(task).start();
Integer j = task.get();//调用get方法获取其返回值
System.out.println(“返回值为:”+j);
for (int i=0;i<10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}//主线程与子线程并发执行
}

  1. static class MyCallable implements Callable<Integer>{
  2. @Override<br /> public Integer call() throws Exception {<br /> //Thread.sleep(3000);<br /> for (int i=0;i<10;i++){<br /> Thread.sleep(100);<br /> System.out.println(i);<br /> }<br /> return 100;<br /> }<br /> }<br />}

线程池概述/Executors

线程池既是线程的容器的意思,
线程池就相当于一个线程数组,合理设置线程池的长度是有必要的。
//线程执行任务的流程 创建任务与执行任务一般占用时间少,而创建线程与关闭线程占用的时间比较多
//创建线程
//创建任务
//执行任务
//关闭线程
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
若无线程空闲,那种非定长线程池会创建一个线程去执行其。执行后新创建的线程又会被缓存在池里面等待下一次去执行,并且会有一些清空缓存的机制,会在非高峰期时把一直处于闲状态的线程池清空。
多线程 - 图21
线程池的好处
降低资源消耗。提高响应速度。提高线程的可管理性。
Java中有四种常用的线程池
缓存线程池(非定长线程池) 定长线程池 单线程线程池 周期性任务定长线程池
单线程线程池应用领域,有时候有些任务需要子线程去执行,但这些任务可能是排队执行的,此时可用单线程来执行这些任务。若需并发执行,则使用定长线程池或缓存线程池。

缓存线程池

public class Demo13 {
/** 缓存线程池.
(长度无限制)
执行流程:
1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在,则创建线程 并放入线程池, 然后使用
/
public static void main(String[] args) {
//向线程池中加入新的任务(指挥线程执行新的任务)
ExecutorService service = Executors.newCachedThreadPool();//创建缓存线程池
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
try {
Thread.sleep(100);//让主线程休眠一秒钟 作用是为了让我们感受到上面三个线程已经执行完,缓存池里拥有了三个线程
//此时再执行此任务,按理说有空闲的线程了,会使用这三个空闲的线程的其中一个来执行任务
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
}
}

定长线程池

public class Demo14 {
/** 定长线程池.
(长度是指定的数值)
执行流程:
1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);//给定参数 固定长度 长度不允许扩容
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
service.execute(new Runnable() {
@Override
public void run() {
//等待线程空闲以后才能继续执行
//3s后再执行此
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
}
}

单线程线程池

每一次执行都是单个线程,没有出现其它线程
任务在执行完毕时代码并没有结束,因为线程池等着你给它传递任务。
相当于你有一个用户线程没有关闭,那么你的用户程序就不会被关闭。一段时间以后也会自动关闭。
public class Demo15 {
/*
单线程线程池。
执行流程:
1.判断线程池的那个线程是否空闲
2.空闲则使用
3.不空闲则等待池中的单个线程池空闲后使用
*/
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
}
});
}
}

实现结果:
pool-1-thread-1锄禾日当午
pool-1-thread-1锄禾日当午
pool-1-thread-1锄禾日当午
pool-1-thread-1锄禾日当午

周期定长线程池

public class Demo16 {
/
周期任务 定长线程池。(特性与流程与定长线程池一样)
执行流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用。
4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

周期性任务执行时:
定时执行,当某个时机触发时,自动执行某任务。
/
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
// 1.定时执行一次
/

参数1.定时执行的任务
参数2.时长数字(单位由参数3决定)
参数3.时长数字的时间单位,TimeUnit的常量指定
/
// service.schedule(new Runnable() {
// @Override
// public void run() {
// System.out.println(“锄禾日当午,此任务五秒后执行”);
// }
// },5, TimeUnit.SECONDS);
/*
周期性执行任务
参数1.任务
参数2.延迟时长数字(第一次执行在什么时间以后)
参数3.周期时长数字(往后每隔多久执行一次)
参数4.时长数字的单位
*/

  1. service.scheduleAtFixedRate(new Runnable() {<br /> @Override<br /> public void run() {<br /> System.out.println("汗滴禾下土");<br /> }<br /> },5,1,TimeUnit.SECONDS);<br /> }<br />}

实现结果:
汗滴禾下土
汗滴禾下土
汗滴禾下土
汗滴禾下土
汗滴禾下土
汗滴禾下土
汗滴禾下土
汗滴禾下土
汗滴禾下土
汗滴禾下土
汗滴禾下土
(每隔5s执行一次)……

Lambda表达式

public class Demo18 {
public static void main(String[] args) {
/ //匿名内部类
print(new MyMath() {
@Override
public int sum(int x, int y) {
return x+y;
}
},100,200);
/
//lambda实现,就是把一个匿名内部类的类的部分都删掉就保留那一个抽象的方法,保留方法的参数部分,保留方法体
print((int x,int y) -> {return x+y;},100,200);
}
//(抽象)接口不可以传对象,可以把此接口实现后,把实现类的对象传递过来
public static void print(MyMath m,int x,int y){
int num = m.sum(x,y);
System.out.println(num);
}

  1. static interface MyMath{//实现Lambda表达式需要注意此接口一定只有一个抽象方法<br />//因为它只包含了一个方法,那么在实现此接口时一定是为了实现这个方法,才可以替换成一个函数传递式的lambda表达式<br /> int sum(int x,int y);<br /> }<br />}