2021-08-12

多线程——个人的理解和认识

目录
1、基本概念
2、实现方法
继承Thread类
实现Runnable接口
实现Callable接口
Thread类的方法
3、Thread类及其中常用的方法
4、线程的安全问题及解决办法
同步代码块、同步方法:synchronized
锁:显示锁、公平锁及非公平锁
5、线程的六种状态
6、线程池

基本的概念

谈及线程,首先想到的应是进程。而进程很好理解,就是我们平时所用到的APP,我们使用到的一个个软件,具有自己独立的内存空间。而线程,可以说是进程进一步细化后的单位,它们是进程中的多条执行路径。一个进程中通常含有多条正在执行的线程。

多线程的实现方法

在谈到方法之前,必须说清楚线程的使用离不开两个:一个是任务对象,一个是线程(对象)。前者由我们编写清楚,将待执行的方法写在任务类中;后者则是建立一个个线程(对象)出来,然后将任务对象传给一个个线程,让线程访问任务类去执行方法。
实现方法主要是三种:一是编写一个任务类后,继承Thread类;二是令任务类实现Runnable接口;三是任务类实现Callable接口。

继承Thread类

public class TEXTextendsThread {
    public static void main(String[] args) {
        /* 创建任务对象mt1、mt2-继承Thread*/
        MyThread mt1 = new MyThread();
        new Thread(mt1).start();
        new Thread(mt1).start();
    }

     public static class MyThread extends Thread{
        //重写run方法
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"尝试:extends Thread类");
        }
    }
}

结果为:
Thread-1尝试:extends Thread类
Thread-2尝试:extends Thread类

实现Runnable接口

public class TEXTimplementsRunnable {
    public static void main(String[] args) {
        MYthread myt = new MYthread();
        /*格式1*/
        Thread t = new Thread(myt);
        t.start();
        /*格式2*/
        new Thread(new MYthread()).start();

            for (int i=0;i<4;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"   我的名字 "+i);
            }/**/

    }



    public static class MYthread implements Runnable{

        @Override
        public void run() {
            for (int i=0;i<4;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"   叫大炮 "+i);
            }
        }
    }
}

结果为:
Thread-0 叫大炮 0
main 我的名字 0
Thread-1 叫大炮 0
Thread-1 叫大炮 1
Thread-0 叫大炮 1
main 我的名字 1
Thread-0 叫大炮 2
Thread-1 叫大炮 2
main 我的名字 2
Thread-1 叫大炮 3
Thread-0 叫大炮 3
main 我的名字 3

实现Callable接口

这个接口比较特殊,情况如下:
1.可以分清主次线程,主线程接收到子线程的返回值后才会执行;
2.无法实现多个子线程,因为主线程一旦收到返回值就会执行,所以情况是如果创建了多个子线程,只有一个抢到时间片的可以执行,然后就轮到主线程执行,然后结束。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TEXTCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> myCallable = new MyCallable();
        FutureTask futureTask = new FutureTask(myCallable);
        new Thread(futureTask).start();
        new Thread(futureTask).start();
        System.out.println("返回值为:   "+futureTask.get());
        int i = 5;
        while (i > 0){
            i--;
            System.out.println(Thread.currentThread().getName()+"   输出 "+i);
        }
    }
    public static class MyCallable implements Callable{

        @Override
        public Object call() throws Exception {
            int i = 5;
            while (i > 0){
                i--;
                System.out.println(Thread.currentThread().getName()+"   输出 "+i);

            }
            return 10;
        }
    }
}

结果为:
Thread-1 输出 4
Thread-1 输出 3
Thread-1 输出 2
Thread-1 输出 1
Thread-1 输出 0
返回值为: 10
main 输出 4
main 输出 3
main 输出 2
main 输出 1
main 输出 0
从 Thread 类的定义可以清楚的发现,Thread 类也是 Runnable 接口的子类,但在Thread类中并没有完全实现 Runnable 接口中的 run() 方法,下面是 Thread 类的部分定义。

继承Thread类和实现Runnable接口完成多线程任务的区别
Private Runnable target;
public Thread(Runnable target,String name){
    init(null,target,name,0);
}
private void init(ThreadGroup g,Runnable target,String name,long stackSize){
    ...
    this.target=target;
}
public void run(){
    if(target!=null){
        target.run();
    }
}

Thread类

Thread类提供了较多的方法供我们使用,其中使用较多的有:
getName():获取当前运行的线程的名称

线程的中断:
是用线程中断自己,用待中断的线程调用interrupt方法即可。

public class TextInterruptDemo {
    public static class ThreadInterruptDemo{
        /**
        * 两者都设置了sleep,从而让两者执行快慢不同,
        * */
        public static void main(String args[]){
            MyThread mt = new MyThread();  // 实例化Runnable子类对象
            Thread t1 = new Thread(mt,"t1-线程");
            Thread t2 = new Thread(mt,"t2-线程");
        // 实例化Thread对象
            t1.start();
            t2.start();
            try{
                Thread.sleep(100);    // 线程休眠2秒
            }catch(InterruptedException e){
                System.out.println(Thread.currentThread().getName()+"   3、被唤醒");
            }
            t2.interrupt();// 中断线程执行
        }
    };

    static class MyThread implements Runnable{ // 实现Runnable接口
        public void run(){  // 重写run()方法
            System.out.println("1.  "+Thread.currentThread().getName()+"   已经进入run方法");
            try{
                Thread.sleep(500);   // 线程休眠1秒
                System.out.println("2.  "+Thread.currentThread().getName()+"  已经睡醒");
            }catch(InterruptedException e){
                System.out.println("3.  "+Thread.currentThread().getName()+"   休眠被终止");
                return;
            }
            System.out.println("4.  "+Thread.currentThread().getName()+"    run方法正常结束,未被打断");
        }
    };
}

结果为:
t2-线程 已经进入run方法
t1-线程 已经进入run方法
t2-线程 休眠被终止
t1-线程 已经睡醒
t1-线程 run方法正常结束,未被打断

5.程序安全问题

当多个线程接收到了同一个任务对象时,他们都可“解开”访问其中数据的“锁”,从而会调用到同一个数据,最终造成数据使用的不安全,例如:

public class TEXTThreadSafety {
    public static void main(String[] args) {
        TRY t = new TRY();
        /*多个线程调用同一任务对象*/
        new Thread(t,"线程-1").start();
        new Thread(t,"线程-2").start();
    }
    public static class TRY implements Runnable{
        int count = 6;

        @Override
        public void run() {

                while (true){
                    if (count > 0) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }count--;
                        System.out.println(Thread.currentThread().getName() + "   输出   " + count);

                    }else {
                        break;
                    }
                } 
        }
    }
}

结果为:
线程-1 输出 5
线程-2 输出 5
线程-1 输出 3
线程-2 输出 4
线程-1 输出 1
线程-2 输出 1
线程-1 输出 -1
线程-2 输出 -1
出现了-1的情况,而这完全不符合前面的if语句中的判断,正常是不可能出现的。而原因正在于两个线程接受到了同一任务对象t,因而两者都对int类型的count数值有改动的能力,而我又在if语句内的开头写了休眠1s从而让出错的几率变得更大:先抢到时间片的线程先进去,进去后就休眠,从而没有改动count所以此时的两个线程拿到的count值是一样的,所以一个能进去的话,另一个也可以进去,所以在前者完成休眠后,假设其此时的count = 0,并输出了结果显示count = 0,然后执行了count–的操作,此时count = -1了,则后者输出结果便是-1,从而出现了与i不符合f语句但又输出了的情况。
原因总结:没有排队,抢到时间片的前者进去后休眠了,此时抢到时间片的后者便紧随其后进入了if语句中,从而出错。
解决方法:另其排队!同步代码块、同步方法、上锁—显示锁
同步代码块和同步方法更像是隐式锁,而后者则为显示锁,有专门的锁对象。

同步代码块、同步方法:synchronized

前者现创建一个Object类型的锁对象,传入synchronized中,再给欲要排队的代码块上“锁”;后者是直接给欲要排队的方法修饰上关键词synchronized。

同步代码块

public class TEXTThreadSafety {
    public static void main(String[] args) {
        TRY t = new TRY();
        /*多个线程调用同一任务对象*/
        new Thread(t,"线程-1").start();
        new Thread(t,"线程-2").start();
    }

    public static class TRY implements Runnable{
        private int count = 6;

        @Override
        public void run() {
                while (true){
                    synchronized (this){
                        if (count > 0) {
                            try {
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }count--;
                            System.out.println(Thread.currentThread().getName() + "   输出   " + count);

                        }else {
                            break;
                        }
                    }
                }
        }
    }
}

结果为:
线程-1 输出 5
线程-1 输出 4
线程-1 输出 3
线程-1 输出 2
线程-1 输出 1
线程-1 输出 0
让线程-1抢到了时间片,然后执行完一次后就“回首掏”,从而后面每次抢到的几率都更大。

同步方法
这个更加简单,直接给待排队的方法加上一个synchronized修饰就好

public class TEXTThreadSafety {
    public static void main(String[] args) {
        TRY t = new TRY();
        /*多个线程调用同一任务对象*/
        new Thread(t,"线程-1").start();
        new Thread(t,"线程-2").start();
       new Thread(t).start();
    }

    public static class TRY implements Runnable{
        private int count = 6;

        @Override
        public synchronized void run() {
                while (true){
                        if (count > 0) {
                            try {
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }count--;
                            System.out.println(Thread.currentThread().getName() + "   输出   " + count);

                        }else {
                            break;
                        }
                }
        }
    }
}

结果为:
线程-1 输出 5
线程-1 输出 4
线程-1 输出 3
线程-1 输出 2
线程-1 输出 1
线程-1 输出 0

显式锁
在任务类的属性创建时创建ReetrantLock类的锁对象(不是在方法体里创建!),然后在待锁代码块前面通过锁对象调用lock()方法以上锁,在代码块结束位置再用锁对象调用unlock()方法以解锁。

public class TEXTThreadSafety {
    public static void main(String[] args) {
        TRY t = new TRY();
        new Thread(t,"线程-1").start();
        new Thread(t,"线程-2").start();
    }

    public static class TRY implements Runnable{
        private int count = 6;
        Lock reentrantLock = new ReentrantLock();
        @Override
        public void run() {
            
            reentrantLock.lock();
            while (true){
                if (count > 0) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }count--;
                    System.out.println(Thread.currentThread().getName() + "   输出   " + count);

                }else {
                    break;
                }
            }
            reentrantLock.unlock();
        }
    }
}

结果为:
线程-1 输出 5
线程-1 输出 4
线程-1 输出 3
线程-1 输出 2
线程-1 输出 1
线程-1 输出 0

公平锁和非公平锁
公平锁:先来先用,排队机制。
非公平锁:一块上来抢,不用排队,谁抢到谁先执行。
也是显式锁,只不过在创建锁对象时通过传入Boolean类的值true或false来区分上的是公平锁还是非公平锁。

5、线程的六种状态

在这里插入图片描述

6、线程池

顾名思义,线程池是一个容纳多个线程的容器。而之所以要有这个线程池,是因为如果每创建一个线程就只执行一个任务然后废除,会极大地消耗资源,而线程池正是容量定量或是不定量的一些线程,反复用于执行各个任务,节省资源的同时又可以较高效率完成各项任务。
种类
根据是否定量,线程池可分为两大类:一是定长类,包括定长线程池、周期性定长线程池和单线程线程池;类一定是非定长类,可以根据实情增加或释放内存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值