1.进程是一个应用程序,线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。
2.在java中,线程A和线程B,堆内存和方法区内存共享,但是栈独立,一个线程一个栈。
3.假设启动10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各的,这就是多线程并发。多线程并发可以提高效率,java中之所以有多线程机制,目的就是为了提高程序的处理效率。
注意!进程A和进程B的内存独立不共享
4.什么是真正的多线程?
t1线程执行t1的,t2线程执行t2的,t1不会影响t2,t2也不会影响t1。
5.java语言中,实现线程的方式:
(1)编写一个类,直接继承java.lang.Thread,重写run方法
代码示例:
class MyThread extends Thread{
public void run(){
//编写程序,这段程序运行在分支线程当中(分支栈)
for(int i = 0; i < 1000; i++){
System.out.println("分支线程------");
}
}
public static void main(String[] args) {
//创建线程对象(在main方法中创建)
MyThread mythread = new MyThread();
//启动线程
myThread.start();//调用线程的start方法
for(int i = 0; i < 100; i++){
System.out.println("主线程------" + i);
}
}
}
①start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后瞬间就结束了,这段代码的任务是为了开辟一个新的栈空间。只要新的栈空间开出来,start()方法就结束了
②启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。
③run和start的区别:
run()不会启动线程,不会分配新的分支栈(即单线程)
start()会启动一个分支线程,开辟新的栈空间
(2)编写一个类实现java.lang.Runnable接口
代码示例:
//创建一个可运行的对象
MyRunnable r = new MyRunnable();
//将可运行的对象封装成一个线程对象
Thread t = new Thread(r);
//启动线程
t.start();
class MyRunnable implements Runnable{
public void run();
}
//这并不是一个线程类,是一个可运行的类,不是一个线程
注意!第二种实现接口比较常用,因为一个类实现了接口它还可以继承其他类,更灵活。
(3)实现Callable接口(JDK8新特性)
这种方法实现的线程可以获取线程的返回值。
6.线程生命周期
(1)新建状态:刚new出来的线程对象
(2)就绪状态:就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权利,CPU时间片就是执行权。当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法的开始执行标志着程序进入运行状态
(3)运行状态:run方法的开始执行标着这个线程进入运行状态,当之前占有的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU的时间片,会出现进入run方法接着上一次的代码继续往下执行
(4)阻塞状态:当一个线程遇到阻塞事件,例如接收用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片
(5)死亡状态:run方法结束
7.获取线程的名字
代码示例:
String name = t,getName();
//获取当前线程对象
Thread currentThread = Thread.currentThread();
//修改线程名字
线程对象.setName("线程名字");
8.线程的sleep方法
static void sleep(long millis)
(1)静态方法:Thread.sleep(1000);
(2)参数是毫秒
(3)作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用
9.终止线程的睡眠
注意!这个不是终断线程的执行,是终止线程的睡眠
代码示例: //终断线程的睡眠 t.interruput();//这种终断睡眠机制依靠了java的异常处理机制
10.强行终止线程的执行
t.stop()
//不建议经常使用,容易丢失数据
合理地终止一个线程的执行(常用):
1.线程不安全的条件
(1)多线程并发
(2)有共享数据
(3)共享数据有修改的行为
2.如何解决?
线程排队执行(不能并发),用排队执行解决线程安全问题这种机制称为:线程同步机制。
异步编程模型:多线程并发(效率较高)
同步编程模型:线程排队执行(效率较低)
1.语法:
synchronized( ){ }
//括号中的数据非常关键,必须是多线程共享的数据才能达到多线程排队
2.以上代码块的执行原理
(1)假设t1和t2线程并发,开始执行以下代码时,肯定有一个先有一个后
(2)假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,找到之后并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都占有这把锁,直至同步代码块结束,这把锁才会被释放。
(3)假设t1已经占有这把锁,此时t2也遇到了synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁也被t1占有,t2只能在同步代码块外面等待t1把同步代码块执行结束,y1会归还这把锁,此时t2终于等到这把锁,然后t2占有这把锁,进入同步代码块执行程序,这样就达到线程排队执行。
==注意!==这个共享对象一定要选好了,这个共享对象一定是你需要排队执行的这些线程对象所共享的。
实例变量:在堆中
静态变量:在方法区中
局部变量:在堆中
以上三大变量中:
局部变量永远都不会共享,所以局部变量永远都不会存在线程安全问题,一个线程一个栈,局部变量在栈中,实例变量在堆中,堆只有一个,静态变量在方法区,方法区只有一个。堆和方法区都是多线程共享的,所以可能会存在线程安全问题。
synchronized有三种写法:
(1)同步代码块:灵活
synchronized( ){ }
//括号中的数据非常关键,必须是多线程共享的数据才能达到多线程排队
(2)在实例方法上使用synchronized,表示共享对象一定是this,并且同步代码块是整个方法体
(3)在静态方法上使用synchronized表示找类锁。类锁永远只有一把,即使是创建了一百个对象也只有一把类锁。
死锁1.什么是死锁?
死锁(Deadlock):指多个进程在运行过程中,因争夺资源而造成的一种僵局。当进程处于这种状态时,若无外力作用,它们都将无法再向前推进。
死锁和饥饿的区别:
死锁:指进程之间无休止地互相等待,进程处于阻塞状态
饥饿:指一个进程无休止地等待,进程处于就绪状态
2.产生死锁的原因
资源问题:
1.可重用性资源和消耗性资源
2.可抢占性资源和不可抢占性资源
那么,竞争不可抢占性资源和竞争可消耗资源都会引起死锁
总结:
①竞争资源
②进程间推进顺序非法
3.死锁的必要条件和处理方法
形成死锁的四个必要条件:(四个缺一不可)
(1)互斥条件
进程对所分配到的资源进行排他性使用
(2)请求和保持条件
进程已经保持了至少一个资源,又提出新的资源请求,而新请求资源被其他进程占有只能造成自身进程阻塞,但对自己已获得的其他资源保持不放,必然影响其他进程
(3)不可抢占条件
进程已获得的资源未使用完之前不能被剥夺,只能在使用完时由自己释放
(4)循环等待条件
哲学家问题
处理死锁的方法:
(1)预防死锁
破坏四个必要条件的一个或几个
(2)避免死锁
动态资源分配
(3)检测死锁
能精确确定与死锁有关的进程和资源;然后采取适当的措施,从系统中将已发生的死锁清除掉
(4)解除死锁
撤销或挂起一些进程,以便回收一些资源并将他们分配给已阻塞进程
死锁的检测与解除措施,有可能使系统获得较好的资源利用率和吞吐量(死锁几率不一定很高),但在实现上难度也最大
4.预防死锁的方法
由于互斥条件时非共享设备所必备的,故要破坏产生死锁的后三个条件
(1)破坏“保持和请求条件”
①必须一次性的申请其在整个运行过程中所需要的全部资源
算法简单、易于实现且很安全。但缺点是资源浪费严重、或进程延迟运行
②只允许一个进程只获得在运行初期所需的资源后,便开始运行。进程运行过程中再逐步释放已分配给自己的、且已用完的全部资源,然后在请求新的资源
(2)破坏“不可抢占”条件
允许进程先运行,但当提出的新要求不被满足时必须释放它已保持的所有资源,待以后需要时再重新申请。实现比较复杂且付出很大代价。可能会造成前功尽弃,反复申请和释放等情况
(3)破坏“循环等待”条件
对系统所有资源类型进行排序并赋予不同的序号,所有进程对资源的请求必须严格按照资源序号递增的次序提出。例如:输入设备规定较低的序号,输出设备规定较高的序号
前两种策略比较,资源利用率和系统吞吐量都有较明显的改善。
但也存在严重问题:
①资源编号限制新设备的增加;
②应用中的使用设备顺序与规定的顺序并不协调;
③限制了用户编程自由
第一种方案:尽量使用局部变量代替“实例变量和静态变量”
第二种方案:如果必须是实例变量,可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应一个对象,对象不共享就没有数据安全问题)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候只能选择synchronized,线程同步机制。
守护线程概述1.java语言中线程分为两大类
(1)用户线程
(2)守护线程即后台线程,其中具有代表性的是垃圾回收线程
2.守护线程的特点
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
==注意!==主线程main方法是一个用户线程
3.实现守护线程
在启动线程之前,将线程设置为守护线程
定时器t.setDaemon(true)
1.定时器的作用:
间隔特定的时间执行特定的程序
**2.**在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置就可以完成定时器的任务。
wait和notify概述(wait()和notify()建立在synchronized线程同步的基础上)1.wait和notify方法不是线程对象的方法,是java中任意一个java对象都有的方法,这两个方法是Object类中自带的
2.wait()方法调用
Object 0 = new Object();
o.wait();
表示让正在o对象上活动的线程进入等待状态,无期限等待,知道被唤醒为止。o.wait()方法的调用会让“当前线程”(正在o对象上活动的线程)进入等待状态
3.notify()方法的作用?
Object 0 = new Object();
o.notify();
表示唤醒正在o对象上等待的线程,还有一个notifyAll()方法,表示唤醒o对象上处于等待的所有线程
4.注意!o.wait()方法会让在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁。notify()只会通知不会释放之前占有的o对象的锁。
生产者和消费者模式需要用wait()方法和notify()方法实现
代码示例:
package ProduceConsumer;
import java.util.ArrayList;
public class Produce {
public Object object;
public ArrayList list;//用list存放生产之后的数据,最大容量为1
public Produce(Object object,ArrayList list ){
this.object = object;
this.list = list;
}
public void produce() {
synchronized (object) {
try {
while(!list.isEmpty()){
System.out.println("生产者"+Thread.currentThread().getName()+" waiting");
object.wait();
}
int value = 9999;
list.add(value);
System.out.println("生产者"+Thread.currentThread().getName()+" Runnable");
object.notifyAll();//然后去唤醒因object调用wait方法处于阻塞状态的线程
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}



