【Java多线程 32】

一、概念

1、程序(program):是为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

2、进程(process):是程序的一次性执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生,存在和消亡的过程。----生命周期

  • 程序是静态的,进程是动态的
  • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

3、线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

  • 若一个进程同一时间并行支撑多个线程,就是支持多线程的
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
  • 一个进程中的多个线程共享相同的内存单元/内存地址空间-->他们从同一堆中分配对象,可以访问相同的变量和对象,这就使的线程间通信更简便、高效。但是多个线程操作共享的系统资源可能就会带来安全的隐患

二、线程的创建和使用

 1、Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类实现

2、Thread类的特性

  |-- 每个线程都是通过某个特定的Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体

  |-- 通过改Thread对象的start()方法来启动这个线程,而非直接调用run()

---->即两种方式创建多线程

**************第一种********************

package com.csii.day01;

/**
 * @Author wufq
 * @Date 2020/11/17 17:33
 * 多线程的创建,方法一:继承与Thread类
 * 1、创建一个继承于Thread类的子类
 * 2、重写Thread类的run()  -->将此线程执行的操作声明在run()中
 * 3、创建Thread类的子类的对象
 * 4、通过此对象调start()方法 -->使该线程开始执行;Java虚拟机调用该线程的run()方法
 *
 * 例子:遍历100以内所有的偶数
 *
 */
//1、创建一个继承于Thread类的子类
class MyThread extends Thread{

//    2、重写run()方法

    @Override
    public void run() {
        for(int i=0;i<50;i++){
            if(i%2==0){
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args){
//        3、创建Thread类的子类对象
        MyThread  t1= new MyThread();

//        4、调用start()方法
        t1.start();

        for(int i=0;i<50;i++){
            if(i%2==0){
                System.out.println(i+"******main()********");
            }
        }
    }
}


----------------
既有主线程在执行,又有MyThread类的run线程子执行,所有就实现了多线程调用

两个问题:

问题一:我们不能通过直接调用run()的方法启动线程,要想启动多线程只能调用start方法

调用了run()方法就相当于是单线程对象调用run方法,程序从上到下执行

问题二:在启动一个线程,遍历100以内的偶数
不可以让已经start()的线程去执行,会报java.lang.IllegalThreadStateException错误

我们需要重新创建一个线程的对象,调用start方法,或者循环控制几个线程

 for(int i=0;i<=3;i++){

//            3、创建Thread类的子类对象
            MyThread  t1= new MyThread();


//        4、调用start()方法
            t1.start();
            System.out.println("=====");

        }

练习题:

package com.csii.day02;

/**
 * @Author wufq
 * @Date 2020/11/18 11:25
 * 练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
 */
public class ThreadExer {

    public static void main(String[] args){
        //第一种直接声明子类对象,调用start()方法
        MyThread1 m1 = new MyThread1();
        MyThread2 m2 = new MyThread2();

        m1.start();
        m2.start();

        //第二种创建Thread类的匿名子类的方式

        new Thread(){
            @Override
            public void run() {
                for(int i=0;i<100;i++){
                    if(i%2==0){
                        System.out.println(Thread.currentThread().getName()+":"+i);
                    }
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                for(int i=0;i<100;i++){
                    if(i%2!=0){
                        System.out.println(Thread.currentThread().getName()+":"+i);
                    }
                }
            }
        }.start();
    }

}

class MyThread1 extends Thread{

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}


class MyThread2 extends Thread{
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            if(i%2!=0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

====执行结果:======
Thread-0:0
Thread-0:2
Thread-0:4
Thread-0:6
Thread-0:8
Thread-0:10
Thread-0:12
Thread-0:14
Thread-0:16
Thread-0:18
Thread-0:20
Thread-0:22
Thread-0:24
Thread-1:1
Thread-1:3
Thread-1:5
Thread-1:7
Thread-1:9
Thread-1:11
Thread-1:13
Thread-1:15
Thread-1:17
Thread-1:19
Thread-0:2

 3、线程常用方法

1、start()启动当前线程;调用当前线程的run()
2、run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3、currentThread():静态方法,返回当前代码的线程
4、getName():获取当前线程的名字
5、setName():设置当前线程的名字
6、yield():释放当前cpu的执行权
7、join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,知道线程b完全执行完以后,线程a才结束阻塞状态。
8、stop():已过时,当执行此方法时,强制结束当前线程
9、sleep(long millis):让当前线程“睡眠”指定的millitime毫秒
10、isAlive():判断当前线程是否存活

package com.csii.day02;

/**
 * @Author wufq
 * @Date 2020/11/18 15:06
 */
public class ThreadMethod {
    public static void main(String[] args){

        /*
        * 给线程起名:
        * 1、setNanme()
        * 2、用子类继承父类Thread的构造方法在声明对象时命名
        * 3、给主线程命名:Thread.currentThread().setName("主线程");
        * */
        HelloThread h2 = new HelloThread("Thread-1");

        HelloThread h1 = new HelloThread();

        h1.setName("线程一");
        h1.start();

        //给主线程恒命名
        Thread.currentThread().setName("主线程");

        for(int i=0;i<50;i++){
            if(i%2 == 0 ){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }

            /*
            * join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,知道线程b完全执行完以后,线程a才结束阻塞状态。
            * */
            if(i == 20){
                try {
                    h1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}


class HelloThread extends Thread{

    @Override
    public void run() {
        for(int i=0;i<50;i++){
            if(i%2 == 0 ){
                try {
                    sleep(1000);//让当前线程“睡眠”指定的millitime毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":"+i);
            }

            /*
            *yield():释放当前cpu的执行权
            **/
            if(i%20==0){
                yield();
            }
        }
    }

    //用子类的构造方法命名

    public HelloThread(String name) {
        super(name);
    }

    public HelloThread(){}
}

4、线程优先级的设置

Java的调度方法

|-- 同优先级线程组成先进先出队列(先到先得服务),使用时间片策略

|-- 对高优先级,使用有线调度的抢占式策略

线程优先级:
* 1、
* MAX_PRIORITY :10
* MIN_PRIORITY :1
* NORM_PRIORITY : 5 -->默认的优先级
*
* 2、如何获取或设置当前线程的优先级
* setPriority():设置线程的优先级
* getPriority():获取线程的优先级
*
* 说明:高优先级的线程要抢占低优先级线程CPU的执行权,但是只是从概率上讲,高优先级的线程高概率的情况下
    被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才被执行

    (设置了优先级只是表明先执行的概率大,并不是一定先执行优先级高的线程)

package com.csii.day02;

/**
 * @Author wufq
 * @Date 2020/11/18 17:36
 *
 * 线程优先级:
 * 1、
 * MAX_PRIORITY :10
 * MIN_PRIORITY :1
 * NORM_PRIORITY : 5  -->默认的优先级
 *
 * 2、如何获取或设置当前线程的优先级
 * setPriority():设置线程的优先级
 * getPriority():获取线程的优先级
 *
 * 说明:高优先级的线程要抢占低优先级线程CPU的执行权,但是只是从概率上讲,高优先级的线程高概率的情况下
 * 被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才被执行
 *
 *
 */
public class ThreadPriority {
    public static void main(String[] args){
        MyThread3 m3 = new MyThread3("分线程");
        m3.setPriority(Thread.MAX_PRIORITY);

        m3.start();

        Thread.currentThread().setName("=主线程=");
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

        for(int i=0;i<50;i++){
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+"*"+i);
            }
        }

    }
}


class MyThread3 extends Thread{

    @Override
    public void run() {
        for(int i=0;i<50;i++){
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+getPriority()+"*"+i);
            }
        }
    }

    public MyThread3(String name) {
        super(name);
    }
}

 **************第二种********************

定义一个类实现Runnable接口

1、创建一个实现Runnable接口的类

2、实现类去实现Runnable中的抽象方法:run()

3、创建实现类对象

4、将此对象作为参数传递到Thread类的构造器,创建Thread类的对象

5、通过Thread类的对象调用start()

package com.csii.day02;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Author wufq
 * @Date 2020/11/24 14:57
 */
public class ThreadTest1 {
    public static void main(String[] args){

        //3、创建实例类对象
        MyThread1 mt = new MyThread1();
        Date now = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        String start_time = dateFormat.format( now );
        System.out.println("多线程查询开始 = 》 start_time:"+start_time);
        for(int i=0;i<3;i++){

            //4、将此对象作为参数传入Thread类的构造器中,创建Thread类的对象
            Thread t1 = new Thread(mt);
            //5、通过Thread类的对象调用start():@1: 启动线程  @2:调用当前线程的run()--> 调用了Runable类型的target的run()方法
            t1.start();
        }
    }
}
// 1、创建子类继承Runable接口
 class MyThread001 implements Runnable{

    //2、重写run()方法
    @Override
    public void run() {
        for(int i=0;i<50;i++){
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

=====执行结果:=====
多线程查询开始 = 》 start_time:2020/11/24 15:18:09
Thread-1:0
Thread-1:2
Thread-1:4
Thread-1:6
Thread-1:8
Thread-1:10
Thread-2:0
Thread-2:2
Thread-2:4
Thread-2:6
Thread-2:8
Thread-2:10
Thread-2:12
Thread-2:14
Thread-1:12
Thread-3:0
Thread-3:2

举例:多窗口卖票,总票为100张,使用Runerable接口的方式

package com.csii.day02;

/**
 * @Author wufq
 * @Date 2020/11/24 15:21
 * 多窗口卖票,总票为100张,使用Runerable接口的方式
 * 存在线程安全,待解决
 */
public class WindowTest01 {
    public static void main(String[] args){
         //只声明了一个对象,放到了三个构造器内,
        // ticket就不需要前面加static,如果声明了多个对象,在用ticket时就必须加static,主要作用是静态变量公用
        Ticket ticket = new Ticket();

        /*Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();*/

        for(int i=0;i<3;i++){
            Thread t1 = new Thread(ticket);
            t1.setName("窗口"+i+" ");
            t1.start();
        }
    }
}



class Ticket implements Runnable{

    private  int ticket =100;
    @Override
    public void run() {
        while (true){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
                ticket --;
            }else {
                break;
            }
        }
    }
}

=====执行结果:=====
窗口1 卖票,票号为:100
窗口2 卖票,票号为:100
窗口0 卖票,票号为:100
窗口2 卖票,票号为:98
窗口1 卖票,票号为:99
窗口1 卖票,票号为:95
窗口2 卖票,票号为:96
窗口0 卖票,票号为:97
窗口0 卖票,票号为:92

--->实现了三个窗口共卖100张票,不存在重复卖票的问题(窗口1卖了第99张,其他窗口只能卖99张以后的了,98,97张)

比较创建线程的两种方式。

开发中:优先选择:实现Runnable接口的方式

原因:1、实现的方式没有类的单继承性的局限性

2、实现的方式更适合来处理多个线程共享数据的情况

联系:public class Thread implements Runable

相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中

原文地址:https://www.cnblogs.com/frankruby/p/13919780.html