javaSE多线程的使用

本文针对多线程,通过为什么使用多线程、多线程优缺点、多线程的实现方式、同步机制来说明多线程的使用;

1 为什么的使用多线程?多线程的作用

就是为了提高程序的运行速度,将主线程主要用于页面交互,子线程用于数据的准备。作者将会在新的博客中使用javaFX案例进行说明;
1、优点:
(1)、使用线程可以把程序中占据时间长的任务放到后台去处理,如图片、视频的下载
(2)、发挥多核处理器的优势,并发执行让系统运行的更快、更流畅,用户体验更好
2、缺点:
(1)、大量的线程降低代码的可读性,
(2)、更多的线程需要更多的内存空间
(3)、当多个线程对同一个资源出现争夺的时候要注意线程安全的问题。

2 多线程的生命周期

按照运行周期,分为新建状态、就绪状态、运行状态、死亡状态,在运行中,可由运行状态转变为阻塞状态,如下图所示
线程运行状态图

3 多线程的内存运行方式

下面准备了一个简单案例:当main方法执行时,主线程开始进行压栈操作,创建线程t对象,当t执行方法startup方法时,开辟分支栈内存空间,此后,分支栈和主栈为两个栈,互相分离,各自运行,这种运行方式为并发运行;

public class MyThreadTest {
    public static void main(String[] args) {
        System.out.println("主线程启动运行");
        Thread t = new Thread(new myThread01());
        t.start();
    }
}
class myThread01 implements Runnable{
    @Override
    public void run() {
        System.out.println("分支线程启动运行");
    }
}

多线程JVM内存图

4 实现多线程

之前展示了多线程的内存运行,下面介绍多线程的实现方式。实现多线程的方式常用的主要分为两种方式,一种为继承thread,一种为实现Runnable。下面有两个案例。
读者可以使用以后代买进行自测,注意一个小点,如果案例中main方法中的start()方法改为run方法,程序会变成单线程操作,原因是没有使用start()方法开辟分支线程的内存空间;
1、案例一,继承Thread类

public class MyThreadTest02 {
    public static void main(String[] args) {
        System.out.println("主线程启动运行");
        MyThread02 myThread02 = new MyThread02();
        myThread02.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程---》"+i);
        }
    }
}
class MyThread02 extends Thread{
    @Override
    public void run() {
        System.out.println("分支线程启动运行");
        for (int i = 0; i < 100; i++) {
            System.out.println("分支线程---》"+i);
        }
    }
}

2、案例二,实现Runnable接口,重写run方法;

public class MyThreadTest03 {
    public static void main(String[] args) {
        System.out.println("主线程启动运行");
        //新建线程对象,使用构造方法传入Runnable方法
        Thread thread = new Thread(new MyRunnable03());
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程---》"+i);
        }
    }
}

class MyRunnable03 implements Runnable{
    @Override
    public void run() {
        System.out.println("分支线程启动运行");
        for (int i = 0; i < 100; i++) {
            System.out.println("分支线程---》"+i);
        }
    }
}

5 多线程Api,获取线程名字、获取当前线程对象、sleep方法、终止线程睡眠、终止线程执行、

1、获取当前线程名字,getName()
对于线程名字,当用户不进行设置时,系统会默认设置,格式为Thread-x,主线程默认为main

MyThread02 myThread01 = new MyThread02();
MyThread02 myThread02 = new MyThread02();
MyThread02 myThread03 = new MyThread02();
//输出Thread-0
System.out.println(myThread01.getName());
//输出Thread-1
System.out.println(myThread02.getName());
//输出Thread-2
System.out.println(myThread03.getName());
设置线程名字setName();
myThread01.setName("t1");
myThread02.setName("t2");
myThread03.setName("t3");
//输出t1
System.out.println(myThread01.getName());
//输出t2
System.out.println(myThread02.getName());
//输出t3
System.out.println(myThread03.getName());
2、获取线程对象currentThread()Thread thread = Thread.currentThread();

3、sleep方法;
在线程代码中调用静态方法sleep(),就可以是该线程进入休眠;

public class MyThreadTest03 {
    public static void main(String[] args) {
        System.out.println("主线程启动运行");
        //新建线程对象,使用构造方法传入Runnable方法
        Thread thread = new Thread(new MyRunnable03());
        thread.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程---》"+i);
        }
    }
}
class MyRunnable03 implements Runnable{
    @Override
    public void run() {
        System.out.println("分支线程启动运行");
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 5; i++) {
            System.out.println("分支线程---》"+i);
        }
    }
}

4、唤醒线程,使用类实例变量调用interrupt(),方法

public class MyThreadTest03 {
    public static void main(String[] args) {
        System.out.println("主线程启动运行");
        //新建线程对象,使用构造方法传入Runnable方法
        Thread thread = new Thread(new MyRunnable03());
        thread.start();
        for (int i = 0; i < 1000; i++) {
            if (i==1){
                thread.interrupt();
            }
            System.out.println("主线程---》"+i);
        }
    }
}

class MyRunnable03 implements Runnable{
    @Override
    public void run() {
        System.out.println("分支线程启动运行");
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程---》"+i);
        }
    }
}

5、合理的终止线程
可以调用静态方法stop,该方法会使得线程中的数据未保存就退出,导致数据丢失。如何合理的终止线程呢?在线程对象中添加实例变量run,在运行中对其进行赋值;

public class MyThreadTest04 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t = new Thread(myRunnable);
        t.start();
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myRunnable.run=false;
    }
}
class MyRunnable implements Runnable{
    Boolean run = true;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (run) {
                System.out.println("分支线程执行--->"+i);
                try {
                    Thread.sleep(1000*1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                /**
                 * 此处用于保存程序运行中的数据
                 */
                return;
            }
        }
    }
}

6 线程安全

以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:*****)
线程安全,什么是线程安全的线程安全问题线程池如何实现;银行账户取款,不安全线程取款(添加yeild,模仿网络延迟),线程安全取款。

如何实现线程安全,使用synchronicized进行修饰。synchronized如何使用。第一种:同步代码块灵活
ynchronized(线程共享对象){
同步代码块;
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized
表示找类锁。
类锁永远只有1把。
就算创建了100个对象,那类锁也只有一把。
线程安全。
接下来将使用银行卡转账的例子来说明案例:
案例一:线程不安全的转账;
代码部分:
银行账户对象:

public class Account {
    //银行卡账户;
    private String actNo;
    //银行余额;
    private Integer balance;
    public void withdraw(Integer money){
        this.balance = balance-money;
    }
    public String getActNo() {
        return actNo;
    }
    public void setActNo(String actNo) {
        this.actNo = actNo;
    }
    public Integer getBalance() {
        return balance;
    }
    public void setBalance(Integer balance) {
        this.balance = balance;
    }
    public Account() {
    }
    public Account(String actNo, Integer balance) {
        this.actNo = actNo;
        this.balance = balance;
    }
}

账户线程对象:

public class AccountRunnable implements Runnable {
    Account account;
    @Override
    public void run() {
        try {
            //模仿网络延迟
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Integer money = 5000;
        account.withdraw(money);
        System.out.println("取款"+money+"成功,"+"余额为:"+account.getBalance());
    }
    public AccountRunnable(Account account) {
        this.account = account;
    }

    public AccountRunnable() {
    }
}

测试程序:

public class AccountThreadTest {
    public static void main(String[] args) {
        //新建账户对象
        Account account = new Account("act-001", 10000);
        AccountRunnable accountRunnable = new AccountRunnable(account);
        //分三个线程进行模拟
        Thread t1 = new Thread(accountRunnable);
        t1.start();
        Thread t2 = new Thread(accountRunnable);
        t2.start();
        Thread t3 = new Thread(accountRunnable);
        t3.start();
    }
}

以上程序运行多次以后出现

取款5000成功,余额为:5000
取款5000成功,余额为:5000
取款5000成功,余额为:5000

即出现错误;
使用synchronized关键字对账户方法中的withdraw方法中的代码块进行修饰:代码如下:

synchronized (this){
    this.balance = balance-money;
}

使用synchronized关键字对账户方法中的withdraw方法进行修饰:

    public synchronized void withdraw(Integer money){
        this.balance = balance-money;
    }

总结:

本文主要从为什么要使用多线程、多线程在JVM的内存方式、如何使用多线程,以及多线程带来的线程安全问题如何解决进行了描述。另外,在实际开发中,可以使用以下的策略来解决线程同步的问题:
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值