前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java多线程

Java多线程

作者头像
赵哥窟
发布2018-12-28 16:09:48
9000
发布2018-12-28 16:09:48
举报
文章被收录于专栏:日常技术分享日常技术分享
多线程实现的方式
扩展java.lang.Thread类
代码语言:javascript
复制
public class Thread1 extends Thread {
    private  String name;

    public Thread1 (String name){
        this.name = name;
    }

    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(name+"运行: "+i);
            try {
                sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args){
        Thread1 mTh1=new Thread1("A");
        Thread1 mTh2=new Thread1("B");
        mTh1.start();
        mTh2.start();

    }
}

输出: A运行: 0 B运行: 0 B运行: 1 A运行: 1 A运行: 2 B运行: 2 B运行: 3 A运行: 3 A运行: 4 B运行: 4

说明:

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。

注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。 从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。 Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。 实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。

实现java.lang.Runnable接口
代码语言:javascript
复制
public class Thread2  implements  Runnable {
    private String name;

    public Thread2(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行  :  " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    public static void main(String[] args) {
        new Thread(new Thread2("C")).start();
        new Thread(new Thread2("D")).start();

    }
}

输出: C运行 : 0 D运行 : 0 C运行 : 1 D运行 : 1 C运行 : 2 D运行 : 2 C运行 : 3 D运行 : 3 C运行 : 4 D运行 : 4

说明:

Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。 在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。 实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。

线程状态转换

image.png

1、新建状态(New):新创建了一个线程对象。 2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。 3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。 4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。 (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程调度

调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会

Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量: static int MAX_PRIORITY:线程可以具有的最高优先级,取值为10。 static int MIN_PRIORITY:线程可以具有的最低优先级,取值为1。 static int NORM_PRIORITY:分配给线程的默认优先级,取值为5。

Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。

每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。 线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。 JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。

2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。

4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。 注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

常用函数
sleep(long millis)

在指定的毫秒数内让当前正在执行的线程休眠

join()

指等待t线程终止。 join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

代码语言:javascript
复制
Thread t = new AThread(); t.start(); t.join();

为什么要用join()方法 在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

代码语言:javascript
复制
public class ThreadJoin  extends Thread{
    private String name;

    public ThreadJoin(String name) {
        super(name);
        this.name=name;
    }
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 线程运行开始!");
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程"+name + "运行 : " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " 线程运行结束!");
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
        Thread1 mTh1=new Thread1("A");
        Thread1 mTh2=new Thread1("B");
        mTh1.start();
        mTh2.start();
        try {
            mTh1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            mTh2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");

    }
}

运行 main主线程运行开始! A运行: 0 B运行: 0 A运行: 1 B运行: 1 A运行: 2 B运行: 2 B运行: 3 A运行: 3 B运行: 4 A运行: 4 main主线程运行结束!

主线程一定会等子线程都结束了才结束

yield():

暂停当前正在执行的线程对象,并执行其他线程。

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。 yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

代码语言:javascript
复制
public class ThreadYield extends Thread{
    public ThreadYield(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 1; i <= 50; i++) {
            System.out.println("" + this.getName() + "-----" + i);
            // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
            if (i ==30) {
                this.yield();
            }
        }

    }

    public static void main(String[] args) {

        ThreadYield yt1 = new ThreadYield("张三");
        ThreadYield yt2 = new ThreadYield("李四");
        yt1.start();
        yt2.start();
    }
}

运行结果: 第一种情况:李四(线程)当执行到30时会CPU时间让掉,这时张三(线程)抢到CPU时间并执行。

第二种情况:李四(线程)当执行到30时会CPU时间让掉,这时李四(线程)抢到CPU时间并执行。

sleep()和yield()的区别 sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

代码语言:javascript
复制
    sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU  的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
   另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield()  方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
setPriority():

更改线程的优先级。

代码语言:javascript
复制
Thread4 t1 = new Thread4("t1");
Thread4 t2 = new Thread4("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
interrupt():

中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭. 要想结束进程最好的办法就是用sleep()函数的例子程序里那样,在线程类里面用以个boolean型变量来控制run()方法什么时候结束,run()方法一结束,该线程也就结束了。

sleep()

sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会; sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。 在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级

wait()

wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问; wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。 wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。

线程同步

为何要使用同步? java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。

同步方法

即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。 注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

代码语言:javascript
复制
synchronized 方法返回值 方法名称(参数列表){ 
 
}
代码语言:javascript
复制
public class synchronizedFun implements Runnable {
    private int ticket = 5 ;    // 假设一共有5张票
    public void run() {
        while (true){
            this.sale(); // 调用同步方法
        }
    }

    public synchronized void sale() { // 声明同步方法
        if (ticket > 0) { // 还有票
            try {
                Thread.sleep(300); // 加入延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("卖票:ticket = " + ticket--);
        }
    }

    public static void main(String args[]) {
        synchronizedFun mt = new synchronizedFun(); // 定义线程对象
        Thread t1 = new Thread(mt); // 定义Thread对象
        Thread t2 = new Thread(mt);  // 定义Thread对象
        Thread t3 = new Thread(mt);  // 定义Thread对象
        t1.start();
        t2.start();
        t3.start();
    }

}
同步代码块

即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步 代码如:

代码语言:javascript
复制
synchronized(object){ 
}
代码语言:javascript
复制
public class Ticket implements Runnable {
    private int ticket = 10; // 假设一共有5张票

    public void run() {
       while (true){
           synchronized (this) { // 要对当前对象进行同步
               if (ticket > 0) {  // 还有票
                   try {
                       Thread.sleep(300); // 加入延迟
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println("卖票:ticket = " + ticket--);
               }
           }
       }
    }

    public static void main(String args[]) {
        Ticket mt = new Ticket(); // 定义线程对象
        Thread t1 = new Thread(mt); // 定义Thread对象
        Thread t2 = new Thread(mt);  // 定义Thread对象
        Thread t3 = new Thread(mt);  // 定义Thread对象
        t1.start();
        t2.start();
        t3.start();
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.12.25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 多线程实现的方式
    • 扩展java.lang.Thread类
      • 说明:
        • 实现java.lang.Runnable接口
          • 说明:
            • 线程状态转换
              • 线程调度
              • 常用函数
                • sleep(long millis)
                  • join()
                    • yield():
                      • setPriority():
                        • interrupt():
                          • sleep()
                            • wait()
                            • 线程同步
                              • 同步方法
                                • 同步代码块
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档