栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java多线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java多线程

多线程 关于线程和进程
  1. 进程A和进程B的内存独立不共享;
  2. 线程A和线程B,堆内存和方法区共享,但是栈内存独立,一个线程一个栈;
  3. 多线程并发:假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行;
  4. 使用了多线程机制之后,main方法结束,有可能程序也不会结束。main方法结束只是主线程结束了,主栈空了,其它的线程有可能还在压栈弹栈;
  5. 单核的CPU表示只有一个大脑:
    不能够做到真正的多线程并发,但是可以给人一种"多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个线程同时在执行!!!
实现线程的三种方式

1、 编写一个类,直接继承java.lang.Thread,重写run方法

–编写MyThread类继承Thread

class MyThread extends Thread{
    @Override
    public void run() {
    }
}

–创建线程对象

MyThread myThread = new MyThread()

–启动线程

myThread.start();

-调用run方法

注意1:start()方法的作用是启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程启动成功。

注意2:线程启动成功之后会自动调用run方法,并且run方法在分支栈的栈底部。main方法在主栈的栈底部。run和main是平级的。

注意:如果不执行start()方法,直接调用run()方法的话实际上并没有开启线程,只是普通的方法调用 ,两种情况的内存图如下所示

2、编写一个类,实现java.lang.Runnable接口,实现run()方法

-定义一个可运行的类

class MyRunnable implements Runnable{
    @Override
    public void run() {
       }
    }
}

-创建一个可运行的对象

MyRunnable r = new MyRunnable();

-将可运行的对象封装成一个线程对象

Thread t = new Thread(r);

-启动线程

t.start();

3、 匿名内部类

-创建线程对象,采用匿名内部类方式

  Thread t = new Thread(new Runnable() {
        @Override
        public void run() { }
    });

-启动线程

t.start();

-其他线程的代码

线程的生命周期
  1. 调用start()方法,进入就绪状态。就绪状态的线程又叫做可运行状态,表示当前线程具有抢守CPU时间片的权利(CPU时间片就是执行权),当一个线程抢到时间片之后开始执行run方法,run方法 的执行标志着线程进入运行状态

  2. run方法的开给执行标志着这个线程进入运行扶态·当之前占有的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU世间片,当再次抢到CPU时间片之后,会重新进入run方法接着上一次的代码进续往下执行。

  3. 线程在就绪和运行两个状态中频繁的变化;

  4. 当一个线程遇到阻塞事件时,例如接收用户键盘的输入,或者sleep方法等,此时线程就会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片;

获取线程对象以及名字

-获取线程对象

Thread currentThread = Thread.currentThread();

-获取线程对象的名字

String tName = t.getName();
Thread.currentThread().getName();

-修改线程对象的名字

t.setName("tttttttttt");
sleep()方法

1、关于sleep()方法

  • static void sleep(long millis, int nanos)
    
  • 是一个静态方法,静态方法的调用与调用对象无关,Thread.sleep(1000)

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

2、sleep()方法的测试

  • 测试1:可以实现每隔相等的时间执行某块代码
public static void main(String[] args) {
    for (int i=0 ;i<5 ;i++){
        System.out.println(Thread.currentThread().getName()+"--->"+i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
} //输出结果:每隔1秒输出 main--->0 main--->1 main--->2 main--->3 main--->4
  • 测试2:sleep()为静态方法,对象.sleep()并不会造成该对象线程休眠
public class ThreadTest{
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.setName("t");
        t.start();
        try {
            t.sleep(1000*2);//由于sleep方法为静态方法,该语句不会造成线程t休眠,而主线程会休眠
            //执行的时候还是会转换成  Thread.sleep(1000) 且当前线程是main ,则main线程会进入休眠。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         System.out.println("hello world!");
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}  //输出结果:瞬间输出t--->0、t--->1、t--->2、t--->3、t--->4,2秒之后输出  hello world!
终止线程的睡眠:依靠java的异常处理机制
public class ThreadTest{

    public static void main(String[] args) {

        Thread t = new Thread(new MyRunnable());
        t.setName("t");
        t.start();
        //以下代码实现5秒之后t线程醒来
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断t线程的睡眠
        t.interrupt();
    }
}
class MyRunnable implements Runnable{

    //重点: run()当中的异常不能throws ,只能try catch
    //因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"--->begin");
        try {
            //睡眠一年
            Thread.sleep(1000*60*60*24*365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"--->end");
    }
}
//执行结果  5秒之后输出 t--->end
t--->begin
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at thread.MyRunnable2.run(ThreadTest08.java:29)
	at java.lang.Thread.run(Thread.java:748)
t--->end
终止线程的执行

1、强行终止线程的执行 :t.stop() ----->不建议使用

public class ThreadTest09 {

    public static void main(String[] args) {

        Thread t = new Thread(new MyRunnable3());
        t.setName("t");
        t.start();
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒之后强行终止t线程
        t.stop();  //直接将线程杀死,容易丢失数据
    }
}
class MyRunnable3 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//执行结果  每隔1秒输出 t--->0、t--->1、t--->2、t--->3、t--->4,5秒之后线程被杀死,后续代码不再执行

2、合理终止线程的执行 通过修改boolean类型的run来结束线程;

public class ThreadTest10 {

    public static void main(String[] args) {
        MyRunnable4 r = new MyRunnable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒之后终止线程
        r.run=false;
    }
}

class MyRunnable4 implements Runnable{

    boolean run = true;
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            if (run){
                System.out.println(Thread.currentThread().getName()+"--->"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                //终止当前进程
                //在线程结束之前保存未保存的数据
                return;
            }
        }
    }
}
多线程并发的安全问题

1、 满足以下3个条件时会存在线程安全问题

  • 多线程并发
  • 有共享数据
  • 共享数据有修改的行为

2、线程同步机制(线程排队)

  • 异步编程模型(并发)

    线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1 —>其实就是多线程并发

  • 同步编程模型(排队)

    线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间存在等待关系。

 synchronized (){
    //线程同步代码块
 }
  • synchronized后面小括号中传的这个”数据“是相当关键的,这个数据必须是多线程共享的数据,才能实现多线程排队;假设t1、t2、t3、t4、 t5有5个线程,你只希望t1、t2、t3排队,t4、t5不需要排队。怎么办?一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。
  • 对象锁:
    • 假设t1先执行了,遇到了synchronized,此时自动找后面共享对象"的对象锁,找到之后,占有这把锁,然后执行同步代码块中的程序,在程序执行过程t1一直占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
    • 假设t1已经占有这把锁,此时t2也遇到synchronized关健字,也会去占有后面共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,当t1执行结束并归还这把锁后,t2占有这把锁之后,进入同步代码块执行程序。

3、java中的三大变量

  • 实例变量:存储在堆中
    • 堆只有1个,是多线程共享的,可能存在线程安全问题
  • 静态变量:存储在方法区中
    • 方法区只有1个,是多线程共享的,可能存在线程安全问题
  • 局部变量:在栈中
    • 局部变量永远不存在线程安全问题,因为局部变量不共享,一个线程一个栈
    • 局部变量在栈中,所以局部变量永远不会共享
  • 局部变量+常量:不会存在线程安全问题
  • 成员变量(包括实例变量和静态变量):可能会有线程安全问题

4、 synchronized的三种方法

  • 同步代码块
synchronized (线程共享对象){
    同步代码块;
 }
  • 在实例方法上使用synchronized

    表示共享对象一定是this,并且同步代码块是整个方法体;

  • 在静态方法上使用synchronized

    表示找类锁,类锁永远只有1把,就算创建了100个对象,类锁也只有1把

在开发中怎样解决线程安全问题
  1. 尽量使用局部变量代替实例变量和静态变量
  2. 如果必须是实例变量,则可以考虑创建多个对象
  3. 如果不能使用局部变量,对象也不能创建多个,只能选择synchronized线程同步机制
守护线程
  1. java语言中线程分为两大类:用户线程和守护线程(后台线程)
  2. 守护线程具有代表性的是垃圾回收线程
  3. 守护线程一般是死循环,所有的用户线程只要结束,守护线程自动结束
  4. 将线程t设置为守护进程
 t.setDaemon(true);
定时器
  1. 间隔特定的时间执行特定的程序
    -每周进行银行账户的总账操作
    -每天进行数据的备份操作
  2. java.util.Timer
  3. 在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架
public class TimerTask {
    public static void main(String[] args) throws Exception {

        Timer timer = new Timer();
        SimpleDateFormat sdf = new SimpleDateFormat("YYY-MM-dd HH:mm:ss");
        Date firstTime =sdf.parse("2021-09-30 15:48:00");
        timer.schedule(new java.util.TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("YYY-MM-dd HH:mm:ss");
                String strTime = sdf.format(new Date());
                System.out.println(strTime+"成功完成了一次数据备份");
            }
        },firstTime, 1000 * 10);
    }
}//执行结果 在firstTime之后每隔10秒钟进行一次输出
实现线程的另一种方式:FutureTask方式,实现Callable接口

-实现Callable接口这种线程实现方式 可以获取线程的返回值

public class ThreadTest15 {

    public static void main(String[] args) throws Exception {

        //创建未来任务类对象
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                System.out.println("call method begin");
                Thread.sleep(1000*10);
                System.out.println("call method end");
                int a=100;
                int b=200;
                return a+b;
            }
        });
        //创建线程对象
        Thread t = new Thread(task);
        //启动线程
        t.start();
        //在main主线程中怎么获得t线程的执行结果
        Object obj = task.get();
        System.out.println("执行结果:"+obj);
        //main方法这里的程序要想执行必须等待get()方法的结束,而get()方法可能需要很久。
        //因为get()方法是为了拿另一个线程的执行结果,另一个线程执行是需要时间的
        //因此主线程会在 Object obj = task.get()位置出阻塞
        System.out.println("hello world");
    }
}

明知道没有结果、没有未来,你会去试试吗?

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/298123.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号