JavaSE---多线程

JavaSE---多线程,第1张

JavaSE---多线程

目录

一、进程与线程的区别

二、实现多线程 

三、线程 Thread 中常用的API

四、线程的生命周期

五、线程调度

 六、守护线程

七、线程安全问题 


一、进程与线程的区别 1. 什么是进程?         一个进程对应一个应用程序。例如:在 windows *** 作系统启动 Word 就表示启动了一个 进程。在 java 的开发环境下启动 JVM ,就表示启动了一个进程。现代的计算机都是支持多 进程的,在同一个 *** 作系统中,可以同时启动多个进程。进程与进程之间的内存是独立的 2. 什么是线程?         线程是一个进程中的执行场景。一个进程可以启动多个线程。 3.进程与线程的区别:         每个进程是一个应用程序,都有独立的内存空间         同一个进程中的线程 共享 其进程中的内存和资源         ( 共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己的栈 。) 4.多线程有什么作用?                  多线程不是为了提高执行速度,而是提高 应用程序 的使用率。         线程和线程共享“堆内存和方法区内存”,栈内存是独立的, 一个线程一个栈 。         可以给现实世界中的人类一种错觉:感觉多个线程在同时并发执行。

二、实现多线程 

Java虚拟机的主线程入口是main方法,用户可以自己创建线程,创建多线程的俩种方法: 

        * 继承 Thread 类

        * 实现 Runnable 接口(推荐使用,因为Java只支持单继承)

 第一种方法:继承 Thread 类

 Thread 类种俩个重要的方法:

        *public void run()        //继承 Thread 类时,可重写 run 方法

        * public void start()   // 用来启动多线程

public class Test01 {
    public static void main(String[] args) {
        Thread t = new ThreadTest();
        //启动线程
        t.start();
        //这不是启动线程,单纯的调用方法!!
        t.run();
    }
}

class ThreadTest extends Thread {
    @Override
    public void run(){
        System.out.println("多线程!!");
    }

}

 start方法的作用是:采用 start 启动线程,在JVM中开辟一个新的栈空间,不是直接调用 run ,start 不是马上执行线程,而是使线程进入就绪 状态,线程的正真执行是由 Java 的线程调度机制完成的

第二种方法:实现 Runnable 接口

Thread是Runnable 的一个实现类,所以Thread中的方法,实现Runnable接口也可以用。

public class Test01 {
    public static void main(String[] args) {
        Thread t = new Thread(new ThreadTest());
        //启动线程
        t.start();
    }
}

class ThreadTest implements Runnable {
    @Override
    public void run() {
        System.out.println("多线程!!");
    }

}
三、线程 Thread 中常用的API void  setName(String s)改变线程名字,使之与参数name一样String  getName()获取该线程的名称void setPriority(int newPriority)更改线程的优先级String getPriority()获取当前线程优先级void interrupt()中断线程的睡眠,并不会中断线程的执行static boolean interrupt() 测试当前线程是否中断void setDaemon(boolean on)将该线程标记为守护线程或用户线程boolean isDaemon()测试当前线程是否是守护线程static Thread currentThread()        返回当前线程对象static void sleep(long millis)指定该线程休眠多少毫秒。并没有终止线程static  void yield()暂停正在执行线程对象,执行其他线程 void join()        合并线程

       API测试:

public class Test01 {
    public static void main(String[] args) {
        //线程1
        Thread t = new Thread(new ThreadTest());
        //线程2
        Thread tt = new Thread(new ThreadTest1());

        System.out.println( "线程1的初始名字 = " + t.getName() + ",线程2的初始名字 = " + tt.getName());
        t.setName("t1");
        tt.setName("t2");
        System.out.println( "线程1修改后名字 = " + t.getName() + ",线程2修改后名字 = " + tt.getName());
        //获取主线程
        Thread thread = Thread.currentThread();
        System.out.println("主线程的初始名字 = " + thread.getName());
        //启动线程
        t.start();
        tt.start();
    }
}

//线程1
class ThreadTest implements Runnable {
    @Override
    public void run() {

    }
}

//线程2
class ThreadTest1 implements Runnable{

    @Override
    public void run() {

    }
}

主线程的初始名字是 main,其他分支线程的初始名字:Thread-0,Thread-1.....

public class Test01 {
    public static void main(String[] args) {
        //线程1
        Thread t = new Thread(new ThreadTest());
        //线程2
        Thread tt = new Thread(new ThreadTest1());
        //启动线程
        t.start();
        tt.start();

        try {
            //虽然是用线程1的引用掉用了此方法,但是这并不会让线程1睡眠 3s,sleep写在哪就会让哪个线程休眠
            //所以执行到此处,主线程会休眠 3 s
            t.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我是主线程");

    }
}

//线程1
class ThreadTest implements Runnable {
    @Override
    public void run() {
        System.out.println("我是线程1");
    }
}

//线程2
class ThreadTest1 implements Runnable{

    @Override
    public void run() {

        try {
            //sleep写在这里会让 线程 2 休眠5s
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我是线程2");

    }
}

public class Test01 {
    public static void main(String[] args) {
        //线程1
        Thread t = new Thread(new ThreadTest());
        //线程2
        Thread tt = new Thread(new ThreadTest1());
        //启动线程
        t.start();
        tt.start();


        // interrupt 会中断线程2的休眠。但不会终止线程的执行,所以线程2的for语句还会执行。
        //interrupt依靠的是 sleep方法 的异常处理机制。
        tt.interrupt();
        //查询中断状态:true 为中断成功,false 为中断失败
        boolean interrupted = tt.isInterrupted();
        System.out.println(interrupted);

    }
}

//线程1
class ThreadTest implements Runnable {
    @Override
    public void run() {
    }
}

//线程2
class ThreadTest1 implements Runnable{

    @Override
    public void run() {

        try {
            Thread.sleep(50000000);
        } catch (InterruptedException e) {
            System.err.println("已经终止睡眠");
        }
        for (int i = 0; i < 10; i++) {
            System.out.print(i + "t");
        }
    }
}
四、线程的生命周期

线程的生命周期分为以下五个:

 使线程进入阻塞状态的方法:

sleep、wait、join、yield

五、线程调度

        1、常见的调度模型有哪些?

抢占式调度模型

        哪个线程的优先级较高,抢夺CPU时间片的概率就会高,Java采用的就是这种模型

均分式调度模型

        平均分配CPU时间片,每个时间占用的时间长度一样

        2、线程调度的方法 

更改线程的优先级

注意:优先级高低,只是将线程抢夺CPU时间片的概率高一些,并不一定会优先执行

Thread.MIN_PRIORITY:最小优先级1
Thread.MAX_PRIORITY:最大优先级10
Thread.NORM_PRIORITY:默认优先级5

yield()方法 暂停当前线程的运行,让给其他线程执行。没有终止线程,只是将当前线程从运行状态转换到就绪状态

join() 方法,合并俩个线程

 六、守护线程

         1、Java中的守护线程分为俩类:

一类是守护线程,一类是用户线程

比如:垃圾回收器就是守护线程,主线程是一个用户线程

        2、守护线程的特点

一般是一个死循环,当所有用户线程结束,守护线程自动结束 

public class Test2 {
    public static void main(String[] args) {
        Thread t = new Thread(new ThreadTest2());
        t.setName("守护线程");
        //将线程t设置为守护线程
        t.setDaemon(true);
        t.start();
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程 i-->" + i + "t");
        }
    }
}

class ThreadTest2 implements Runnable {

    @Override
    public void run() {
        int i = 0;
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("守护线程 i-->" + (++i));
        }
    }
}

 虽然守护线程是个死循环,但是当用户线程结束时,守护线程会自动结束

七、线程安全问题 

     ★★★★★   1、什么时候数据在多线程开发的时候会遇到安全问题? 

多线程并发

共享数据

修改数据

共同存在以上三种行为有可能会引发多线程安全问题

2、同步编程模型和异步编程模型的概念

同步编程模型:假设俩个线程 t1和t2,线程 t1 执行的时候必须等待 t2 执行完才能执行,也就是线程排队机制

异步编程模型:t1,t2 自己执行自己的,谁也不管谁,也就是多线程并发

3、怎么解决线程安全问题 

线程排队执行,叫做线程同步机制

4、线程排队机制的语法

               synchronized(){
                   //线程同步代码块
               }
               //synchronized后面这个小括号里面传的这个 “数据” 非常重要,
               //这个数据必须是多线程共享的数据,才能达到多线程排队。

 5、 实现synchronized 的三种方法

第一种方法:

synchronized(){} 

第二种方法:

出现在实例方法上,这个同步的是整个方法,效率第,共享的对象只能是 this 

public static void synchronized(){}

第三种方法:

出现在类上,无论创建多少个对象,类锁只有一个

 6、通过一个例子体现线程安全的重要性以及实现线程同步的用法

设计一个程序,实现简单的银行转账功能。 

目的:假设银行账户余额为10000,俩个人同时去银行对同一个账户进行取钱 *** 作,假设每个人取5000,不使用线程同步时,账户余额是否有可能还剩下 5000 ?

public class AccountTest {
    public static void main(String[] args) {
        //初始化账户
        Account account = new Account(123, 10000);
        //俩个线程代表俩个用户
        Thread t1 = new Thread(new AccountThread(account));
        Thread t2 = new Thread(new AccountThread(account));
        //用户开始取钱
        t1.start();
        t2.start();

    }
}

//账户类
class Account {
    //账号
    int no;
    //余额
    double balance;

    //构造器
    public Account(int no, double balance) {
        this.no = no;
        this.balance = balance;
    }

    //取钱的方法
    public void getMoney(double money) {
        //保证出现实验效果,每个线程休眠1s
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //剩余余额
        this.balance = balance - money;
    }
}

//一个用户就是一个线程
class AccountThread implements Runnable {
    //模拟线程安全问题,保证多个线程都共享一个对象
    private Account account;

    public AccountThread(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        double money = 5000;
        account.getMoney(money);
        System.out.println("账号 = " + account.no + ", 取走 = " + money + ", 余额 = " + account.balance);

    }
}

实验结果:我们可以看到,当俩个用户取钱时,账户余额本应该剩余 0,但是却剩余 5000,这个时候就出现线程安全问题 

解决办法就是使用线程排队机制:每个用户进行取钱 *** 作时,一个执行,另一个等待。 

 

此时,取钱 *** 作就不会出现安全问题了。共享的对象就是 银行账户。也就是 this。

 线程排队的原理:

在java中,每个对象都有一把 "锁",100个对象有100把 "锁"。线程 t1 和 t2 共享一个对象,所以只会有一把 "锁",假设 t1 先执行到 synchronized 处,会霸占这把 "锁" ,拿着这把 "锁" 会先执行取钱 *** 作,而当 t2 执行到此处时,由于这把 "锁" 被 t1 霸占,所以 t2 会等着 t1 先执行完取钱 *** 作, t2 才会执行,这样就不会出问题 

6、关于Java中的变量,哪种变量永远不会有线程安全问题,哪种变量可能存在安全问题? 

 Java中的变量:

实例变量【全局变量】:保存在堆内存中

局部变量:保存在栈内存中

静态变量:保存在方法区中

只有局部变量永远不会有线程安全问题,因为在多线程访问变量时,每个线程都有一个栈内存。而实例变量,静态变量可能会存在线程安全问题

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5703453.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存