多线程

1,什么是进程?什么是线程?

  进程是一个应用程序(1个进程是一个软件),线程是一个进程中的执行场景/执行单元,一个进程可以有多个线程

2.对于java程序来说,当在DOS命令窗口中输入java Helloworld回车之后,先会启动jvm,而jvm就是一个进程,jvm再启动一个主线程,调用main方法,同时再启动一个垃圾回收线程负责看护回收垃圾。最起码现在的java程序中至少有两个线程并发。一个是垃圾回收线程,一个是执行main方法的主线程。

3.线程和进程是什么关系?举例:

  阿里巴巴: 进程

    马云: 阿里巴巴的一个线程

   童文红:阿里巴巴的一个线程

   京东   : 进程

   强东:京东的一个线程

   妹妹:京东的一个线程

 进程可以看做现实生活中的公司,线程可以看作是公司当中的某个员工

 注意:进程A和进程B的内存独立不共享(阿里和京东资源不会共享)

    线程A和线程B呢?

      在java语言中:线程A和线程B,堆内存和方法去内存共享 但是栈内存独立,一个线程一个栈。

 假设10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发

  举例:火车站可以看做是一个进程,火车站中的每一个售票窗口可以看作是一个线程,我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。java中之所以有多个线程机制,目的就是为了提高程序的处理效率。

4,思考一个问题

  使用了多线程机制之后,main方法结束了,是不是有可能程序也不会结束,main方法结束了只是主线程结束了,主栈空了,其它的栈(线程)可能还在弹栈压栈

5,分析一个问题?

  对于单核CPU来说,真的可以做到真正的多线程并发吗?对于多核的CPU电脑来说,真正的多线程并发是没有问题的,4核的CPU表示同一时间点上,可以真的有4个进程并发执行。

  什么是真正的多线程并发?

  t1线程执行t1的      t2线程执行t2的,  t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。

  单核的CPU表示只有一个大脑: 不能做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快。多个线程之间频繁切换执行,给人的感觉是:多个事情同时再做。

  线程A:播放音乐       线程B:运行英雄联盟

  线程A和线程B频繁切换执行,人类会感觉音乐一直播放,游戏一直在运行,给我们的感觉时同时并发的。

  例如: 电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度后,人类的眼睛产生错觉,感觉是动画的。这说明人类的反应速度很慢,就像是一根钢针扎到手上,到最终感觉到疼,这个过程时需要“很长的时间”的,在这个期间计算机可以进行亿万次的循环,所以计算机的执行速度很快。

6,分析这个程序有几个线程,除垃圾回收线程之外,有几个线程? 一个线程 因为只有一个栈

  

public class ThreadTest {
    public static void main(String[] args) {
        System.out.println("main begin");
        m1();
        System.out.println("main over");
    }
    private static void m1(){
        System.out.println("m1 begin");
        m2();
        System.out.println("m1 over");
    }
    private static void m2(){
        System.out.println("m2 begin");
        m3();
        System.out.println("m2 over");
    }
    private static void m3(){
        System.out.println("m3 begin");
    }
//    main begin
////    m1 begin
////    m2 begin
////    m3 begin
////    m2 over
////    m1 over
////    main over   进行弹栈压栈

}

 

 

 

7,java语言中,实现线程的方式有两种

  java支持多线程机制,并且java已经将多线程实现,我们只有需要继承就可以了

  第一种:编写一个类 ,直接继承java.long.Thread.  重写run()方法

    怎么创建线程对象? new对象 就行                               怎么启动线程呢? 调用线程对象的start()方法

  

public class ThreadTest01 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(); //新建一个分支线程对象
        
        myThread.start(); //不去调用myThread.start()方法,不会去启动分支线程,申请一个新的线程来执行run()方法
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程-->" + i);
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程分支" + i);
        }
    }
}

 

 

 

      start()方法的作用是,启动一个分支线程,在jvm开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。这段代码的任务只是为了开启一个新的栈空间,只要新的栈空开辟出来,start()方法就结束了。线程就会启动成功了。

  启动成功的线程会自动调用run()方法,并且run()方法在分支的栈底部(压栈)。run()方法在分支栈的栈底部,main()方法在主栈的栈底部 ,run()和main()是平级的。

  第二种:编写一个类,实现java.lang.Runable接口,实现ruan()方法。

    注意:第二种比较常用,因为一个类实现接口,它可以继承其它的类,更灵活。

    

public class ThreadTest02 {
    public static void main(String[] args) {
        MyThread01 myThread01 = new MyThread01();
        Thread t= new Thread(myThread01);
        
        t.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程--->" + i);
        }

    }
}
class MyThread01 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程" + i);
        }
    }
}

     第二种采用匿名内部类的方式

    

public class ThreadTest03 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("分支线程" + i);
                }
            }
        });
        t.start();//启动线程
        for (int i = 0; i <1000; i++) {
            System.out.println("main主线程" + i);
        }
    }
}

 8,线程生命周期

 =

 

9,线程的一些常用方法

  1,怎么获取当前线程对象?

    Thread t = Thread.currentThread()                返回值t就是当前线程

  2,获取线程对象的名字?

     String name = 线程对象.getName();

  3,修改线程对象的名字

    线程对象.setName("线程名字");

  4,当线程没有设置名字的时候,默认的名字有什么规律

    Thread ----------------0;

    Thread-----------------1;

  5,定时器 : 关于sleep()方法

    static void sleep(long millis)

    1)静态方法 :Thread.sleep(10000);

    2) 参数是毫秒

    3)作用:让当前线程进入休眠,进入”阻塞状态“,放弃占有的CPU时间片,让给其它线程使用。

        进入代码出现在A线程中,A线程进入睡眠

        进入代码出现在B线程中,B线程进入睡眠

    4)T和read.sleep() 方法,可以做到这种效果

      间隔特定时间,去执行一段特定的代码,每隔多久执行一次。

        public class Thread01 {

    public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
Thread.sleep(1000);
}
}
}

  如果 Thread t = new MyThread() //多态 t.sleep(1000*5)
  在执行的时候,还是会转换成:Thread.sleep(10000*5); 这行代码的作用是:当前线程进入休眠,也就是main线程进入休眠,出现在main方法中,main方法休眠,与MyThread03类无关
  5)sleep睡眠太久了,如果希望在半道上醒来,应该怎么办?也就是说怎么叫醒一个正在睡眠的线程?
    注意:这个不是线程的执行,是终止线程的睡眠
    t.interrupt()这个方法终止睡眠。
    强行终止线程: t.stop() 这个方法过时,已经不在使用。缺点:容易丢失数据,直接将线程杀死。
    
//重点:run()方法当中的异常不能throws,只能try-catch 因为run()方法在父类中没public class ThreadDemo05 {

 ThreadDemo05 
public static void main(String[] args) throws InterruptedException {
        MyRunnable1 r = new MyRunnable1();
        Thread t = new Thread();
        t.setName("t");
        t.start();
        r.run();
        t.sleep(3000); //模拟5秒
        r.run = false;
    }
}
class MyRunnable1 implements Runnable{
    boolean run = true; //打一个标记
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (run){
                System.out.println(Thread.currentThread().getName() + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                return; //如果false终止
            }
        }
    }

  6,静态方法:

    static void yield(); 让位方法     暂停当前正在执行的线程对象,并执行其它线程。

    yiele() 方法不是阻塞方法,让当前线程让位,让给其它线程使用。

    yiele()方法的执行会让当前线程从”运行状态“回到”就绪状态“, 

    注意:在回到就绪状态之后,有可能还会再次抢到

    

public class Thread01 extends Thread {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    if (i%100 == 0){
                        Thread.yield();
                    }
                    System.out.println(Thread.currentThread().getName() + i);
                }
            }
        });
        t.setName("t");
        t.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

 

  7,实例方法

    void join()  合并线程

     

public class Thread02 {
    public static void main(String[] args) throws InterruptedException {
        MyThread05 t = new MyThread05();
        t.start();
        t.run();
        t.join(); //当线程进入阻塞t线程执行,知道t线程结束,当前线程继续执行
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程" + i);
        }
    }
}
class MyThread05 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("分支线程" + i);
        }
    }
}

 

10,线程调度的概念(了解)

  1,常见的线程调度模型有哪些?

    抢占式调度模型:哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些,java采用的就是抢占式调度模型。

    均分式调度模型:平均分配CPU时间片,每个线程占有的CPU时间片长度一样,平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式

  2,JAVA中提供了哪些方法式和线程调度有关系的呢?

    实例方法:

        Void setPriority(int  newPriority) 设置线程的优先级

        int getPriority() 获取线程优先级

        最低优先级1,默认优先级式5,最高优先级式10,优先级比较高的获取CPU时间片可能会多一些。(但也不会是,大概率是多一些)。

11,线程不安全

  1,什么时候数据在多线程并发的环境下会存在安全问题呢?  

    三个条件: 1) 多线程并发    2)有共享数据    3)共享数据有修改的行为

    满足以上3个条件之后,就会存在线程安全问题

  2,怎么解决线程安全问题呢?

    线程排队执行 (不能并发),用排队执行解决线程安全问题,这种机制被称为:线程同步机制

    线程同步就是线程排队,线程排队了就会牺牲一部分效率。没办法,数据安全第一位,只有数据安全了,我们才可以谈效率

  3,线程同步涉及两个专业术语

    1)异步编程模型:

       线程t1和线程t2,各自执行各自的,t1不管t2,t2也不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是多线程并发(效率较高);异步就是并发。

    2)同步编程模型:

       线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束。或者说在t2线程执行的时候,必须等待t1线程执行结束。两个线程之间发生等待关系,这就是同步编程模型。

       效率比较低,线程排队执行,同步就是排队

    

public class Account {
    private String account; //账号
    private double balance; //余额

    public Account() {
    }

    public Account(String account, double balance) {
        this.account = account;
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    public void withDraw(double money){  //取款方法
        synchronized (this){
            double before = this.getBalance(); //取款之前的余额
            double after = before - money;  //取款之后的余额
            this.setBalance(after);       // 跟新余额
        }
    }
}
public class AccountThread extends Thread {
    private Account act; //两线程必须同一个共享对象
    public AccountThread(Account act){  //通过构造方法传递过来账户对象
        this.act = act;
    }

    @Override
    public void run() {
        double money = 5000; //假设取款5000
        act.withDraw(money); //取款
        System.out.println("账户" + act.getAccount() + "取款成功,余额" + act.getBalance());
    }
}
class Test01{
    public static void main(String[] args) {
        Account act = new Account("act-001", 1000);//创建账户对象
        AccountThread t1 = new AccountThread(act);
        AccountThread t2 = new AccountThread(act);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

 

12,synchronized有三种写法:

    1),同步代码块    灵活

      synchronized(线程共享对象){

        同步代码块;  

      }

    2)在实例方法上使用synchronized,表示共享对象一定是this,并且同步代码块式整个方法体。

   3),在静态方法上使用synchronized ,表示找类锁,类锁永远只有1把,就算创建100个对象,那类锁也只有一把

    对象锁:1个对象一把锁,100个对象100把锁

   类锁  : 100个对象,也可能只有1把类锁。

  聊一聊,我们以后开发中应该怎么解决线程安全问题?

  是一上来就选择线程同步吗?synchronized     不是,synchronized会让程序的执行效率低,用户体验不好。系统的用户吞吐量降低,用户体验差。在不得已的情况下再选择线程同步机制。

  第一种方案:尽量使用局部变量代替“实例变量和静态变量”。

  第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共存了。(一个线程对应一个对象,100个线程对应100个对象,对象不共存就没有安全问题了)。

  第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

13,守护线程

  1)java语言中线程分为两大类:

    一类:用户线程

    一类:守护线程(后台线程)

    其中具有代表性的就是:垃圾回收线程(守护线程);

    守护线程的特点:一般守护线程是一个死循环,所有的用户线程结束,守护线程自动结束。

     注意:主线程main是一个用户线程

    守护线程用在什么地方呢?

      每天00:00的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程,一直在那里看着,每到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。

    t.setDaemon(true); 启动线程之前将线程设置为守护线程。

  2),定时器:

    作用:间隔特定的时间,执行特定的程序

    在实际开发中,每隔多久执行一段特殊的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现;

    可以使用sleep方法,设置睡眠时间,每到这个时间点醒来执行任务,这种方式是最原始的定时器(比较low).

    在java的类库中已经写好了一个定时器:java.util.Timer. 可以直接拿来用,不过这种方式在目前的开发中很少用,因为现在有很多高级的框架都是支持定时人物的。

    在实际开发中,目前使用较多的是spring 框架中提供的springTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

    tirmer.schedule(定时任务,第一次执行时间,间隔多久执行一次);

    

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:ss:mm");
        Date firstTime = sdf.parse("2021-12-16 15:55:50");
        timer.schedule(new LongTimerTask(),firstTime,1000*2);
        /**
         * new LongTimerTask(), 定时任务
         * firstTime, 第一次执行之间
         * 1000*10 间隔多久执行一次
         */
    }
}
class LongTimerTask extends TimerTask{

    @Override
    public void run() {
        //编写你要执行的任务
        System.out.println("备份文件");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:ss:mm");
        String strTime = sdf.format(new Date());
        System.out.println(strTime + "完成一次备份");
    }
}

  3) , 实现线程的第二种方式:实现Callable接口

    这种方式实现的线程可以获取线程的返回值,之前的两种方式无法获取返回值,因为run()方法返回void

    task.get(); 获取返回值

    优点:可以获取到线程的执行结果

    缺点:效率低,在获取t线程的执行结果的时候,当前线程变阻

  4),关于object类中的wait()和notify()方法(生产者和消费者模式);

    第一 wait()和notify()方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是object类自带的。

    wait()方法和notify()方法不是通过线程对象调用的。不是这样的:t.wait(); 也不是这样的:t.notify()  不对。

     

    T线程在O对象上活动,T线程是当前线程对象。当调用o.wait()方法后,下线程进入无线等待。直到最终调用o.notify()方法,可以让正在o对象上等待的 线程唤醒。

 

posted @ 2022-12-30 10:11  阿文程序猿  阅读(82)  评论(0)    收藏  举报