初识java多线程 (java多线程基础知识汇总)

来源:互联网 发布:软件行业能力资质 编辑:程序博客网 时间:2024/06/06 15:48
线程Thread

首先明确:
1.了解进程与线程之间的概念
2.线程的好处
3.创建线程
4.线程的操作
5.线程的缺点,如何防范。

===========================================

一、概念
   1. 进程与线程

      a. 进程---就是正在进行的程序。
         CPU在某一时间段只处理一个程序。
程序也是一个进程,当程序结束,进程消失。

      b. 线程---就是程序中的一个执行路径(控制单元)
线程在控制着进行的执行;
它是真正的执行者。

    进程与线程的关系:一个进程中至少包含有一个或多个线程。



   示例:得到当前线程名

   System.out.println(Thread.currentThread().getName());

   示例:创建一个线程,测试主线程与其他线程
         效率高是因为利用了程序间进入CPU的时间差


   2. 多线程存在的意义
多线程可以让我们的程序部分可以产生同时运行的效果,各玩各的。
提高效率是其次,主要是能让多段代码同时执行。(例如 打撸啊撸的时候,我可以打个蓝爸爸,又可以打个红爸爸,我就拥有了双层Buffer啦)

   3. 多线程的目的:
为了最大限度的利用CPU资源。

   4. 线程调度: 按照特定的机制为多个线程分配CPU的使用权。

调度的模式有两种:
(1) 分时调度:所有线程轮流获得CPU使用权,并平均分配每个线程占用CPU的时间;
(2) 抢占式调度:根据线程的优先级别来获取CPU的使用权。
JVM的线程调度模式采用了抢占式模式。

    总结:虽然操作系统是多线程的,但CPU每一时刻只能做一件事,和人的大脑是一样的。




二、线程创建方式一:继承Thread类

   在Java中,多线程的实现有两种方式:
继承java.lang.Thread类
实现java.lang.Runnable接口


   1. Thread类创建,分两种写法:
   步骤:
      第1步:定义类继承Thread类;
      第2步:子类中重写Thread类中的run()方法;
      第3步:调用线程的start()方法


   写法一:
   子类:-------------------------------------------------
public class Person extends Thread{    @overriad    public void run(){System.out.println("This is a thread");    }}
   测试类:------------------------------------------------
 Person ps=new Person(); //初始化线程   ps.start();             //启动线程并调用run方法

   写法二:
   测试类:------------------------------------------------
      
new Thread(){@overriadpublic void run(){    System.out.println("这是个线程");}      }.start();


   1. 为什么要重写run方法?
目的:将自定义的代码存储在run方法中,让线程运行(也就是将同时要运行的代码写在run()方法中)。
   2. 通过对象.run()调用,不用start()方法调用也可以吗?
可以,但run()就变成主线程中的方法,与线程没关系。
因为线程没有开启,你只执行了调用。
   3. run()方法中仅仅是封装多线程运行的代码,而start()则是开启多线程的钥匙。
   4. start()方法是开启多线程,并执行run()方法。
   5. 多线程的一个特性:随机性(谁抢到谁执行,至于执行多长,CPU说了算)



三、线程的操作

    1. 获取线程的名字
    Thread.currentThread().getName();


    2. 设置线程优先级
(1)每个线程都有一个优先级;
(2)高优先级线程的执行优先于低优先级线程;
(3)每个线程都可以或不可以标记为一个守护程序。
(4)当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。
    
setDaemon(true)
//标识将该线程设置为后台线程,或者叫守护线程,或者叫精灵线程
例:对象.setDaemon(true)


setPriority()
//设置的优先级一定要足够长时间才会有效果
例:对象.setPriority(Thread.MAX_PRIORITY);  //设置最高级别10
      对象.setPriority(Thread.MIN_PRIORITY);  //最低级别1
      对象.setPriority(Thread.NORM_PRIORITY);  //值为5
注意:
(1)设置线程优先级的代码,要放在.start()前面。
(2)优先级高不一定代表该线程就先抢到执行权,理论是是可以,但实际情况不一样。


四、线程的生命周期


1.新建:刚new出来在内存中
2.就绪:start表示该线程有资格抢CPU
3.运行:抢到了CPU,会执行run方法
4.冻结:放弃了执行资格,醒了获得执行资格,但不一定马上执行。
4.死亡:执行完run方法
5.阻塞:调用了sleep方法或其他阻塞的方法


执行资格与执行权
    没执行资格的情况下,是冻结状态;
    有执行资格的是临时状态;
    既有执行资格又有执行权的是运行状态。

友情小知识点:
当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:


六、创建线程的第二种方式:Runnable接口
步骤:
    1. 创建类实现Runnable接口; 
    2. 重写Runnable接口中的run()方法;
       将线程要运行的代码存放在该run方法中。
    3. 通过Thread类建立线程对象;
    4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法;
    为什么要将Runnable接口的子类对象传递给Thread类的构造方法?因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。
    5. 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。


小案例: 《卖票程序》多个窗口卖票,1号窗口卖票,2号窗口也能卖……,多个窗口同时间都能卖,而且不出相同的票号。


为什么不用Thread而要用Runnable?
解释:一个类继承了Thread,设置变量num=100,当在测试类实例2个线程时,就有200张票了,这明显有问题的。

public class Ticket implement Runnable{private int tick=100;@overridepublic void run(){    while(true){if(tick>0){System.out.println(Thread.currentThread().getName()+" sale..."+ tick--);    }}}
public class TickDemo{    public static void main(String[] args){    Ticket t=new Ticket();  //先实例化一个卖票的对象    //实例化四个线程    Thread t1=new Thread(t);  //创建线程    Thread t2=new Thread(t);    Thread t3=new Thread(t);    Thread t4=new Thread(t);    //让四个线程开始工作    t1.start();  //执行线程启动run()    t2.start();    t3.start();    t4.start();    }}

看结果,会出现负数出来,为什么?

执行资格和执行权


线程的继承方式和实现方式有什么区别吗?
   继承Thread类:线程代码存放在Thread子类run方法中;
   实现Runnable:线程代码存在接口的子类的run方法中。


   要知道为什么设置Runnable接口非常重要。
   当一个子类中有一段代码需要多线程执行,那不能再继承Thread类时该怎么办?请用Runnable。
   (你不是我儿子,但我还是帮你实现)


   实现方式好处:避免了单继承的局限性。
                在定义线程时,
建议使用实现方式。

七、多线程的安全性
    多线程,最怕的就是出现安全问题。
    没有迸发,也不会出现这种情况。(一个人卖票就不会出现这种问题)
    这种出现的机率可能一时检测不出来,但一出现就要命。


    卧倒:具有执行资格,执行权被其它线程抢走了。
    经过了判断,在执行输出代码时卧倒了,CPU再过来执行时,数可能已变了,票有可能输出0号票,-1,-2等等错票。
    //模拟让它停一下:Thread类下面的sleep(毫秒值);


分析问题出在哪?
问题原因:
 
  当多条语句在操作同一个线程共享数据时,一个线程对多条语句时执行了一部分,还没有执行完,另一个线程参与进来执行。导致了共享数据的错误。


解决办法:
    对多条操作共享数据的语句,只能让一个线程执行完。在执行过程中,其他线程不可以参与执行。


Java对于多线程的安全问题提供了专业的解决办法:同步。
同步分:同步代码块、同步方法


锁旗标(同步):synchronized

(1) 同步代码块:
    synchronized(对象){  
//需要被同步的代码
    }
    //表示该段代码上锁
    //如果是代码块后面要放上锁旗标,如果修饰方法,那么它的锁旗标是隐含的this。

(2)锁旗标也可以修饰方法----同步方法。

关于死锁:
同步也有个很烦心的事:死锁(概率很小)
何谓死锁:
    吃饭的必要条件:要有两根筷子。
    现在你拿一根筷子,他拿一根筷子,双方都没有达到吃饭的条件,双方又不愿把自己的那根筷子让给对方,结果两个人都吃不成。
    你有一个锁,我有一个锁,我到你那里面去运行,你到我这里运行,你不放我去运行,我不放你去运行,结果就完了,都卡在那里了。


但死锁不常发生,因为有和谐的处理办法:
你给我一根筷子,我吃一口,把筷子给你,你吃一口……,交替着吃。


同步中嵌套同步,而锁却不同,就会造成死锁。
关于死锁就是互相拿着对方的锁却等着对方解锁

原创粉丝点击