入门Java第十五天 线程

一、多线程

1.1进程和线程

进程:进程就是操作系统中运行的每一个应用程序。例如:微信,QQ

线程:线程是进程中的每一个任务

        多线程:在一个进程中,可以同时执行多个线程。同时完成多个任务。

        并发:同时发生。多个线程同时完成任务时。

我们编写的应用程序运行时,也是一个进程。在我们进程中也是有线程的。

        第一个现车给称为主线程main方法就是第一个线程。

1.2 CPU轮转时间片

内存是作用是什么?处理程序数据。

CPU是中央处理器。程序想要运行必须使用CPU。

一个CPU同一时间只能处理一个程序运行。

一个程序想要运行必须抢到CPU

1.3 Thread类

Thread线程的父类

(1) 构造方法

public Thread()

public Thread(String name)

        name 是线程的名称

public Thread(Runnable target)

(2) 常用方法

public synchronized void start()

        线程启动方法。 启动与运行现在不在是相同的概念。

        线程启动之后,表示当前这个线程对象可以被运行。

public void run()

        线程运行方法。

1.4 使用Thread类创建自定义的线程类

创建Thread类的子类。重写run的方法。为这个方法编写具体任务代码

Thread子类:

public class Thread01 extends Thread{
    @override
    public void run(){
        System.out.println("abc");
    }
}

main方法:

public static void main(String[] args){
    Thread thread = new Thread01();
    thread.start();//启动
}

1.5 为什么要用多线程

示例代码:

public class Thread01 extends Thread{
    @override
    public void run(){
        for(int i = 1;i <= 1000 ;i++){
            System.out.println(i+":abc");
        }
    }
}
public static void main(String[] args) {
    Thread thread = new Thread01();//创建了一个新的子线程。
    thread.start();//启动子线程。
    //thread.run();
    for(int i = 1;i<=1000;i++) {
        System.out.println(i+":000");
    }
}

输出: 

1:000

1:abc

2:abc

3:abc

2:000

4:abc

3:000

 在main方法中调用了thread.start();就表示在主线程中开辟了一个新的子线程。

主线程与子线程是并列的关系。二个线程之间的执行要通过抢CPU

1.6 使用Runnable接口创建线程任务

线程:线程是在进程中的每一个任务

Runnable接口就表示的任务接口。

@FunctionalInterface
public interfaceRunnable{
    public abstract void run();
}

编写Runnable接口的实现类。任务类

public class Runnbale02 implements Runnable{
    @Override
    public void run() {
        for(int i = 1;i<=1000;i++) {
            System.out.println(i+":000");
        }
    }
}

使用任务类来创建一个线程对象

将任务类交给线程对象进行加载。线程启动时就可以调用任务类的run方法。

public static void main(String[] args) {
    //使用Runnable接口的实现类
    Runnbale02 runnbale02 = new Runnbale02();
    Thread thread02 = new Thread(runnbale02);

    thread02.start();
}

1.7 使用Callable接口编写任务类

在JDK1.5加入的关于线程的新的类。 

解决了Runnable接口的二个问题:

        1 异常没有抛出。调用线程启动时类,不知道在线程执行过程中有没有出现异常。

        2 run方法没有返回值。调用线程结束之后。没有数据返回。

 编写Callable接口下的实现类。任务类

public class Callable03 implements Callable<String> {
    @Override
    public String call() throws Exception {
        for(int i = 1;i<=1000;i++) {
            System.out.println(i+":***");
        }
        return "任务Callable03执行完成!";
    }
}

1.8 使用线程池执行Callable接口的任务

(1) 线程池

池:容器。

线程池是存放大量线程对象的容器。

一个线程对象(Thread t)可不可能加载多个任务(Runnable r,Callable c)?

多个任务以串行方式,以先后顺序的方式进行执行。有多个任务可以同时使用同一个线程对象。

线程池:就是一个存放了大量线程对象的容器。

        当有任务需要执行时,从线程池中拿一个线程对象。任务执行完成之后。将线程对象还给线程池。

(2)线程池Executor接口 

实际开发时使用Executor接口的子接口ExecutorService

Submit 方法称为注册任务方法。

public interface ExcecutorService extends Executor{

        <T>Future<T> submit(Callable<T> task);

}

这是一个创建线程池对象的工具类。

通过这个类的静态方法来获得不同的线程池对象

public class Executors {

返回一个有固定大小的线程池容器对象

public static ExecutorService newFixedThreadPool(int nThreads);

返回只有一个线程对象的线程池对象

public static ExecutorService newSingleThreadExecutor()

返回一个可变化的线程池对象

public static ExecutorService newCachedThreadPool()

}

(3) 返回值Future接口 

用来接收Callable任务执行完成之后的返回值。

public interface Future<V> {

获取返回值对象的方式

V get() throws InterruptedException, ExecutionException;

}

public static void main(String[] args) {
    Callable03 callable03 = new Callable03();

    ExecutorService service = Executors.newFixedThreadPool(5);
    Future<String> future03 = service.submit(callable03);

    try {
        System.out.println(future03.get());
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        service.shutdown();
    }
}

1.9 线程状态

(1) 初始状态

一个刚被创建好的线程对象,就是初始状态的对象。

Thread t = new Thread(new Runnable02());

当前新创建的t线程对象就是初始状态

(2) 可运行状态

一个线程想要运行必须先抢到CPU。启动与运行是不同的概念。

当一个线程对象调用了start()方法启动了,但还没有抢到CPU时,都是一个可运行状态

可运行状态的线程对象只做一件事:抢CPU

(3) 运行状态

一个抢到CPU正在指向的线程对象。称为运行状态。

当一个运行装药的线程对象,CPU时间到期要转换为可运行状态。

(4) 终止状态

一个执行完成run()方法的线程对象,就是终止状态。

1.10 线程中的常用属性和方法

(1) name 属性

用来表示一个线程对象的名称。可以在创建线程对象时指定。也可以通过方法指定。

public class NameTest{
    public static void main(String[] args){
        Thread t = new Thread(new Runnable02(),"线程1");
        System.out.println(t.getName());
    }
}

(2) currentThread()

Thread类的静态方法。动态获取当前执行的线程对象。

        public static native Thread currentThread()

public class Runnable02 implements Runnable{
    @Override
    public void run(){
        for(int i = 1;i <= 10;i++){
            String name = Thread.currentThread().getName();
            System.out.println(name+"-"+i+":000");
        }
    }


}

1.11 阻塞状态

线程对象运行过程中,出现以下情况:

        1 等待IO。做标准输入时,用户需要冲控制台输入数据时。

        2 sleep方法。当前线程休眠。

当线程遇到以上两种情况时,会进入阻塞状态。并会释放CPU

 

二、本地线程对象ThreadLocal<T>类

2.1 本地线程对象是什么

1 本地线程对象其实是一个容器。而且是一个只能保存一个数据的容器对象。

2 这个容器在每一个不同的线程中,都有一份自己的实例。

3 每一个线程对象都使用自己的ThreadLocal类的实例。

4 当出现多个线程时,每一个线程中都有一个自己使用的ThreadLocal对象。

2.2 常用方法

public static void main(String[] args){
    ThreadLocal<String> local = new ThreadLocal<>();
    local.set("zhangsan");
    String s = local.get();
    System.out.println(s);
}

三、案例:取款与存款案例

3.1 编写两个任务

存款任务

取款任务

3.2 专家原则

专家原则:事情哪个对象最清楚(处理最直接),哪个对象负责

3.3 编写账号的类

public class Acount{
    private Double money;
    public Acount(Double money){
        this.money = money;
    }
    public Double getMoney() {
        return money;
    }
    public void setMoney(Double money) {
        this.money = money;
    }
}

3.4 编写DAO

public class AcountDAO{
    public void update(Acount acount,Double money){
        acount.setMonet(money);
    }
}

3.5 编写Service

public class AcountService{
    private AcountDAO acountDAO = new AcountDAO();
    
    public void deposit(Acount acount,Double money){
        double temp = acount.getMoney();
        temp = temp + money;
        acountDAO.update(acount,temp);
    }

    public void draw(Acount acount,Double money)throws Exception{
        double temp = acount.getMoney();
        if(temp > money){
            temp = temp - money;
            acountDAO.update(acount.temp);
        }else{
            throw new Exception("余额不足!");
        }
    }
}

3.6 编写任务类

任务类一定是与Service层对接。

存款任务:DepositRunnable

public class DepositRunnable implements Runnable{
    private AcountService service = new AcountService();
    private Acount acount;
    private Double money;
    public DepositRunnable(Acount acount,Double money){
        this.acount = acount;
        this.money = money;
    }
    @Override
    public void run(){
        service.deposit(acount money);
        System.out.println("存款成功!"+acount.getMoney());
    }
}

取款任务:Draw

public class DrawRunnable implements Runnable{
    private AcountService service = new AcountService();
    private Acount acount;
    private Double money;
    public DrawRunnable(Acount acount, Double money) {
        this.acount = acount;
        this.money = money;
    }
    @Override
    public void run() {
        try {
            service.draw(acount,money);
            System.out.println("取款成功!"+acount.getMoney());
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage()+"取款失败!"+acount.getMoney());

        }
    }
}

3.7 编写测试类

public static void main(String[] args) {
    Acount acount = new Acount(10000.0);

    DepositRunnable depositRunnable01 = new DepositRunnable(acount,10000.0);
    DepositRunnable depositRunnable02 = new DepositRunnable(acount,2000.0);

    DrawRunnable drawRunnable01 = new DrawRunnable(acount,5000.0);

    Thread t1 = new Thread(depositRunnable01,"自己");
    Thread t2 = new Thread(depositRunnable02,"父母");
    Thread t3 = new Thread(drawRunnable01,"女朋友");

    t1.start();
    t2.start();
    t3.start();
 }

问题!会出现数据不一致的情况。

账户的余额会经常变化。不是一个正确的固定的数值

四、线程不安全

线程不安全是指对各线程在操作数据时,数据不正确的情况。

线程不安全的几个条件:

1  多个线程        -         多线程

2  同时操作        -         并发

3  同一个数据        -         临界资源

4  破坏了不可分割的操作时        -         原子性

当以上的情况发生时,就有可能造成数据的不一致。这种情况就称线程不安全。

当多线程并发操作临界资源时,破坏了其原子性操作时,就有可能造成数据不一致。

解决方案:线程同步

五、线程同步

线程同步:多个线程以串行的方式执行。这种方式称为线程同步。

        手拉手一起走,是异步

串行的方式执行:是针对对临界资源的操作部分以串行的方式操作。

5.1 同步锁 synchronized

我们使用同步锁,来保证临界资源的原子性不被分割。

在执行原子性操作之前,先获取同步锁对象。之后再执行原子性代码。只有原子性代码执行完后曾之后。我们才会释放同步锁对象

特殊注意:只有使用同一个同步锁的多个线程,才会以串行的方式执行。

必须要保证,多个线程使用的是同一个锁对象。才能叫多个线程的同步。

一般情况下,挑选临界资源作为锁对象更合适。

5.2 同步锁的使用方式一

synchronized(锁对象){

        原子性操作代码;

}

加入同步锁的存款任务 

public class DepositRunnable implements Runnable{
    private AcountService service = new AcountService();
    private Acount acount;
    private Double money;
    public DepositRunnable(Acount acount, Double money) {
        this.acount = acount;
        this.money = money;
    }
    @Override
    public void run() {
        synchronized (acount) {
            System.out.println(Thread.currentThread().getName() + "-存款任务开始!");
            service.deposit(acount, money);
            System.out.println(Thread.currentThread().getName() + "-存款成功!" + acount.getMoney());
            System.out.println(Thread.currentThread().getName() + "-存款任务结束!");
        }
    }
}

 加入同步锁的取款任务

public class DrawRunnable implements Runnable {
    private AcountService service = new AcountService();
    private Acount acount;
    private Double money;

    public DrawRunnable(Acount acount, Double money) {
        this.acount = acount;
        this.money = money;
    }

    @Override
    public void run() {
        synchronized (acount) {
            System.out.println(Thread.currentThread().getName() + "-取款开始!");
            try {
                service.draw(acount, money);
                System.out.println(Thread.currentThread().getName() + "-取款成功!" + acount.getMoney());
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println(Thread.currentThread().getName() + "-" + e.getMessage() + "取款失败!" + acount.getMoney());
            }
            System.out.println(Thread.currentThread().getName() + "-取款结束!");
        }
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Acount acount = new Acount(10000.0);//临界资源

        DepositRunnable depositRunnable01 = new DepositRunnable(acount,10000.0);
        DepositRunnable depositRunnable02 = new DepositRunnable(acount,2000.0);

        DrawRunnable drawRunnable01 = new DrawRunnable(acount,5000.0);
        DrawRunnable drawRunnable02 = new DrawRunnable(acount,5000.0);

        Thread t1 = new Thread(depositRunnable01,"自己");
        Thread t2 = new Thread(depositRunnable02,"父母");
        Thread t3 = new Thread(drawRunnable01,"女儿");
        Thread t4 = new Thread(drawRunnable02,"老婆");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
     }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

修贤323

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值