多线程相关

1:多线程与多进程的概念

一个进程是一个应用程序(一个进程是一个软件)
线程是一个进程中执行的场景/执行的单元
一个进程可以启动多个线程

对于java程序来说,当dos命令窗口输入:
java HelloWold 回车之后
会先启动 JVM 而JVM本身就是一个主进程
JVM会再启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发

注意:进程A与进程B的内存独立不共享
线程A与线程B堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈

在这里插入图片描述

使用的多线程后 main方法结束,程序也有可能不会结束。
main方法结束只是主线程结束了,主栈空了,其他栈的线程可能还在压栈弹栈。

对于单核CPU只有一个大脑,不能够做到多线程并发,但可以给人做到一种多线程并发的感觉

2:Java中实现多线程的两种方法

1)编写一个类,直接继承java.lang.Thread,重写run方法

start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这句代码执行完任务之后,瞬间就结束了,只是为了开辟新的栈空间,只要新的栈内存空间被开辟出来,start()方法就结束了。
启动成功的线程会自动去调用run方法,并且run方法在分支栈的底部(压栈)main方法在主栈的底部。run方法和main方法是平级的。多一个分支线程

直接调用run()方法,不会启动线程,不会分配新的分支栈(依旧是单线程执行)
在这里插入图片描述
2)编写一个类,实现java.lang.Runnable接口
new Thread(new 类名)

匿名内部类
public class Test03 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                }
            }
        });
        thread.start();
        for (int i=0;i<10;i++){
            System.out.println(i);
        }
    }
}

3:线程的生命周期

在这里插入图片描述
注意:

Thread thread = Thread.currentThread(); //the currently executing thread.
System.out.println(thread.getName());

Returns a reference to the currently executing thread object.

对象名.getName() 

Returns this thread’s name.

4:常见的线程调度模型

抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。java采用的就是抢占式调度模型。

均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式。

java中提供了一些方法是和线程调度有关系
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程优先级
最低优先级1
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)

static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。

t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。Waits for this thread to die.

5:关于多线程开发环境下,数据的安全问题

在这里插入图片描述
什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
条件1:多线程并发。
条件2:有共享数据。指向同一个对象
条件3:共享数据有修改的行为。

满足以上3个条件之后,就会存在线程安全问题。

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。用排队执行解决线程安全问题。
这种机制被称为:线程同步机制。

异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)
异步就是并发。

同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。
同步就是排队。

模拟银行账户取款

6:synchronized ()

在java语言中任何一个对象都有一把锁,这把锁就是标记
100个对象,100把锁。1个对象一把锁

1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后t2占有这把锁之后,进入同步代码块执行程序。
这样就达到了线程排队执行。这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。
object 同样是共享对象
在这里插入图片描述
Object obj2 = new Object();
synchronized (this)
synchronized (obj)
synchronized (“abc”) // "abc"在字符串常量池当中。
synchronized (null) // 报错:空指针。
synchronized (obj2) // 这样编写就不安全了。因为obj2不是共享对象。

synchronized 出现在方法上,表示整个方法体都要同步,可能无故扩大同步的范围,导致程序执行的效率较低。所以这种方式不常用。
如果共享的对象就是this,并且同步的代码块就是整个方法体,建议使用这种方式。

7:java 中的三大变量

实例变量:在堆中
静态变量:在方法区中
局部变量:在栈中

以上三大变量中:局部变量永远不存在线程问题。因为局部变量不共享,在栈中。

如果使用局部变量:建议使用StringBuilder,因为局部变量不存在线程安全问题。

ArrayList是线程不安全的,Vector是线程安全的;HashMap HashSet是非线程安全的。Hashtable是线程安全的。

8:synchronized面试题

1:关于doOther执行需不需要doSome 执行结束 结论是不需要

/**
 * 关于doOther执行需不需要doSome 执行结束 结论是不需要
 * doSome 前面没有 synchronized 关键字 不在线程安全问题的考虑范围之内
 * 因此不需要等待 doSome 的结束
 */
public class Test {
    public static void main(String[] args) {
        My my = new My();
        Myth myth = new Myth(my);
        Myth myth1 = new Myth(my);
        myth.setName("t1");
        myth1.setName("t2");

        myth.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myth1.start();

    }
}
class My{
    public synchronized void doSome(){
        System.out.println("do some begin");
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("do some over");

    }
    public void doOther(){
        System.out.println("do Other begin");
        System.out.println("do Other over");
    }
}
class Myth extends Thread{
    My my;

    public Myth(My my) {
        this.my = my;
    }
    @Override
    public void run(){
        if (Thread.currentThread().getName().equals("t1")){
            my.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            my.doOther();
        }
    }
}

2:对synchronized方法加静态static修饰,称为类锁;类锁不管创建了几个对象,类锁只有一个

package com.Exam;

/**
 * 关于doOther执行需不需要doSome 执行结束 结论是不需要
 * doSome 前面没有 synchronized 关键字 不在线程安全问题的考虑范围之内
 * 因此不需要等待 doSome 的结束
 */
public class Test {
    public static void main(String[] args) {
        My my = new My();
        My my1 = new My();
        Myth myth = new Myth(my);
        Myth myth1 = new Myth(my1);
        myth.setName("t1");
        myth1.setName("t2");

        myth.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myth1.start();

    }
}
class My{
    public synchronized static void doSome(){
        System.out.println("do some begin");
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("do some over");

    }
    public synchronized static void doOther(){
        System.out.println("do Other begin");
        System.out.println("do Other over");
    }
}
class Myth extends Thread{
    My my;

    public Myth(My my) {
        this.my = my;
    }
    @Override
    public void run(){
        if (Thread.currentThread().getName().equals("t1")){
            my.doSome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            my.doOther();
        }
    }
}

9:死锁

示意图:
不会出现错误。程序一直僵持在那里,这种错误最难调试。
在这里插入图片描述
死锁的代码模拟

package com.deadlock;

public class DLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        MyThread1 myThread1 = new MyThread1(o1,o2);
        MyThread2 myThread2 = new MyThread2(o1, o2);
        myThread1.start();
        myThread2.start();

    }
}
class MyThread1 extends  Thread{
    Object o1;
    Object o2;

    public MyThread1(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run(){
        synchronized (o1){
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){

            }
        }
    }

}
class MyThread2 extends Thread{
    Object o1;
    Object o2;

    public MyThread2(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run(){
        synchronized (o2){
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){

            }
        }
    }
}

10:开发中应该如何解决线程安全问题

1:尽量使用局部变量代替实例变量与静态变量
2:如果必须是实例变量,可以考虑创建多个对象,这样实例变量的内存就不共享了
3:不能使用局部变量,对象不能创建多个,那么就只能使用synchronized关键字

11:线程的分类

守护线程(后台线程):最具代表的是垃圾回收线程(守护线程)
一般守护线程是一个死循环,所有用户线程只要结束,守护线程自动结束
系统自动备份,定时器设置为守护线程。
调用setDaemon 将线程设置为守护线程
用户线程

定时器
定时器去的作用:间隔特定的时间,执行特定的程序。
每周进行银行账户的总账造作
每天进行数据的备份操作

在实际开发中每隔多久执行特定的程序,这种需求是常见的
1)可以使用sleep方法,设置睡眠时间,没到这个时间点,执行任务
2)类库中 java.util.Timer
3)高级框架 SpringTask

package com.fangun;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class DingShiqi {
    public static void main(String[] args) {
        //Timer timer = new Timer();

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date parse = simpleDateFormat.parse("2020-10-01 20:40:00");
            Timer timer = new Timer();
            timer.schedule(new Loger(),parse,1000*5);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}
class Loger extends TimerTask{

    @Override
    public void run() {
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = simpleDateFormat.format(date);
        System.out.println(format+"执行了备份任务");
    }
}

FutureTask
实现Callable接口
这种方式可以获取线程的返回值。
之前讲解的那两种方式,run方法返回值都是void
将之前实现线程的需要传入的Runable接口换成Callable
Object wait() notify() 方法(生产者消费者模式)
任何一个javaObject都自带的方法
o.wait() :让正在o对象上活动的线程进入等待状态,无期限的等待下去,知道被唤醒。
会让当前线程(正在o对象上活动的线程)进入等待。
在这里插入图片描述
o.notify() 唤醒正在o对象上等待的线程
o.notifyAll() 唤醒正在o对象上等待的所有线程

在这里插入图片描述

1、使用wait方法和notify方法实现“生产者和消费者模式”

2、什么是“生产者和消费者模式”?
生产线程负责生产,消费线程负责消费。
生产线程和消费线程要达到均衡。
这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。

3、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。

4、wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。

5、wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。

6、notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。

7、模拟这样一个需求:
仓库我们采用List集合。
List集合中假设只能存储1个元素。
1个元素就表示仓库满了。
如果List集合中元素个数是0,就表示仓库空了。
保证List集合中永远都是最多存储1个元素。

必须做到这种效果:生产1个消费1个。
生产者消费者的代码实现:

package com.ThreadSafe;

import java.util.ArrayList;
import java.util.List;

/**
 * 生产需求平衡
 */
public class ShengChanzheXiaoFeizhe {
    public static void main(String[] args) {
        List list = new ArrayList();
        Thread thread = new Thread(new Producer(list));
        Thread thread1 = new Thread(new Consumer(list));
        thread.setName("生产者线程");
        thread1.setName("消费者线程");
        thread.start();
        thread1.start();


    }
}

class Producer implements Runnable{
    private List list;

    public Producer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true){
            synchronized (list){
                if (list.size()>0){
                    try {
                        list.wait(); //释放之前占用的list锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Object o = new Object();
                list.add(o);
                System.out.println(Thread.currentThread().getName()+"...."+o);
                list.notifyAll();
            }
        }

    }
}
class Consumer implements  Runnable{
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true){
            synchronized (list){
                if (list.size()==0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //开始消费
                Object remove = list.remove(0);
                System.out.println(Thread.currentThread().getName()+"..."+remove);
                list.notifyAll();
            }
        }
    }
}

利用生产者消费者实现交替输出

package com.ThreadSafe;

public class Test11 {
    public static void main(String[] args) {
        Number number = new Number(1);
        Thread thread = new Thread(new First(number));
        Thread thread1 = new Thread(new Second(number));
        thread.setName("t1");
        thread1.setName("t2");
        thread.start();
        thread1.start();
    }
}

class Number{
    private Integer integer;

    public Number(Integer integer) {
        this.integer = integer;
    }

    public Integer getInteger() {
        return integer;
    }

    public void setInteger(Integer integer) {
        this.integer = integer;
    }
}

class First implements Runnable{
    private Number number;

    public First(Number number) {
        this.number = number;
    }

    @Override
    public void run() {
        while (true){
            synchronized (number){
                if (number.getInteger()%2==0){//偶数
                    try {
                        number.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(1000*5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+">>>"+number.getInteger());
                number.setInteger(number.getInteger()+1);
                number.notifyAll();
            }
        }
    }
}

class Second implements Runnable{
    private Number number;

    public Second(Number number) {
        this.number = number;
    }

    @Override
    public void run() {
        while (true){
            synchronized (number){
                if (number.getInteger()%2!=0){
                    try {
                        number.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(1000*5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+">>>"+number.getInteger());
                number.setInteger(number.getInteger()+1);
                number.notifyAll();
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值