学习Java的多线程以及同步死锁

Java多线程以及同步死锁

1. 多线程

学习前我们要清楚什么是线程和进程,两者有什么关系?线程是如何创建的,它的作用是什么,它的常用方法是什么?线程的同步和死锁可以解决什么问题。

2. 了解基本的概念:

线程(Thread):线程是进程中最小的调度的单元(单位),cpu控制的最小的执行单元。轻量级的进程。任何一个程序都至少有一个线程在用,多个线程共享内存。多线程切换消耗的资源少。
进程(Processor):活动的程序,进程;已经启动,进驻到内存中,正在使用的程序,:进程;每个进程拥有独立的内存空间。
简单举个例子:进程相当于某工厂的某生产车间,线程就是该车间里的流水线。线程和进程的关系,这里我们引入了一个概念同步:
1.一个进程可以有多个线程,但至少有一个线程;而一个线程只能在一个进程的地址空间内活动。
2、资源分配给进程,同一个进程的所有线程共享该进程所有资源。
3、CPU分配给线程,即真正在CPU处理器运行的是线程。
4、线程在执行过程中需要协作同步,不同进程的线程间要利用消息通信的办法实现同步。

3.学习线程的创建办法:

  1. 无返回值创建线程的两种方式:第一种可以去直接继承Thread类,重写run方法,创建线程对象,启动线程(start())对象;第二种可以去实现Runnable接口,重写run方法。具体通过代码:
//继承Thread类
public class MyThread extends Thread{

    public MyThread() {
    }
    public MyThread(String name) {
        super(name); //调用父类构造方法
    }

    @Override
    public void run(){
        for (int i = 0; i <=10; i++) {
            System.out.println(Thread.currentThread().getName()+"在玩游戏"+i);
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
//        myThread.run(); 执行方法,不是 启动线程
        myThread.start();//启动线程

        MyThread1 myThread1 = new MyThread1();
        myThread1.start();

        for (int i = 0; i <=10; i++) {
            System.out.println("主线程执行"+i);
        }
    }
}

//实现Runnable接口,重写run方法
public class MyRun1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <=10; i++) {
            System.out.println(Thread.currentThread().getName()+"正在思考");
        }
    }

    public static void main(String[] args) {
        MyRun1 myRun1 = new MyRun1();
        Thread t1 = new Thread(myRun1);
        Thread t2 = new Thread(myRun1);
        t1.start();
        t2.start();
    }
}

通过以上学习,你发现多个线程在执行时,它是没有先后执行顺序的。它由新建进入就绪状态,在运行时它们会抢占Cpu时间片,谁抢到谁先运行,直到进程结束。
学习过程中,大家发现没有run()方法和start()两个方法,这两个方法在线程中的区别如下:

  • t.run() 不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)
  • t.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
    启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的哈。
  1. 有返回值创建线程时,我们实现Callable接口可以获取线程的返回值,实现Callable接口,要和FutrueTask类连用去获取线程返回值。具体方法如下:
public class MyCall implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"小狗正在吃瓜");
        return "吐出了瓜子";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCall call = new MyCall();
//        FutureTask类  获得线程返回值
        FutureTask<String> futureTask = new FutureTask<>(call);
        Thread t1 = new Thread(futureTask);
        t1.start();
        String s = futureTask.get();
        System.out.println("吃完西瓜后:小狗"+s);
    }
}
  1. 学习线程的常用API,这里我们引用一下。

在这里插入图片描述

  1. 了解线程的状态,进行基本的线程调度操作。
  • 线程的五种状态,明白在线程执行时对应的操作是:
    在这里插入图片描述
    体系图看这里:
    在这里插入图片描述
  • 对线程进行调度的基本操作:
    1.调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
public class MyThread extends Thread{

    public MyThread() {
    }
    public MyThread(String name) {
        super(name); //调用父类构造方法
    }

    @Override
    public void run(){
        for (int i = 0; i <=10; i++) {
            System.out.println(Thread.currentThread().getName()+"在玩游戏"+i);
        }
    }

    public static void main(String[] args) {
//    通过构造犯法
        MyThread myThread = new MyThread("西门吹雪");
        myThread.setPriority(Thread.MAX_PRIORITY);//优先级最大为1
        myThread.start();
        System.out.println("优先级1:"+myThread.getPriority());
//      主线程优先级 默认为5
        System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority());

    }
}

2.线程阻塞
sleep(),线程休眠,会让当前线程处于阻塞状态,指定时间过后,线程就绪状态。yield()暂停当前正在执行的线程对象,并执行其他线程,yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。interrupt:中断线程,仅仅发送了一个中断的信号,当碰到wait(),sleep方法时,清除中断标记,抛出异常。setDaemon:设置线程为后台(守护)线程。

  1. 了解多线程数据安全问题:
    以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
    最重要的是: 你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。以下可能会产生数据安全问题:
  • 条件1:多线程并发。
  • 条件2:有共享数据。
  • 条件3:共享数据有修改的行为。

我们举例子来验证,例如:我和其余两位同时取一个账户的钱,我取钱后数据还没返回给服务器,两位同学依次再取,这个时候显示的余额为后面同学取钱的余额。显然这样不对,我们如何解决?线程排队执行?用线程同步,使用Synchronized同步实现?大家可以理解一下锁:
Lock是显示锁,需要自己手动开启和关闭,synchronized是隐式锁,出了作用于自动释放,无需自己释放。
Lock只能锁代码块,synchronized可以锁代码块和方法,使用Lock锁,JVM将花费更少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

//建立一个账户类,写一个取钱方法
public class Account {
    private String no;//账号
    private double money;//金额

    public Account() {
    }

    public Account(String no, double money) {
        this.no = no;
        this.money = money;
    }

    private Lock lock = new ReentrantLock(); //创建锁对象

    public void getMoney(double m){

        synchronized (this){ //利用同步,解决多线程安全问题
            double before = this.money;
            double after = before - m;
            lock.lock();  //上锁
            try {
                Thread.sleep(300); //线程休眠
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            } finally {
                lock.unlock();//关锁
            }
            this.money = after;
            System.out.println(Thread.currentThread().getName()+"取钱:"+ m + "余额:" + this.money);
        }
    }
}

写Thread继承类,重写run()方法,写测试类进行测试。

public class AccountThread extends Thread {
    private Account account;

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

    @Override
    public void run() {
        account.getMoney(500);
    }

    public static void main(String[] args) throws InterruptedException {
        Account account = new Account("123456", 3000);
        AccountThread t1 = new AccountThread(account);
        AccountThread t2 = new AccountThread(account);
        AccountThread t3 = new AccountThread(account);
        t1.start();
        t2.start();
        t3.start();
    }
}

结果:
请添加图片描述

  1. 死锁:当多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,从而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况。最简单的,让某一个同步块同时拥有“两个以上的对象的锁”的时候,就可能会发生死锁问题,这个时候就引入一个经典案列:哲学家就餐问题。解决该问题上锁,要求尽量不要让 一个同步代码块 同时拥有“两个以上的对象的锁”,实例如下:
//定义生产者消费者
public class Producer extends Thread {
    private Stock stock;

    public Producer(Stock stock) {
        this.stock = stock;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            stock.push();
        }
    }
}

public class Customer extends Thread{
    private Stock stock;

    public Customer(Stock stock) {
        this.stock = stock;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            stock.pop();
        }
    }
}
//解决生产者消费者问题,方法1上Stock锁。
public class Stock {
    private int count = 0;//库存
//    生产
    public synchronized void push(){
        if(count<10){
            count++;
            this.notifyAll();
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"库存不足,开始生产棒棒糖"+count);
        }else{
            System.out.println(Thread.currentThread().getName()+"库存已足,停止生产");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
//    消费
    public synchronized void pop(){
        if(count>0){
            count--;
            this.notifyAll();
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"库存足够,消费一个棒棒糖"+count);
        }else{
            System.out.println(Thread.currentThread().getName()+"库存不足,继续生产");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Stock stock = new Stock();
        Producer producer1 = new Producer(stock);
        Producer producer2 = new Producer(stock);
        Customer customer1 = new Customer(stock);
        Customer customer2 = new Customer(stock);
        Customer customer3 = new Customer(stock);
        producer1.start();
        producer2.start();
        customer1.start();
        customer2.start();
        customer3.start();

    }
}

//方法2:写一个阻塞队列,确定生产消费长度
//实现Runnable接口,用put()和take()方法。
public class ArrayBq {
    public static void main(String[] args) {
//        阻塞队列:
        BlockingQueue<Integer> queue =  new ArrayBlockingQueue<>(10);
        Thread prouder = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    try {
                        queue.put(i);
                        System.out.println("生产牛肉:"+i);
//                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });


        Thread customer = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    try {
                      int values =  queue.take();
                        System.out.println("消费牛肉:" + values);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        prouder.start();
        customer.start();
    }
}

学到这里本篇就结束了,感谢大家阅览!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值