美文网首页
Java多线程

Java多线程

作者: 赵哥窟 | 来源:发表于2018-12-25 16:20 被阅读4次
多线程实现的方式
扩展java.lang.Thread类
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接口
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):新创建了一个线程对象,但还没有调用start()方法。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5、休眠状态:调用sleep()方法进入休眠状态。
6、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
7、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

初始状态

实现Runnable接口或继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。

就绪状态

1.调用线程的start()方法,此线程进入就绪状态。
2.什么时候运行是由操作系统决定的,就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
3.当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
4.当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
5.锁池里的线程拿到对象锁后,进入就绪状态。

运行状态

线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

阻塞状态

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

等待

处于等待状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

休眠

调用sleep()方法让线程休眠,由于sleep()方法的执行可能抛出InterruptedException异常,所以将sleep()方法的调用存放try-catch块中。虽然使用了sleep()方法的线程一段时间内会醒来,但是并不能保证它醒来后进入运行状态,只能保证它进入就绪状态。

终止状态

1.当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
2.在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

线程调度

调整线程优先级: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()方法加入到另外一个线程时,另一个线程会等待该线程执行完毕后再继续执行。

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

常用函数
sleep(long millis)

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

join()

join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:当某个线程使用join()方法加入到另外一个线程时,另一个线程会等待该线程执行完毕后再继续执行。

Thread t = new AThread(); t.start(); t.join();

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

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()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

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()的线程有可能在进入到可执行状态后马上又被执行。

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

更改线程的优先级。

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关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

synchronized 方法返回值 方法名称(参数列表){ 
 
}
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关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
代码如:

synchronized(object){ 
}
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();
    }
}
线程通信

正常情况下,每个子线程完成各自的任务就可以结束了。不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了。

常用的方法
  wait() 该线程进入等待状态,释放锁
  notify() 随机唤醒一个线程
  notifyAll() 唤醒全部线程

如何让两个线程依次执行?

public class Thread3 {
    private static void demo1() {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                printNumber("A");
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                printNumber("B");
            }
        });
        A.start();
        B.start();
    }

    private static void printNumber(String threadName) {
        int i=0;
        while (i++ < 3) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + "print:" + i);
        }
    }

    public static void  main(String[] args){
        demo1();
    }
}

运行结果
Aprint:1
Bprint:1
Aprint:2
Bprint:2
Aprint:3
Bprint:3
可以看到 A 和 B 是同时打印的。

如果我们希望 B 在 A 全部打印 完后再开始打印呢?我们可以利用 thread.join() 方法,代码如下:

public class Thread3 {
    private static void demo1() {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                printNumber("A");
            }
        });
        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B 开始等待 A");
                try {
                    A.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                printNumber("B");
            }
        });
        A.start();
        B.start();
    }

    private static void printNumber(String threadName) {
        int i=0;
        while (i++ < 3) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + "print:" + i);
        }
    }

    public static void  main(String[] args){
        demo1();
    }
}

运行结果
B 开始等待 A
Aprint:1
Aprint:2
Aprint:3
Bprint:1
Bprint:2
Bprint:3

建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC

public class Thread4  implements Runnable{
    private String name;
    private Object prev;
    private Object self;

    public Thread4(String name, Object prev, Object self) {
        this.name = name;
        this.prev = prev;
        this.self = self;
    }

    @Override
    public void run() {

        int i = 0;
        while (++i <= 10) {
            synchronized (prev) {
                synchronized (self) {
                    System.out.println(name);
                    self. notify();
                }
                try {
                    prev.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("done");
    }

    public static void main(String []args) throws InterruptedException {
        Object a = new Object();
        Object b = new Object();
        Object c = new Object();

        Thread4 threadA = new Thread4("A", c, a);
        Thread4 threadB = new Thread4("B", a, b);
        Thread4 threadC = new Thread4("C", b, c);

        Thread A = new Thread(threadA);
        A.start();
        Thread.sleep(100);

        Thread B = new Thread(threadB);
        B.start();
        Thread.sleep(100);

        Thread C = new Thread(threadC);
        C.start();
        Thread.sleep(3000);
    }

}

相关文章

  • 带你搞懂Java多线程(五)

    带你搞懂Java多线程(一)带你搞懂Java多线程(二)带你搞懂Java多线程(三)带你搞懂Java多线程(四) ...

  • 带你搞懂Java多线程(六)

    带你搞懂Java多线程(一)带你搞懂Java多线程(二)带你搞懂Java多线程(三)带你搞懂Java多线程(四)带...

  • Java多线程目录

    Java多线程目录 Java多线程1 线程基础Java多线程2 多个线程之间共享数据Java多线程3 原子性操作类...

  • java多线程--Callable

    **移步[java多线程系列文章]Java多线程(二十二)---LockSupport工具Java 停止线程 一、...

  • android 多线程 — 线程的面试题和答案

    这里都是我从各个地方找来的资料,鸣谢: Java多线程干货系列—(一)Java多线程基础 JAVA多线程和并发基础...

  • 5月份第一周学习安排

    学习内容: java多线程及线程同步的方法(使用) java多线程各种同步方法的原理和优缺点 java多线程设计模...

  • 带你搞懂Java多线程(四)

    带你搞懂Java多线程(一)带你搞懂Java多线程(二)带你搞懂Java多线程(三) 什么是线程间的协作 线程之间...

  • Java基础(六)

    多线程 Java多线程并发 1.1 JAVA 并发知识库 1.2 JAVA 线程实现/创建方式 1.2.1 继承 ...

  • (五) volatile关键字

    Java多线程目录 1 背景 理解Java多线程的内存抽象逻辑请阅读java多线程内存模型,当代操作系统,处理器为...

  • Java多线程高级特性(JDK8)

    [TOC] 一、Java多线程 1.Java多线程基础知识 Java 给多线程编程提供了内置的支持。一条线程指的是...

网友评论

      本文标题:Java多线程

      本文链接:https://www.haomeiwen.com/subject/hrmflqtx.html