多线程
多线程
进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。本质上就是一块内存空间。
线程:就是进程中的程序执行的最小单元。
一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。
jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。
当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的代码执行会停止,会去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃圾回收。(异常也是并行运行)
随机性的原理:因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。
- 关于运行线程代码,要知道的是:
- 返回当前线程的名称:Thread.currentThread().getName()
- 线程的名称是由:Thread-编号定义的。编号从0开始。(当然可以自己去命名)
-
线程要运行的代码都统一存放在了run方法中。
-
线程要运行必须要通过类中指定的方法开启。start方法。
3.线程状态:
它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
-
新建状态,当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值
-
就绪状态,当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行,
- notify 是随机唤醒。 notify全部释放
-
运行状态,如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态
-
阻塞状态,当处于运行状态的线程失去所占用资源之后,便进入阻塞状态,有wait , sleep可以变成阻塞
- wait 不会被被唤醒后是不会释放锁, sleep会释放锁
- 在线程的生命周期当中,线程的各种状态的转换过程!
创建线程的第二种方式:实现一个接口Runnable
1,定义类实现Runnable接口。
2,覆盖接口中的run方法(用于封装线程要运行的代码)。
3,通过Thread类创建线程对象;
4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。
5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法
为什么要有Runnable接口的出现?
1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。
可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢?
只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。
所以,通常创建线程都用第二种方式。
因为实现Runnable接口可以避免单继承的局限性。
2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。
所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。
实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。
wait(),notify(),notifyAll()用来操作线程为什么定义在Object类中,sleep 存在thread中?
这些方法存在于同步中;
使用这些方法必须标识同步所属的锁;
锁可以是任意对象,所以任意对象调用方法一定定义在Object类中。
而sleep休眠是需要设定固定时间而不是通过锁的方式。
new Thread(new Runnable(){ //匿名
public void run(){
System.out.println("runnable run");
}
})
{
public void run(){
System.out.println("subthread run");
}
}.start(); //结果:subthread run
Try {
Thread.sleep(10);
}catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟cpu切换情况
- 多线程的安全问题:
多线程环境下, 数据资源的争抢。 - 原因: 在某个时刻多条语句被一个线程执行的时候, 还没执行完, 就被其他的线程执行了。
- 解决方案: 同步代码块:
synchronized(对象){
需要被同步的代码
}
同步
好处:解决了线程安全的问题
弊端:相对降低了性能, 因为判断锁需要消耗资源, 产生了死锁。
-
定义同步是有前提的:
-
必须是有两个以上的线程, 才需要同步
-
多个线程必须保证使用的是同一把锁
-
同步的第二种表现形式: 将同步关键字定义再函数上面, 让函数具备了同步性
-
同步函数用的是个锁, 函数都有自己所属的对象this, 所以同步函数所使用的锁就是本对象锁。
-
同步函数被static修饰的时候, 这个时候锁就是所属的类,也可以说是字节码文件对象, 也就是类名.class
-
同步代码块和同步函数的区别: 同步代码块使用的锁可以是任何对象
-
同步函数使用的锁是this, 静态同步函数的锁是字节码文件对象。再一个类中只有一个同步, 可以使用同步函数
延迟加载的单例模式:
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){ //锁是谁?字节码文件对象;
if(s == null){
synchronized(Single.class){
if(s == null)
s = new Single();
}
}
return s;
}
}
- 同步死锁:将同步进行嵌套的时候就可以看见同步死锁
- 线程间的通信:思路:多个线程按照序列操作同一个资源,仓储模式
- 将资源封装成对象
- 将线程执行的任务(run)方法也封装成了对象
- 等待唤醒机制:
- wait:将线程对象存储到线程池
- notify:到线程池唤醒对象
- notifyall: 唤醒线程池中所有的纤层
注意点:
- 这些方法都需要定义再同步中, 因为要标识所属的锁。
- 者三个方法都定义再Object类中,因为三个方法都需要定义同步内,并标识同步锁, 而同步锁可以定义为任何对象, 所以这个对象最好的选择就是object
wait和sleep区别:
wait可以指定时间也可以不指定时间,会释放执行权
sleep必须指定时间,不会释放执行权
线程停止:
1.通过定义循环的结束标记
2. 通过interrupt中断线程
关于优先级的问题:
- 设置优先级, 只会再大体上进行控制,就是说数量达到一定的程度的时候才可以。
- 而要真正的实现只有通过join和interrupt才可以终端线程, 而不是通过yield让步和设置优先级
- setDamon设置,将该线程标记为守护线程, 或者是用户线程
- toString可以返回线程的许多信息。 setProprity,getProprity.
Lock接口:
同步是隐示的锁操作,而Lock对象是显示的锁操作
< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
--------------------------------------------------------
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
}
finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
}
finally {
lock.unlock();
}
}
}
API:
(Application Programming Interface,应用程序编程接口)就是让别人通过访问封装好的类, 而不用去理解底层直接调用即可。