JAVASE核心之多线程

概念介绍:所谓线程就是程序的之中的单独的流程序。而线程本身是不能独立运行的只能运行在程序之中。线程只能只用分配给程序的环境和资源,而多线程指的就是在单个程序之中可以同时运行多个线程进行执行。

多线程编程的目的
多线程编程的最大目的就是最大限度的使用CPU资源,减少资源使用的浪费。

线程和进程之间的区别:
(1)多进程之间的数据状态是完全独立的,而多线程之间共享一块内存空间和系统资源,相互之间可能会产生相对的影响。
(2)线程本身使用的数据通常只有寄存器数据,以及程序执行时使用的堆栈信息,因此线程间的切换系统损耗远比进程间的要小。
(3)多线程相比多进程之间的管理费用更加的低廉。进程一般来讲程序执行的时候,系统会分配相对独立的地址空间。进程之间的通信同样花费相对线程比较巨大。
JAVA之中的线程模型
java之中的多线程机制主要优点在于取消了主循环和轮询机制。一个线程可以暂停而且不影响其他程序的执行部分。当一个线程从网络之中读取数据或者等待用户的输入之间产生的空闲时间可以被利用到其他地方。多线程允许在活得循环每一帧的间隙沉睡一秒而不需要暂停系统的服务。
关于轮询:
短轮询:浏览器发起一个“询问”请求,服务器无论有无新数据,都立即响应(有就返回新数据,没有就返回一个表示’空’的自定义数据格式),一个HTTP连接结束。
长轮询:长轮询的经典实现 —— Comet:基于 HTTP 长连接的“服务器推”技术
浏览器发起一个“询问”请求,当没有新数据时,服务器端并不立即响应,而是等待数据,当有新数据产生时,才向浏览器响应,一个HTTP连接结束。

多线程优势:(此部分为转载仅供参考)
这里写图片描述

线程的创建方式:线程的创建方式有两种。
(1)继承Threa类,并且覆盖Thread类的run()方法:这种方法的优点是Thread子类对象就是线程对象,具有Thread类的方法,且具有线程体,但是不适应多继承。因为java里类的继承是单继承的,继承了Thread类就无法继承其他类了。
(2)实现Runnable接口:如果一个类已经继承了一个类还必须要以线程方式运行,就需要实现Runnable接口。实现Runnable接口的类的对象本身并不是线程对象,只是作为一个创建线程对象的目标对象使用。

相应实例:

//方式一:继承于Thread类
class PrintNum extends Thread{
    public void run(){
        //子线程执行的代码
        for(int i = 1;i <= 100;i++){
            if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
    public PrintNum(String name){
        super(name);
    }
}


public class TestThread {
    public static void main(String[] args) {
        PrintNum p1 = new PrintNum("线程1");
        PrintNum p2 = new PrintNum("线程2");
        //设置线程优先级
        p1.setPriority(Thread.MAX_PRIORITY);//10
        p2.setPriority(Thread.MIN_PRIORITY);//1
        p1.start();
        p2.start();
    }
}
方式二:实现Runnable接口
class SubThread implements Runnable{
    public void run(){
        //子线程执行的代码
        for(int i = 1;i <= 100;i++){
            if(i % 2 == 0){             System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }           
    }
}
public class TestThread{
    public static void main(String[] args){
        SubThread s = new SubThread();
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);      
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}

两种实现方式比较:(更推荐实现Runnable方式)
①解决了单继承的局限性。
②如果多个线程有共享数据的话,建议使用实现方式,同时,共享
数据所在的类可以作为Runnable接口的实现类。

线程里的常用方法:
start() run() currentThread() getName()
setName(String name) yield() join() sleep() isAlive()
getPriority() setPriority(int i); wait() notify() notifyAll()

总结:
(1)两种方法均需要执行start方法为线程分配相应的系统资源,进行线程的调度以及run方法的调用。
(2)具体采用哪种方式进行实现,具体还是根据实际的线程体来进行实现,通常情况来讲,当一个线程已经继承了另一个类的时候,就应该采用第二种方法的方式来进行实现,即为实现runnable()接口的方式。
(3)需要注意的是,线程的消亡不是通过调用stop(),而是让run()方法执行完毕之后自然进行的消亡。

线程的生命周期:
线程的生命周期分为 : 新建 就绪 执行 消亡 四个阶段
这里写图片描述
线程状态解析:
(1)创建状态:当时用new关键字创建一个新的线程的对象的时候,该线程的处于创建的状态。(处于创建状态的线程只是一个空的线程对象,系统不为它进行分配相应的资源)
(2)可运行状态:执行线程的start方法之后,线程就处于可运行状态,系统会为线程进行分配相应的系统资源,并且进行调用run方法。(这一状态线程并不是运行中的状态,因为在这一状态的线程还未真正进行运行)
(3)不可运行状态:当发生以下情况的时候,线程就可能进入不可运行状态。
a 调用了sleep()方法
b 调用wait方法 等待特定的条件满足
c 线程输入/输出阻塞
(4)返回可运行的状态:指的是处于睡眠的线程在指定的时间过去之后,进入可运行的状态。
如果线程进入等待某一个条件的时候,这时候另一个对象就必须调用notify或者notifyAll()方法进行线程条件改变。
如果线程因为输入/输出阻塞,进行等待输入/输出的完成。
(5)消亡状态:指的是当线程的run方法执行过后,线程的自然消亡。

这里写图片描述

线程的调度策略:(仅供参考)
线程调度两种模式
1. 抢占式模式
指的是每个线程执行的时间线程切换的都由系统控制,
(系统控制指的是砸系统某种运行机制下,没跳线程都分同样的执行时间篇,也可能有些线程的时间片较长,某些深圳得不到执行片)
这种情况下 一个线程堵塞不会导致整个线程堵塞
2. 协同式模式
指某一线程执行完成后主动通知系统切换到另一个线程上执行,这种模式像接力赛一样,一个人跑完自己的路程就把接力棒交给下一个人,下个人继续往下跑.线程执行时间由线程本身控制,线程切换可以预知,不存在多线程桐本问题.但是他有一个致命的弱点,如果一个线程有问题,运营到一半就堵塞,那么可能导致整个系统崩溃。

java之中的线程调度器会优先进行选择优先级高的线程进行执行,发生以下情况的时候,就会终止线程的执行
(1)线程之中使用了yield()方法,让出了CPU的使用权。
(2)线程调用了sleep方法,使线程进入了睡眠状态。
(3)线程由于I/O操作而受阻塞。
(4)另一个更高的优先级线程的出现
(5)在支持时间片的系统中,线程的时间片用完。

线程的同步机制重点内容
引入原因:在多线程编程的环境之中,当多个线程同时对同一个共享资源进行访问的时候,就可能发生安全隐患。
解决办法:加锁机制进行解决。

前提:如果我们创建的多个线程,存在着共享数据,那么就有可能出现线程的安全问题:当其中一个线程操作共享数据时,还未操作完成,
另外的线程就参与进来,导致对共享数据的操作出现问题。
解决方式:要求一个线程操作共享数据时,只有当其完成操作完成共享数据,其它线程才有机会执行共享数据。
方式一:同步代码块:(加锁机制)
synchronized(同步监视器){
//操作共享数据的代码
}
注:1.同步监视器:俗称锁,任何一个类的对象都可以才充当锁。要想保证线程的安全,必须要求所有的线程共用同一把锁!
2.使用实现Runnable接口的方式创建多线程的话,同步代码块中的锁,可以考虑是this。如果使用继承Thread类的方式,慎用this!
3.共享数据:多个线程需要共同操作的变量。 明确哪部分是操作共享数据的代码。

方式二:同步方法:将操作共享数据的方法声明为synchronized。
比如:public synchronized void show(){ //操作共享数据的代码}
注:1.对于非静态的方法而言,使用同步的话,默认锁为:this。如果使用在继承的方式实现多线程的话,慎用!
2.对于静态的方法,如果使用同步,默认的锁为:当前类本身。以单例的懒汉式为例。 Class clazz = Singleton.class

总结:释放锁:wait();
不释放锁: sleep() yield() suspend() (过时,可能导致死锁)

死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
死锁是我们在使用同步时,需要避免的问题!

5.线程的通信:如下的三个方法必须使用在同步代码块或同步方法中!
wait():当在同步中,执行到此方法,则此线程“等待”,直至其他线程执行notify()的方法,将其唤醒,唤醒后继续其wait()后的代码
notify()/notifyAll():在同步中,执行到此方法,则唤醒其他的某一个或所有的被wait的线程。

猜你喜欢

转载自blog.csdn.net/qq_37779333/article/details/82083080
今日推荐