【使用JAVA实现并发编程——多线程】

简介:不同语言实现并发编程的方式存在不同,在Java标准库中,就提供了一个Thread类来表示线程!

一.Thread类的基本用法

1.创建线程的写法:

①.创建子类,继承自Thread.重写Thread类中的run()方法,在new的时候newThread的子类,在run()中写在县城中具体实现的代码,描述了这个线程内部要执行哪些代码.在代码中,并非定义了子类,一写run方法,线程就创建出来,线程的具体实现,需要调用start方法,线程才被创建出来。调用start之前,系统中并没有创建出线程。

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}

 如果在一个循环中不加任何限制,这个循环的速度非常非常快,在线程中可以加上一个sleep操作,来强制让这个线程休眠一段时间,这个休眠操作,就是强制让线程进入阻塞状态,单位是ms,相当于一秒之内这个线程不会到cpu上执行

class MyThread1 extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 一个进程中至少会有一个线程.在一个java进程中,也是至少会有一个调用main方法的线程(系统自生成),,此时t线程和main线程,就是并发执行的关系(此处的并发=并行+并发),现在两个线程都是打印一条休眠一秒,当一秒时间到了之后.系统先唤醒哪个线程的顺序是随机的.对于操作系统来说,内部对于线程之间的调度顺序,在宏观上也可以认为是随机的~~~(抢占式执行)

class MyThread1 extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo2 {
    public static void main(String[] args) {
        Thread t = new MyThread1();
        t.start();
        
        while (true){
            System.out.println("hello world");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

②.创建一个类,实现Runnable接口,再创建Runnable实例传给Thread

Runnable就是在描述一个"任务",通过Runnable来描述一个任务,进一步的再把描述好的任务交给Thread实例

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}

 ③.写法三/写法四:就是上述两个写法的翻版,使用了匿名内部类,继承自Thread类,同时重写run方法,同时在new出这个匿名内部类的实例

public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(){//匿名内部类
            @Override
            public void run(){
                System.out.println("hello thread");
            }
        };
        t.start();
    }
}

public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello thread!");
            }
        });
        t.start();
    }
}

new的Runnable,针对这个创建的匿名内部类,同时new出的Runnable实例传给Thread的构造方法 

 

④.第五种写法是第四种写法的延伸,使用lambda表达式(使用lambda代替了Runnable而已~~)

public class Demo6 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            System.out.println("hello thread");
        });
        t.start();
    }
}

 2、Thread类的其他属性和方法

①.是否后台线程(isDaemon)

*具体代码案例参考Demo7

如果线程是后台线程,就不影响进程退出;如果线程不是后台线程(前台线程),就会影响到进程退出。创建的t1和t2默认都是前台的线程,即使main方法执行完毕,进程也不能退出,得等t1和t2都执行完,整个进程才能退出;如果t1和t2是后台线程,此时如果main执行完毕,整个进程就直接退出,t1和t2就被强行终止了~~

②.给线程(thread对象)起一个名字

Thread(String name)

 仅仅只是影响程序猿调试,可以借助一些工具看到每个线程以及名字,很容易在调试中对线程做出区分~~~

public class Demo8 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() ->{
            while (true){
                System.out.println("hello thread1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"ThreadT1");
        t1.start();
        Thread t2 = new Thread(() ->{
            while (true){
                System.out.println("hello thread2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"ThreadT2");
        t2.start();
    }
}

 ③.线程是否存活(isAlive),操作系统中对应的线程是否正在运行

Thread t对象的生命周期和内核中对应的线程,生命周期并不完全一致~~

创建出t对象之后,在调用start之前,系统中是没有对应线程的~~

在run方法执行完毕之后,系统中的线程就销毁了,但是t这个对象可能还存在~~

通过isAlive就能判定当前系统的线程的运行情况

如果调用start之后,run执行完之前,isAlive就返回True;

如果调用start之前,run执行完之后,isAlive就返回False。

3、Thread中的一些重要方法

①.start(决定了系统是不是真的创建出线程)

t.run(); ---> run单纯的只是一个普通的方法,描述了任务的内容

              run方法只是一个普通的方法,在main线程里调用run,并没有创建新的线程

t.start(); --->start则是一个特殊的方法,内部会在系统中创建线程

②.中断线程~~让一个线程停下来~~

线程停下来的关键,是要让线程对应的run方法执行完~~(还有一个特殊的,是main这个线程,对于main来说,得是main方法执行完,线程就完了)

(一)、可以手动的设置一个标志位,来控制线程是否要执行结束~~~

public class Demo9 {
    private static boolean isQuit = false;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!isQuit){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        //只要把isQuit设为True,此时这个循环就退出了,进一步的run就执行完了,再进一步就是线程执行结束了
        try {
            Thread.sleep(5000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
 //main线程在此时执行isQuit = true的操作,此时t线程的while循环条件不成立,那么t线程就终止了
        isQuit = true;
        System.out.println("终止T线程");
    }
}

 (二)、更好的做法,使用Thread中内置的一个标志位来进行判定~~

可以通过:Thread.interrupted() 这是一个静态的方法;Thread.currentThread().isInterrupted() 这是实例方法,其中currentThread能够获取到当前线程的实例。

public class Demo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //在主线程中,调用interrupted方法,来中断这个线程
        //t.interrupt的意思就是t线程被中断!!!
        t.interrupt();
    }
}

 t.interrupt();调用这个方法,可能产生两种情况:①.如果t线程是处在就绪状态,就是设置线程的标志位为true;②.如果t线程处在阻塞状态(sleep休眠了),就会触发一个InterruptException~~

 此处的中断,是希望能够立即产生效果的~~如果线程以及是阻塞状态下,此时设置标志位就不能起到及时唤醒的效果~~

调用这个interrupt方法,就会让sleep触发一个异常,从而导致线程从阻塞状态被唤醒~当下的代码一旦触发了异常之后,就会进入了catch语句,在catch语句中,就单纯的只是打了一个日志。

            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //当触发异常的时候,立即就退出循环
                    break;
                }
            }

 Thread.interrupted() 这个方法判定的标志位是Thread的static成员(一个程序中只有一个标志位)。

Thread.currentThread().isinterrupted()这个方法判定的标志位是Thread的普通成员,每个实例都有自己的标志位。

③. 线程等待

多个线程之间的调度顺序不确定的~~

线程等待就是一种控制线程执行顺序的手段~~此处的线程等待,主要是控制线程结束的先后顺序~
join~

调用join的时候,哪个线程调用的join,哪个线程就会阻塞等待~~得到对应的线程执行完毕为止(对应线程的run执行完)

通过线程等待,就是在控制让t先结束,main后结束

public class Demo11 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            for (int i = 0; i < 5; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        //在主线程中就可以使用一个等待操作,等待t线程执行结束
        try {
            t.join();//首先,调用这个方法的线程是main线程,针对t这个线程对象调用的,此时就是让main等待t~~~~
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //调用join之后,main线程就会进入阻塞状态(暂时无法再cpu上执行)
        //等t线程执行完之后,才会执行下面的main线程中的代码
        System.out.println("t执行完了,main才执行");
    }
}

 join操作默认情况下,是死等(不合理),join提供了另外一个版本,就是可以执行等待时间,最常等待时间,如果等不到,就不等了去执行要执行的代码。

public class Demo12 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            for (int i = 0; i < 10; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        try {
            t.join(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("等不及了,不等了");
    }
}

 ④.获取当前线程的引用

Thread.currentThread()就能够获取到当前线程的引用;哪个线程调用的这个currentThread,就获取到的哪个线程的实例~~~

public class Demo13 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        t.start();
        
        //这个操作是在main线程中使用,拿到的就是main这个线程的实例
        System.out.println(Thread.currentThread().getName());
    }
}

 ⑤.线程休眠

sleep~~~

如果一个进程有多个线程,此时每个线程都有一个PCB,一个进程对应的就是一组PCB了~~

PCB上有一个字段tgroupid,这个id其实就相当于进程的id,同一个进程中的若干个线程的tgroupid是相同的!!!

如果某个线程调用了sleep方法,这个PCB就会进入到阻塞队列~~

操作系统调度线程的时候,就只是从就绪队列中挑选合适的PCB到CPU上运行,阻塞队列里的PCB就只能干等着~~

当睡眠时间到了,系统就会把刚才这个PCB从阻塞队列挪回就绪队列(休眠结束)

二.多线程能够提高任务完成的效率

public class Demo7 {
    private static final long count = 10_0000_0000;
    public static void serial(){
        //记录程序执行时间
        long begin = System.currentTimeMillis();
        long a = 0;
        for (int i = 0; i < count; i++) {
            a++;
        }
        long b = 0;
        for (int i = 0; i < count; i++) {
            b++;
        }
        //记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("串行消耗的时间:" + (end - begin) + "ms");
    }

    public static void concurrency() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread t1 = new Thread(() ->{
            long a = 0;
            for (int i = 0; i < count; i++) {
                a++;
            }
        });
        t1.start();
        Thread t2 = new Thread(() ->{
            long b = 0;
            for (int i = 0; i < count; i++) {
                b++;
            }
        });
        t2.start();
        t1.join();
        t2.join();
        long end = System.currentTimeMillis();
        System.out.println("并行消耗的时间:"+ (end - start)+"ms");
    }
    public static void main(String[] args) throws InterruptedException {
        serial();
        concurrency();
    }
}

【使用JAVA实现并发编程——多线程】_第1张图片

serial代表串行执行,concurrency代表了多线程并行执行。

并发执行相比于串行执行效率提升了将近50%。

main和t1、t2是并发执行的关系,此处t1和t2还没执行完呢,这里就开始记录结束时间了。因此,main线程需要等待t1、t2执行完毕后再记录结束时间。使用t1.join和t2.join,join效果就是等待线程结束,t1.join就是让main线程等待t1执行结束,t2.join让main线程等待t2结束。

创建线程本身也会消耗时间,当数据量小时,串行执行效率就会高于并发执行!

 

你可能感兴趣的:(java)