多线程的实现
一、程序、进程、线程是什么?
- 程序(program):为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
- 程序(program):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
- 如:运行中的QQ,运行中的MP3播放器
- 程序是静态的,进程是动态的
- 线程(thread):进程可进一 步细化为线程,是一个程序内部的一条执行路径。
- 若一个程序可同一时间执行多个线程,就是支持多线程的
二、线程调度
(一)线程的调度方式
线程调度分以下两种
- 时间片(你执行一会儿,我执行一会儿)
- 抢占式:高优先级的线程抢占CPU(谁抢到就是谁用)
注:
- 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
(二)线程的优先级控制
注:优先级的高低只能说明抢到CPU执行权的概率变大,而不是总是先执行
MIN_ PRIORITY(1) ;
NORM_ PRIORITY(5) ;(默认优先级)
MAX_ PRIORITY (10) ;
- getPriority() :返回线程优先值
- setPriority(int newPriority):改变线程的优先级
- 线程创建时继承父线程的优先级
(三)使用线程时常用方法
- start():启动线程并执行相应的run( )方法
- run():子线程要执行的代码放入run( )方法中
- currentThread():静态的,调取当前的线程
- getName( ) :获取此线程的名字
- setName( ) :设置此线程的名字
- yield() :调用此方法的线程释放当前CPU的执行权
- join():在A线程中调用B线程的join()方法,表示:当执行到此方法,A线程停止执行,直至B线程执行完毕,A线程再接着join()之后的代码执行
- isAlive():判断当前线程是否还存活
- sleep(long l) :显式的让当前线程睡眠l毫秒
- 线程通信: wait()、notify()、notifyAll()。这三个方法是Object的方法
- getPriority() :返回线程优先值
- setPriority(int newPriority):改变线程的优先级
二、实现线程的两种方式
实现线程的两种方式:继承Thread类、实现Runnable接口
(一)通过继承Thread类实现线程
//1. 创建一个继承于Thread的子类
class myThread extends Thread {
// 2.重写Thread类中的run()方法,方法内实现此子线程要完成的功能
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public static void main(String[] args) {
// 3.创建子类对象
Thread thread = new myThread();
// 4.调用线程的start():启动此线程;调用相应的run( )方法,一个线程只能够执行一次start(),
thread.start();
// 注:不能通过Thread实现类对象的run()去启动一个线程!
thread.run();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
(二)通过实现Runnable接口实现线程的例子
//1.创建一个实现了Runnable接口的类
class PrintNum1 implements Runnable {
//实现接口的抽样方法
public void run() {
//子线程执行的代码
for(inti=1;i<=100;i++){
if(i%2==0){
System. out. println( Thread. current Thread(). getName() + ":" + i);
}
}
}
public class TestThread1
public static void main(String[] args) {
//3.创建一个Runnable接口实现类的对象
PrintNum1 p = new PrintNum1();
//要想启动一个多线程,必须调用start()
//4.将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程
Thread t1 = new Thread(p) ;
//5.调用start()方法:启动线程并执行run()
t1.start();//启动线程;执行Thread对象生成时构造器形参的对象的run()方法。
//再创建一个线程,只需用同一个对象即可
Thread t2 = new Thread(p);
t2.start();
}
(三)对比一下继承的方式vs实现的方式
- 联系: Thread implements Runnable
- 哪个方式好?实现的方式优于继承的方式
why?① 避免了java单继承的局限性
②如果多个线程要操作同一份资源(或数据),更适合使用实现的方式,共享数据所在的类可以作为Runnable接口的实现类。
三、线程的生命周期
JDK中用Thread.State枚举表示了线程的几种状态,要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
-
新建(New):当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态, 例如new Thread(r)
-
就绪(Runnable):处于新建状态的线程被start()后, 将进入线程队列等待CPU时间片,此时它已具备了运行的条件
-
运行(Running):当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能
-
阻塞(Blocked):在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU 并临时中止自己的执行,进入阻塞状态;
- 堵塞的情况分三种:
1)、等待堵塞:执行的线程执行wait()方法,JVM会把该线程放入等待池中。
2)、同步堵塞:执行的线程在获取对象的同步锁时,若该同步锁被别的线程占用。则JVM会把该线程放入锁池中。
3)、其它堵塞:执行的线程执行sleep()或join()方法,或者发出了I/O请求时。JVM会把该线程置为堵塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完成时。线程又一次转入就绪状态。
-
死亡(Dead):有两个原因会导致线程死亡:
- run方法正常退出而自然死亡,
- 一个未捕获的异常终止了run方法而使线程猝死。
四、线程安全
1.线程安全问题存在的原因?
由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题。
2.如何来解决线程的安全问题?
必须让一个线程操作共享数据完毕以后,其它线程才有机会参与共享数据的操作。
3. java如何实现线程的安全?
方式一:同步代码块
synchronized(同步监视器) {
//需要被同步的代码块(即为操作共享数据的代码)
)}
注:
- 共享数据:多个线程共同操作的同一一个数据(变量)
- 同步监视器(俗称:锁):由任意一个类(要求是唯一的对象)的对象来充当。哪个线程获取此监视器,谁就执行大括号里被同步的代码。
要求:所有的线程必须共用同一把锁! - 在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this
方式二:同步方法
将操作共享数据的方法声明为synchronized即此方法为同步方法
比如: public synchronized void show(){ //操作共享数据的代码}
注:
- 同步方法的锁就是当前对象
- 对于非静态的方法而言.使用同步的话,默认锁为: this。如果使用在继承的方式实现多线程的话,慎用!
- 对于静态的方法,如果使用同步.默认的锁为:当前类本身。以单例的懒汉式为例。
synchronized (Singleton.class){}
线程的同步的弊端:由于同个时间只能有一个线程访问共享数据,效率变低了
4. 释放锁的操作
- 当前线程的同步方法、同步代码块执行结束(正常执行)
- 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程,对象的wait()方法,当前线程暂停,并释放锁。
5:不会释放锁的操作
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield() 方法暂停当前线程的执行
- 线程执行同步代码块时,其他线程调用了该线程的suspend() 方法将该线程挂起,该线程不会释放锁(同步监视器)
➢应尽量避免使用 suspend()和resume() 来控制线程
6:线程死锁问题
死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
解决方法:
- 专门的算法、原则
- 尽量减少同步资源的定义
7:线程的通信
wait()与notify()和notifyAll()介绍
- wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
注:
- 使用要注意要用锁对象去使用 ,锁.wait()
- Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.langlllegalMonitorStateException异常
例子:
/*
*生产者/消费者问题
*生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
*店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一 下,
*如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一 下,
*本如果店中有产品了再通知消费者来取走产品。
*/
//店员
class Clerk {
int product;
public synchronized void addProduct() {// 生产产 品
if (product >= 20) {
System.out.println("产品已满,停止生产产品");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName()+"已生产,剩余:" + ++product);
notify();
}
}
public synchronized void consumeProduct() {// 消费产品
if (product == 0) {
System.out.println("产品不足,正在等待生产者生产产品。。。");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName()+"已消费,剩余:" + --product);
notify();
}
}
}
//生产者
class Producer implements Runnable {
Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName()+"开始生产");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
//消费者
class Customer implements Runnable {
Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
Thread.currentThread().sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"开始消费");
clerk.consumeProduct();
}
}
}
public class AccountDemo {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
Producer p2 = new Producer(clerk);
Customer c1 = new Customer(clerk);
Thread tp1 = new Thread(p1);
Thread tp2 =new Thread(p2);
Thread tc1 =new Thread(c1);
tp1.setName("生产者1号");
tp2.setName("生产者2号");
tc1.setName("消费者1号");
tp1.start();
tp2.start();
tc1.start();
}
}
运行结果:
生产者1号开始生产
生产者2号开始生产
生产者2号已生产,剩余:1
生产者2号开始生产
生产者1号已生产,剩余:2
生产者1号开始生产
生产者2号已生产,剩余:3
生产者2号开始生产
生产者1号已生产,剩余:4
生产者1号开始生产
消费者1号开始消费
消费者1号已消费,剩余:3
生产者2号已生产,剩余:4
生产者2号开始生产
生产者1号已生产,剩余:5
生产者1号开始生产
生产者2号已生产,剩余:6
生产者2号开始生产
生产者1号已生产,剩余:7
生产者1号开始生产
生产者1号已生产,剩余:8
生产者1号开始生产
生产者2号已生产,剩余:9
生产者2号开始生产
消费者1号开始消费
消费者1号已消费,剩余:8
生产者2号已生产,剩余:9
生产者2号开始生产
生产者1号已生产,剩余:10
生产者1号开始生产
生产者2号已生产,剩余:11
生产者2号开始生产
生产者1号已生产,剩余:12
生产者1号开始生产
生产者2号已生产,剩余:13
生产者2号开始生产
生产者1号已生产,剩余:14
生产者1号开始生产
消费者1号开始消费
消费者1号已消费,剩余:13
生产者1号已生产,剩余:14
生产者1号开始生产
生产者2号已生产,剩余:15
生产者2号开始生产
生产者1号已生产,剩余:16
生产者1号开始生产
生产者2号已生产,剩余:17
生产者2号开始生产
生产者1号已生产,剩余:18
生产者1号开始生产
生产者2号已生产,剩余:19
生产者2号开始生产
消费者1号开始消费
消费者1号已消费,剩余:18
生产者1号已生产,剩余:19
生产者1号开始生产
生产者2号已生产,剩余:20
生产者2号开始生产
产品已满,停止生产产品
产品已满,停止生产产品
消费者1号开始消费
消费者1号已消费,剩余:19
生产者1号开始生产
生产者1号已生产,剩余:20
生产者1号开始生产
生产者2号开始生产
产品已满,停止生产产品
产品已满,停止生产产品
消费者1号开始消费
消费者1号已消费,剩余:19
生产者2号开始生产
生产者2号已生产,剩余:20
生产者2号开始生产
生产者1号开始生产
产品已满,停止生产产品
产品已满,停止生产产品
消费者1号开始消费
消费者1号已消费,剩余:19
生产者2号开始生产