文章目录

  • 1.多线程概述
  • 2. 线程和进程的关系
  • 3.分析程序存在几个线程
  • 4. 实现线程的三种方式
  • 5.Thread和Runnable的区别
  • 6.线程的生命周期
  • 7.线程中的常用方法
  • 8.线程的sleep方法
  • 9. 终止线程睡眠
  • 10.关于synchronized关键字
  • 11.守护线程
  • 12.定时器作用和如何实现


1.多线程概述

什么是进程?什么是线程?

进程是一个应用程序(一个进程是一个软件)。

线程是一个进程中的执行单元或者执行场景。

2. 线程和进程的关系

举个例子:

阿里巴巴:进程A
马云:阿里巴巴的一个线程a
童文红:阿里巴巴的一个线程b

京东:进程B
刘强东:京东的一个线程a
奶茶妹妹:京东的一个线程b

进程A和进程B的内存独立不共享。
一个进程可以启动多个线程。

那么线程a和线程b呢?

在java语言中,线程a和线程b堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。
假设启动十个线程,会有十个栈空间,每个栈与每个栈之间各自执行各的,互不干扰,这就是多线程并发。

思考一个问题:使用了多线程机制后,main方法结束,是不是整个程序也不会结束?

main方法结束,只是主栈空了,其他的栈(线程)可能还在压栈弹栈。

如图:

java允许多少线程同时运行_jvm

3.分析程序存在几个线程

分析下面代码有几个线程?

public class csdnTest {
    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!");
            System.out.println("m3 over!");
        }
}

答案是:一个。

java允许多少线程同时运行_java允许多少线程同时运行_02

4. 实现线程的三种方式

第一种方式:

/*
实现线程的第一种方式:编写一个类,继承Thread,重写run方法
*/
public class ThreadTest01 {
    public static void main(String[] args) {
        //创建一个分支栈的对象
        elseThread elseThread=new elseThread();
        //启动线程
        //start()方法的作用是:启动一个分支线程,在jvm中开辟一个新的栈空间,这段代码任务完成后就,瞬间就结束了
        //启动成功的线程会自动调用run()方法,并且run方法在分支栈的底部(压栈)
        //run方法在分支栈的底部,main方法在主栈的栈底部,run和main是平级的
        //elseThread.run(); 不会启动线程,不会分配新的分支栈(这种方式就是单线程)只是一个普通方法的调用
        elseThread.start();//这行代码不结束,下面的代码永远不会执行
        //这里的代码还是在主线程中
        for (int b=0;b<1000;b++){
            System.out.println("主线程————————"+b);
        }
    }
}
class elseThread extends Thread{
    public void run() {
        for (int a=0;a<1000;a++){
            System.out.println("支线程——————"+a);
        }
    }
}

运行结果:出现以下结果和线程抢占cpu时间片有关,主线程和支线程共同抢夺cpu时间片(也就是执行权),具体原理参考下面线程生命周期详解。

java允许多少线程同时运行_java_03

第二种方式:

/*
实现线程的第二种方式,编写一个类,实现Runnable接口,实现run方法,这种方式比较常用,因为一个类实现了一个接口,还可以继承其他的
类,但是一个类继承了一个类,就无法继承其他的类了
*/
public class ThreadTest02 {
    public static void main(String[] args) {
        s s=new s();
        Thread thread=new Thread(s);
        thread.start();
        for (int b=0;b<1000;b++){
            System.out.println("主线程————————"+b);
        }
    }
}
class s implements Runnable{
    @Override
    public void run() {
        for (int a=0;a<1000;a++){
            System.out.println("支线程——————"+a);
        }
    }
}

运行结果:

java允许多少线程同时运行_开发语言_04

第三种方式:

/*
采用匿名内部类的方法
*/
public class ThreadTest03 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int a = 0; a < 1000; a++) {
                    System.out.println("支线程——————" + a);
                }
            }
        });
        t.start();
        for (int b = 0; b < 1000; b++) {
            System.out.println("主线程————————" + b);
        }
    }
}

运行结果:

java允许多少线程同时运行_java_05

5.Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

6.线程的生命周期

线程生命周期存在五种状态:

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)

(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

如图:

java允许多少线程同时运行_开发语言_06

7.线程中的常用方法

1.设置线程名字
2.获取线程名字
3.启动支线程

如下:

public class ThreadTest04 {
    public static void main(String[] args) {
        sss s=new sss();
        //支线程永远次于主线程执行*****
        s.start();
        //设置线程名字
        s.setName("s1");
        //输出线程名字
        System.out.println(s.getName());
    }
}
class sss extends Thread{
    @Override
    public void run() {
        for (int a=0;a<1000;a++){
            System.out.println("支线程——————"+a);
        }
    }
}

运行结果:

java允许多少线程同时运行_java允许多少线程同时运行_07


4.获取当前线程对象

如下:

public class ThreadTest05 {
    public static void main(String[] args) {
        gxs gxs=new gxs();
        Thread thread=new Thread(gxs);
        thread.start();
        //获取当前线程对象 currentThread就是当前线程,当前线程也就是main方法主线程
        Thread currentThread=Thread.currentThread();
        thread.setName("支线程");
        //输出main
        System.out.println(currentThread.getName());
        for (int b=0;b<1000;b++){
            System.out.println(currentThread.getName()+"————————"+b);
        }
    }
}
class gxs implements Runnable{

    @Override
    public void run() {
        for (int a=0;a<1000;a++){
            Thread thread=Thread.currentThread();
            System.out.println(thread.getName()+"——————————"+a);
        }
    }
}

运行结果:

java允许多少线程同时运行_jvm_08

8.线程的sleep方法

Thread.sleep(long millis)

使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;

如下:

/*
关于线程sleep方法:
1.静态方法
2.参数是毫秒
3.作用:让当前线程进入休眠状态,进入阻塞状态,放弃占有cpu时间片,让给其他线程使用
    这段代码出现在a线程就会让a线程休眠
    这段代码出现在b线程就会让b线程休眠
*/
public class ThreadTest06 {
    public static void main(String[] args) {
        try {
            //让当前线程休眠五秒钟
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("睡醒了!");
    }
}

运行结果:程序等待五秒钟,然后输出“睡醒了!”

java允许多少线程同时运行_java_09

sleep方法可以模拟计时器:

//相当于是个定时器
public class ThreadTest07 {
    public static void main(String[] args) {
        for (int i=0;i<=10;i++){
            System.out.println(i);
            try {
                //每隔一秒钟输出一个数字
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

运行结果:每隔一秒输出一个数字

java允许多少线程同时运行_jvm_10

9. 终止线程睡眠

依靠java异常处理机制,中断当前线程睡眠,使用关键字interrupt。

public class ThreadTest08 {
    public static void main(String[] args) {
        Thread thread=new Thread(new teacher());
        thread.start();
        thread.setName("hahah");
        //模拟睡眠,希望线程睡眠五秒钟醒来
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
            //中断线程睡眠,这种中断睡眠的方式依靠了java异常处理机制
        }thread.interrupt();
    }
}
class teacher implements Runnable{
    //这里不能throws,因为子类重写,不能抛出比父类更多的异常
    //因为run方法的父类没有抛出任何异常.所以子类不能抛出异常
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-------"+"begin");
        try {
            Thread.sleep(10000*60*60*24*365);
        } catch (InterruptedException e) {
            //throw语句一执行,后面的语句执行不到了
          throw new RuntimeException(e);

        }
        System.out.println("over");
    }
}

运行结果:支线程本应该睡眠一年,但是在主线程里面模拟线程睡眠五秒钟,然后通过java异常处理机制,抛出异常,终止线程睡眠。

java允许多少线程同时运行_System_11

10.关于synchronized关键字

synchronized关键字的作用:

在 Java 中,关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能),这点确实也是很重要的。

synchronized的三种应用方法:

1.修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。
2.修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁,一个对象一把锁。
3.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

第一种方式:修饰实例方法

/*
多线程代码现在的处理器一般都能解决,唯一需要解决的是线程安全*****
线程同步这一块,涉及两个专业术语:
     异步编程模型:异步就是并发
     同步编程模型:同步就是排队
*/
public class ThreadsafeTest10 {
    public static void main(String[] args) {
        //创建一个对象
        bank b=new bank("gxs",10000);
        //创建两个线程
        Thread t=new account(b);
        Thread t1=new account(b);
        //设置线程名字
        t.setName("t1");
        t1.setName("t2");
        //启动两个线程
        t.start();
        t1.start();
    }
}
class bank {
    String id;
    double balance;

    public bank() {
    }

    public bank(String id, double balance) {
        this.id = id;
        this.balance = balance;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public double getBalance() {
        return balance;
    }
        //定义一个模拟银行取款的方法
    public void setBalance(double balance) {
        this.balance = balance;
    }
    public synchronized void tikuan(double num) throws InterruptedException {
        //t1和t2线程并发,两个线程对应两个栈,操作同一个对象
        //synchronized () {线程同步代码块},处理线程同步机制语法,小括号里面填什么?填多个线程共享的对象,这里的共享对象是银行对象
       // Thread.sleep(1000);
            double before = this.balance;
            double after = before - num;
            //模拟发生网络延迟,余额更新慢延迟了
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            //更新余额
            this.setBalance(after);
            //System.out.println(after);
    }
}
class account extends Thread{
    private bank b;

    public account(bank b) {
        this.b=b;
    }

    @Override
    public void run() {
        //模拟取款操作
        double money=5000;
        //调用取款方法
        try {
            b.tikuan(money);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread()+"对"+b.getId()+"取款成功"+"余额为"+b.getBalance());
    }
}

运行结果:使用synchronized关键字可以避免两个用户同时对同一种银行卡进行取款操作时,发生同时取走5000元,余额却还显示5000元。

java允许多少线程同时运行_System_12

第二种方式:修饰静态方法

public class test02 {
    public static void main(String[] args) throws InterruptedException {
        gxs g=new gxs();
        Thread t1=new mythread(g);
        Thread t2=new mythread(g);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}
class mythread extends Thread{
    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1")) {
               g.dosome();
           }
        if (Thread.currentThread().getName().equals("t2")) {
               g.doother();
       }
    }
    public gxs g;
    public mythread(gxs g) {
        this.g=g;
    }
}
class gxs{
    //这是类锁
    public synchronized static void dosome(){
        System.out.println("begin");
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("over");
    }
    //这是对象锁
    public synchronized static void doother(){
        System.out.println("过一会再输出吧");
    }
}

运行结果:先输出begin,过五秒几乎同时输出“over”和“过一会再输出吧”。注意:必须要对两个静态方法都要加上synchronized关键字,否则doother方法是不会等待t1线程结束再执行的。

java允许多少线程同时运行_jvm_13

第三种方式:修饰代码块

/*
多线程代码现在的处理器一般都能解决,唯一需要解决的是线程安全*****
线程同步这一块,涉及两个专业术语:
     异步编程模型:异步就是并发
     同步编程模型:同步就是排队
*/
public class ThreadsafeTest10 {
    public static void main(String[] args) {
        //创建一个对象
        bank b=new bank("gxs",10000);
        //创建两个线程
        Thread t=new account(b);
        Thread t1=new account(b);
        //设置线程名字
        t.setName("t1");
        t1.setName("t2");
        //启动两个线程
        t.start();
        t1.start();
    }
}
class bank {
    String id;
    double balance;

    public bank() {
    }

    public bank(String id, double balance) {
        this.id = id;
        this.balance = balance;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public double getBalance() {
        return balance;
    }
        //定义一个模拟银行取款的方法
    public void setBalance(double balance) {
        this.balance = balance;
    }
    public void tikuan(double num) throws InterruptedException {
        //t1和t2线程并发,两个线程对应两个栈,操作同一个对象
        //synchronized () {线程同步代码块},处理线程同步机制语法,小括号里面填什么?填多个线程共享的对象,这里的共享对象是银行对象
       // Thread.sleep(1000);
        synchronized (this) {
            double before = this.balance;
            double after = before - num;
            //模拟发生网络延迟,余额更新慢延迟了
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            //更新余额
            this.setBalance(after);
            //System.out.println(after);
        }
    }
}
class account extends Thread{
    private bank b;

    public account(bank b) {
        this.b=b;
    }

    @Override
    public void run() {
        //模拟取款操作
        double money=5000;
        //调用取款方法
        try {
            b.tikuan(money);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread()+"对"+b.getId()+"取款成功"+"余额为"+b.getBalance());
    }
}

运行结果:

java允许多少线程同时运行_java_14

11.守护线程

概述:

java提供两种类型线程:一个是用户线程,一个是守护线程。
用户线程:是高优先级线程。JVM 会在终止之前等待任何用户线程完成其任务。
守护线程: 是低优先级线程。其唯一作用是为用户线程提供服务。

守护线程用来做什么?

常见的做法,就是将守护线程用于后台支持任务,比如垃圾回收、释放未使用对象的内存、从缓存中删除不需要的条目,按照这个解释,那么大多数 JVM 线程都是守护线程。

如何创建守护线程?

守护线程也是一个线程,因此它的创建和启动其实和普通线程没什么区别,要将普通线程设置为守护线程,方法很简单,只需要调用 Thread.setDaemon() 方法即可。

代码如下:旨在用户线程结束时,守护线程也跟着结束。

public class BakDataThreadTest {
    public static void main(String[] args) {
    //创建守护线程对象,父类型引用指向子类型对象
    Thread thread=new bakdata();
    //启动线程之前,将这个线程设置为守护线程
    thread.setDaemon(true);
    thread.start();
    thread.setName("守护线程");
    Thread current=Thread.currentThread();
    current.setName("主线程");
    //主线程:也就是用户线程
    for (int i=0;i<=10;i++){
        System.out.println(Thread.currentThread().getName()+"---------->"+i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    }
}
//这是个守护线程
class bakdata extends Thread{
    @Override
    public void run() {
        int i=0;
        //虽然是个死循环,但是用户线程一结束,守护线程自动结束
        while(true){
            System.out.println(Thread.currentThread().getName()+"------->"+(++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

运行结果:主线程循环十次输出到10,守护线程察觉到用户线程结束,便也会自动结束。

java允许多少线程同时运行_java_15

12.定时器作用和如何实现

作用:

间隔特定的时间,执行特定的程序。
每周要进行银行总账操作。
每天要进行数据备份操作。

在实际开发中,每隔多久执行一个程序是很常见的。在java中有很多种实现方式:

1.可以使用sleep方法,每隔一段时间线程醒来,执行特定的程序,这是最原始的定时器(但是比较low)。
2.在java中已经写好了一个定时器:java.util.Timer,可以直接拿来用,但是这种方式在目前开发中还用得少,因为现在很多高级框架都时支持定时任务的。
3.在实际的开发中,目前是用的比较多的是spring框架中提供的springTask框架,在这个框架中只要进行简单的配置,就可以实现定时器任务。

实现方式:

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) {
        //创建定时器对象
        Timer timer=new Timer();
        //获取当前时间
        Date nowtime=new Date();
        System.out.println(nowtime);
        //给日期制定一个格式
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        //SimpleDateFormat里面有一format方法,将现在的时间转换成指定格式
        //System.out.println(sdf.format(nowtime));

        timer.schedule(new logtimertask(),nowtime,1000*10);
    }
}
//public abstract class TimerTask
//extends Object
//implements Runnable
class logtimertask extends TimerTask {

    @Override
    public void run() {
        //编写你需要执行的任务
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        String time=sdf.format(new Date());
        System.out.println("电脑"+time+" 完成日志记录!");
    }
}

运行结果:

java允许多少线程同时运行_jvm_16