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中的变量:

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

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

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

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

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>