JavaSE---多线程

2022年01月14日 阅读数:5
这篇文章主要向大家介绍JavaSE---多线程,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

目录java

1、进程与线程的区别编程

2、实现多线程 windows

3、线程 Thread 中经常使用的API安全

4、线程的生命周期多线程

5、线程调度并发

 6、守护线程异步

7、线程安全问题 ide


1、进程与线程的区别

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

2、实现多线程 

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("多线程!!");
    }

}

3、线程 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");
        }
    }
}

4、线程的生命周期

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

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

sleep、wait、join、yield

5、线程调度

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

抢占式调度模型

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

均分式调度模型

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

        二、线程调度的方法 

更改线程的优先级

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

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

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

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

 6、守护线程

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

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

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

        二、守护线程的特色

通常是一个死循环,当全部用户线程结束,守护线程自动结束 

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));
        }
    }
}

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

7、线程安全问题 

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

多线程并发

共享数据

修改数据

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

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

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

异步编程模型:t1,t2 本身执行本身的,谁也无论谁,也就是多线程并发

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

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

四、线程排队机制的语法

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

 五、 实现synchronized 的三种方法

第一种方法:

synchronized(){} 

第二种方法:

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

public static void synchronized(){}

第三种方法:

出如今类上,不管建立多少个对象,类锁只有一个

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

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

目的:假设银行帐户余额为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 才会执行,这样就不会出问题 

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

 Java中的变量:

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

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

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

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