加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

多线程基础

发布时间:2020-12-15 05:23:42 所属栏目:Java 来源:网络整理
导读:注:File file = new File("a.txt");不是指这个文件或者文件夹,而是引用。 一 : 进程和线程 ?? 1.1 :进程 ? ? ?? ? 指的是一个动态过程,就是代码从加载到执行完毕的过程 ??????? 特点: ??????????????? 1)独立性:每个进程是相互独立的,互不干涉 ????

注:File file = new File("a.txt");不是指这个文件或者文件夹,而是引用。

一 : 进程和线程

?? 1.1 :进程

? ? ?? ? 指的是一个动态过程,就是代码从加载到执行完毕的过程

??????? 特点:

??????????????? 1)独立性:每个进程是相互独立的,互不干涉

??????????????? 2)动态性:每个进程是一直活动的

??????????????? 3)并发性:多个进程在单个CPU上是并发进行的

?? 1.2:线程

?????????????? 线程是进程的一部分,一个进程可以有多个线程,每个线程取执行特定的任务

????????????? 1)线程是抢占式的,多个线程共用一个CPU,其实就是 CPU快速切换

?? 1.3 :两者关系

???????????? 1)线程是进程的一部分;

???????????? 2)进程是不资源共享的,线程是资源共享

???????????? 3)一个进程至少有一个线程

二:实现多进程的三种方法

? 2.1)继承Thread?? 实现Runnable? 继承Callable

??????? 1.继承Thread:继承自Thread类,Thread类是所有线程类的父类,实现了对线程的抽取和封装继承Thread类创建并启动多线程的步骤:
?? ??? ? ? ? ? ? ? ? ? ? ? ? a.定义一个类,继承自Thread类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此,run方法的方法体被称为线程执行体
?? ?? ? ? ? ? ? ? ? ? ? ? ?? b.创建Thread子类的对象,即创建了子线程
?? ? ? ? ? ? ? ? ? ? ? ? ? ? c.用线程对象的start方法来启动该线程
?????????????????????? 注意:1 程序运行时会自动创建一个线程,这个线程叫主线程;可以通过主线程创建子线程。?? ?
???? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? 2 启动线程使用start()方法,不要直接调用run()方法。

????????????????????????????????? 3主线程是JVM自动创建的

? 代码示例一

 1 public class Demo1 {
 2     public static void main(String[] args) {
 3        /**因为Mythread是继承Thread,当然肯定继承了Thread的属性name,
 4          * 但是没有继承他的构造方法,所以不能用构造方法是指线程名字
 5          * thread.setName("11");
 6          * 设置优先级
 7          * thread.setPriority(5);
 8          * 设置是否后台运行
 9          * thread.setDaemon(true);
10          */
11         MyThread thread = new MyThread();
12         thread.start();
13         for (int i = 0; i < 10; i++) {
14             System.out.println("正在执行主线程"+i);
15         }
16     }
17 }
18 public class MyThread extends Thread{
19 
20     @Override
21     public void run() {
22         for (int i = 0; i < 10; i++) {
23             System.out.println("正在执行子线程"+i);
24         }
25     }
26 }

?

???????????? 注:获取线程名字两个方法:1:Thread.currentThread.getName();这个在哪都可以准确定位目前线程名字 ? 2:thread.getName();

?????????

代码示例二(经典卖票)

public class Demo2 {
    public static void main(String[] args) {

        /**
         * 有构造方法
         * public Ticket(String name) {
         *         super(name);}
         * 所以可以用构造方法定义线程名        
         */

        Ticket ticket1 = new Ticket("窗口一");
        Ticket ticket2 = new Ticket("窗口一");
        Ticket ticket3 = new Ticket("窗口一");
        Ticket ticket4 = new Ticket("窗口一");
        ticket1.start();
        ticket2.start();
        ticket3.start();
        ticket4.start();

    }
}
public class Ticket extends Thread {

    int ticket = 100;

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

    @Override
    public void run() {
        while (true){
            if (ticket < 1)break;
            System.out.println(Thread.currentThread().getName()+"正在售卖第"+ticket+"张票");
            ticket--;
        }

    }
}

内存图解析:因为Ticket类是创建出来的,每次创建一个对象w1,w2就会进入栈,相应的堆内存也会分配空间,有各自属性int Ticket = 100,但是w1 优势各自独立的线程,所以它们又会自己创建一个栈,都有各自的栈内存

所以出现总共有400张票,每个线程栈空间是独立的,堆空间是共享的。

????? 2? 实现Runnable接口:

?????????????????????? 步骤? : a.定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行体
?? ??? ???????????????????????? ? ? b.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
?? ??? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? c.调用线程对象的start方法来启动该线程

??? 代码案例(案例:模拟售票员售票:四个窗口共卖100张票)

???

public class Demo3 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread w1 = new Thread(ticket,"w1");
        Thread w2 = new Thread(ticket,"w2");
        Thread w3 = new Thread(ticket,"w3");
        Thread w4 = new Thread(ticket,"w4");
        w1.start();
        w2.start();
        w3.start();
        w4.start();
    }
}

public class Ticket implements Runnable{

    int ticket = 100;

    @Override
    public void run() {
        while (true){
            if (ticket < 1)break;
            System.out.println(Thread.currentThread().getName()+"正在售卖第"+ticket+"张票");
            ticket--;
        }

    }
}

?

????????????????

代码&源码分析

    /**
     * 我们先看源码Run()方法
     *     @Override
     *     public void run() {
     *         if (target != null) {
     *             target.run();
     *         }
     *     }
     *  run方法中有一个属性是   "target"   尤其重要的属性,它是为何四个线程公用100张票的原因
     *  在看源码:
     *         private Runnable target;
     *   target 是Runnale类型的
     *   源码416行
     *         this.target = target;
     *   这个target就是传过来的目标类,也就是票类
     *   再看源码  Thread构造函数
     *       public Thread(Runnable target,String name) {
     *         init(null,target,name,0);
     *     }
     *   Thread构造函数调用的方法init()
     *       private void init(ThreadGroup g,Runnable target,String name,*                       long stackSize,AccessControlContext acc,*                       boolean inheritThreadLocals) {
     *    String name就是线程名字,target就是Ticket类,四个线程共用一个target对,即共同消费属性  int ticket=100;                   
     *                       
     */

分析内存图:main方法的Ticket先加载,在栈中创建Ticket引用“ticket”,在堆开辟内存空间,有属性 int ticket = 100;Thread在栈中压入引用,堆中分配内存空间属性target,四个引用都有相应的堆空间,target放入的就是目标类对象

target的地址,即都指向堆空间的target实行int target= 100;四个线程消费也都是消费同一个target

?

案例二(你和你女朋友公用一张银行卡,你向银行卡中存钱、你女朋友取钱!)

?

public class Demo1 {
    public static void main(String[] args) {
        BankCard card = new BankCard();
        AddMoney addMoney = new AddMoney(card);
        SubMoney subMoney = new SubMoney(card);

        Thread w1 = new Thread(addMoney);
        Thread w2 = new Thread(subMoney);
        w1.start();
        w2.start();

    }
}
public class BankCard {
    //银行卡里有钱
    private int money;

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }
}
public class AddMoney implements Runnable {
    //因为需要car所以私有化bankcard
    private BankCard card;

    public AddMoney(BankCard card) {
        this.card = card;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            card.setMoney(card.getMoney()+1000);
            System.out.println("增加1000,余额"+card.getMoney());
        }
    }
}


public class SubMoney implements Runnable {
    //因为需要car所以私有化bankcard
    private BankCard card;

    public SubMoney(BankCard card) {
        this.card = card;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            card.setMoney(card.getMoney()-1000);
            System.out.println("钱少了,余额"+card.getMoney());
        }
    }
}

三 : 两种方式的比较

????????? 1 继承Thread类的方式
?? ?????????? a.没有资源共享,编写简单如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还可以使用getName()获取线程名字。
? ? ? ? ? ??? b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】
????????? 2 实现Runnable接口的方式
? ? ? ? ? ? ? a.可以多个线程共享同一个资源,所以非常适合多个线程来处理同一份资源的情况
? ? ? ? ? ? ? b.资源类实现了Runnable接口。如果资源类有多个操作,需要把功能提出来,单独实现Runnable接口。
? ? ? ? ? ? ? c.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()

??????????? 总结:实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】

??????????????????? 【问题】调用start()? 和 run()区别?

?????????????????????? 当调用start()方法时将创建新的线程,并且执行run()方法里的代码,但是如果直接调用run()方法,不会创建新的线程。

???? 3)第三种? 实现? Callable接口

import java.util.concurrent.FutureTask;
/**
 * 1.有返回值
 * 2.需要借助FutureTask
 */
public class Demo1 {
    public static void main(String[] args)throws Exception {

        myCallable callable = new myCallable();

        FutureTask<Integer> task = new FutureTask<Integer>(callable);

        Thread thread = new Thread(task);
        thread.start();
        Integer integer = task.get();
        System.out.println(integer);
    }
}
import java.util.concurrent.Callable;

public class myCallable implements Callable<Integer> {

    int sum = 0;

    @Override
    public Integer call() throws Exception {

        for (int i = 1; i <=100 ; i++) {
            sum +=i;
        }

        return sum;
    }
}

第三 : 线程的常用方法

? ? ? ? ? 线程的方法
?? ?
?? ? ? ? ? ? ? ? ?? 线程对象.setName(); ?
?? ? ? ? ? ? ? ? ? Thread.currentThread().getName();
?? ? ? ? ? ? ? ? ? Thread.sleep();
?? ??????????????? 线程对象.priority();
?? ???????? ? ???? 线程对象.join();
?? ?????????? ? ?? 线程对象.setDaemone();
?? ?????????????? Thread.yield();
?? ? ? ? ? ? ? ?? 线程对象.interrupt(); ???????

?

???????? 3.1)获取设置线程名字

???????? ? ? ? ? ? 设置: 1.使用构造方法

??????????? ?? ? ????????????? 2.set.name("");

?????????????????? 获取:1.thread.getName();

????????????????????????????? 2.Thread.currentThread.getName();

?????? 3.2) 线程休眠 ??

????????????????? 使得当前正在执行的线程休眠一段时间,释放时间片,导致线程进入阻塞状态
?? ?????????????? Thread.sleep(5000),5000的单位是毫秒,设置了sleep就相当于将当前线程挂起5s,这个操作跟线程的优先级无关,当对应的时间到了之后,还会再继续执行

?????? 3.3)设置线程优先级

?????????????? 可以通过设置优先级来改变线程抢到时间片的概率,优先级高的线程获得较多的执行机会。
?? ? ? ? ? ? ? 默认情况下,每个线程的优先级都与创建它的父线程具有相同的优先级,例如:main线程具有普通优先级,则由main线程创建的子线程也有相同的普通优先级
?? ??????????? 注意:
?? ??????????????????? 优先级范围1~10,默认为5,对应的数值越大,说明优先级越高,这个方法的设置一定要在start之前
?? ? ? ? ? ? ? ? ? ? ? 线程的优先级低并不意味着争抢不到时间片,只是抢到时间片的概率比较低而已

?????? 3.4 )合并(加入)线程

?????????? ? 在执行原来线程的过程中,如果遇到了合并线程,则优先执行合并进来的线程,执行完合并进来的线程后,再回到原来的任务中,继续执行原来的线程。
?? ? ? ? ? ? ? ?? 特点:
?? ??? ? ? ? ? ? ? ? ?? a.线程合并,当前线程一定会释放cpu时间片,cpu会将时间片分给要Join的线程
?? ??? ???????????????? b.哪个线程需要合并就在当前线程中,添加要合并的线程
?? ??? ???????????????? c.join之前,一定要将线程处于准备状态start

public class JoinThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<50;i++) {
            System.out.println(Thread.currentThread().getName()+"....."+i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args) throws Exception {
        //1创建线程对象
        JoinThread joinThread=new JoinThread();
        //2启动
        joinThread.start();
        //加入线程(阻塞了主线程,等join执行完完毕才继续执行)
        joinThread.join();
        //3for
        for(int i=0;i<30;i++) {
            System.out.println("主线程--------------"+i);
            Thread.sleep(20);
        }
    }

?

?

3.5 )后台线程

????????? 线程分为前台(用户)线程和后台(守护)线程。
?? ????????????????????? 后台线程:隐藏起来一直在默默运行的线程,直到进程结束,又被称为守护线程,JVM的垃圾回收线程就是典型的后台线程。特征:如果所有的前台线程都死亡,后台线程会自动死亡。
?? ????????????????????? 前台线程 :? 默认的线程都是前台线程,如果前台不执行完毕,程序不会退出。

?

?

?

?

?

?

?

?

?

public class DeamonThread extends Thread {
    @Override
    public void run() {
        for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+"...."+i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args) throws Exception{
        //1创建线程对象
        DeamonThread deamonThread=new DeamonThread();
        //设置线程为后台线程
        deamonThread.setDaemon(true);
        //2启动线程
        deamonThread.start();
        for(int i=0;i<20;i++) {
            System.out.println("主线程:"+i);
            Thread.sleep(20);
        }
}

?

3.6)线程让步(虚伪的让步)

????????? ?? 可以让当前正在执行的线程暂停,但它不会阻塞该线程,他只是将该线程转入就绪状态,完全可能出现的情况是:当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
???????????? 注意:

? ? ? ? ? ? ? ? ? ?? 实际上,当某个线程调用了yield方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程才会获得执行的机会。

public class YieldFunctionDemo01 {
    public static void main(String[] args) {
        YieldThread t0 = new YieldThread("线程000");
        //t0.setPriority(8);
        t0.start();
        
        YieldThread t1 = new YieldThread("线程111");
        t1.start();
    }

}
class YieldThread extends Thread {
    public YieldThread(){}
    public YieldThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for(int i = 0;i < 50;i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            
            if(i==20) {
                //线程让步,不会让线程进入阻塞状态
                Thread.yield();
            }
        }
    }
}

?

6)声明周期五个状态?? ?新生--->就绪---->运行---->阻塞----->死亡

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读