java-多线程

1)多线程的介绍
1.1、进程和线程的区别

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

1.2、进程和线程的关系
进程可以比作是现实生活当中的中公司
线程可以比作是公司当中的某个员工
注意:
  一、进程A和进程B的内存独立不共享
  二、在Java语言中,线程A和线程B的堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。
  三、当使用多线程机制后,main方法结束只是主线程结束,其它的栈(线程)可能还在运行。

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

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

1.4、线程对象的生命周期
新建状态:new出线程对象
就绪状态:对象调用start()方法时进入就绪状态
运行状态:线程对象的run()方法开始执行 或者 继续执行时进入运行
阻塞状态:遇到阻塞事件进入阻塞
死亡状态:线程对象的run()方法执行完毕后进入死亡
在这里插入图片描述
2)多线程的实现
第一种方式:编写一个类,直接继承java.lang.Thread,重写run()方法

   public static void main(String[] args){
        //创建线程对象
        MyThread t=new MyThread();
        //t.run();  //不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
        t.start();//启动线程,自动调run()方法
    }
}
//定义线程类
class MyThread extends Thread {
    @Override
    //这段代码运行在分支线程中(分支栈)
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("分支线程---->"+i);
        }
    }
}

第二种方式:编写一个类,实现java.lang.Runnable接口,实现run()方法。也可以采用匿名内部类方式
这种方式比较常用,因为一个类实现了接口,它还可以去继承其他类,更灵活

    public static void main(String[] args){
        //创建一个可运行对象
        MyRunnable r=new MyRunnable();
        //将可运行对象封装成一个线程对象
        Thread t=new Thread(r);

        //Thread t=new Thread(new MyRunnable22()); //合并代码

        //启动线程
        t.start();
    }
}


 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("分支线程---->"+i);
        }
    }
}

第三种方式:编写一个类,实现java.util.concurrent.Callable接口,实现call()方法。也可以采用匿名内部类方式
优点:可以获取线程的执行结果
缺点:在获取某个线程执行结果时,当前线程受阻塞,效率较低。

    public static void main(String[] args) {
        //创建一个Callable对象
        MyCallable c=new MyCallable();
        //将Callable对象封装成“未来任务类”对象。
        FutureTask task=new FutureTask(c);
        //创建一个线程对像
        Thread t=new Thread(task);
        t.start();
        try {
            //get()方法可以获得t线程的返回结果
            //但该方法的执行会导致当前线程的阻塞
            Object o=task.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        //要等到get方法结束,才执行下面语句
        System.out.println("当前线程(主线程)");
    }
}

class MyCallable implements Callable{
    @Override
    // call()方法就相当于run方法。只不过这个有返回值
    public Object call() throws Exception {
        System.out.println("Call begin");
        Thread.sleep(1000*10);
        System.out.println("Call over");
        int a=10,b=20;
        return a+b;  //自动装箱
    }
}

3)多线程的常用方法

//1、获取当前线程对象
    Thread t = Thread.currentThread();

//2、获取线程对象的名字
    String name = 线程对象.getName();

//3、修改线程对象的名字
    线程对象.setName("线程名字");  //默认名字为Thread-i (i表示数字)
   
// 4、让当前对象进入“阻塞状态”,放弃占有CPU时间
	Thread.sleep(1000);  //Thread的静态方法,参数是毫秒
	
注意:当用引用.sleep(1000)时,它会自动转成Thread.sleep(1000);
让当前线程进入休眠,是当前对象

//5、终断t线程的睡眠 (依靠Java的异常处理机制)
     t.interrupt();

//6、让位。当前线程暂停,回到就绪状态,让给其他线程
	 Thread.yield();  //静态方法
	 
注意:在回到就绪状态后,有可能再次抢到CPU时间片??

//7、线程合并
	 t.join(); //t线程合并到当前线程中,当前线程受阻塞,直到t线程执行结束

//8、守护线程
	 t.setDaemon(true); //将t线程设置为守护线程,当用户线程结束时,该线程也结束
	 
守护线程的特点:
    一般守护线程是一个死循环,所有的用户线程结束时,守护线程自动结束。
注意:主线程main方法是一个用户线程

//9、设置线程的优先级
	 t.setPriority(1); //设置t线程的优先级为1
	 
注意:优先级范围【110】,默认为5。
      优先级越高,获取CPU时间片的概率会高一些
      
//10、获取线程的优先级
	 t.getPriority();	 

4)定时器
4.1、定时器作用:间隔特定的时间,执行特定的程序
eg:每天进行数据的备份操作

4.2、实现方法:
第一种:使用sleep方法睡眠,设置睡眠时间,每到这个时间就执行任务。
这种方式是最原始的定时器。(不推荐)

第二种:Java类库中已经写好了一个定时器:java.util.Timer,直接调用就可以。(开发中也少用)

    public static void main(String[] args) throws Exception {
        //Timer t=new Timer(true);  //守护线程
        //创建定时器对象
        Timer t = new Timer();

        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstDate= sdf.parse("2021-03-30 18:20:00");
        //void schedule(定时任务, 第一次执行时间, 每次多久执行一次)
        //指定定时任务,每隔10秒执行一次
        t.schedule(new MyTask(),firstDate,1000*10);
    }
}

class MyTask extends TimerTask{
    @Override
    public void run() {
        //定时执行的任务
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String str=sdf.format(new Date());
        System.out.println(str+"完成一次备份");
    }
}

第三种方式:使用spring框架中提供的SpringTask框架,来完成定时器

5)同步线程实现线程安全
5.1、什么时候数据在多线程并发的环境下会存在安全问题
满足以下三个条件:
  1、多线程并发
  2、有共享数据
  3、共享数据有修改的行为

5.2、Java三大变量哪会存在安全问题

  • 实例变量:在堆中
  • 静态变量:在方法区
  • 局部变量:在栈中
    注意:
      1、局部变量在栈中,栈中数据不共享,因此不存在安全问题
      2、jvm只有一个堆内存和一个方法区。堆和方法区多是多线程共享的,所以可能会存在线程安全问题。

5.3、怎么解决线程安全问题
第一种方案:尽量使用局部变量代替实例变量静态变量

第二种方案:如果一定要实例变量,可以考虑创建多个对象,那么实例变量的内存就不共享了。(一个线程对应一个对象,对象不共享,就没有数据安全问题)

第三种方案:当不能使用局部变量和创建多个对象时,只能使用synchronized了,线程同步机制

5.4、实现线程同步机制(synchronized)有三种写法
第一种:同步代码块

	synchronized(线程共享对象){
		同步代码块;
	}

第二种:在实例方法上使用synchroized
缺点:整个方法体都需要同步,可能会扩大同步的范围,导致程序的执行效率降低

	public synchronized void test(){
		这种写法表示的共享对象一定是this(当前对象)
		而且同步代码块是整个方法体
	}

第三种:在静态方法上使用synchronized

	public synchronized static void test(){
		静态方法同一个类中的所有对象都共享	
		一个类只有一把类锁。
	}

注意:在Java语言中,任何一个对象都有“一把锁”,锁其实是标记而已。(一个对象一把锁)
执行原理:
1、假设线程t1和t2并发,假设t1先遇到了synchronized关键字,这时自动找“后面共享对象”的对象锁,找到后,占有这把锁,然后执行同步代码块的程序,直到同步代码块执行完毕后,这把锁才会释放。

2、当t1占有这把锁后,t2也遇到了synchronized关键字,也会去占有“后面共享对象”的这把锁,但这把锁被t1占有,t2只能在同步代码块外面等待t1结束,当t1执行完同步代码块后,t1会归还这把锁,此时,t2终于等到这把锁,然后t2占有这把锁后,进入同步代码块执行程序。

6)死锁的实现

    public static void main(String[] args) throws Exception {
        Object o1=new Object();
        Object o2=new Object();

        MyThread8 t1=new MyThread8(o1,o2);
        MyThread88 t2=new MyThread88(o1,o2);

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

class MyThread8 extends Thread{
    Object o1;
    Object o2;

    public MyThread8(Object o1,Object o2){
        this.o1=o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized(o1){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(o2){

            }
        }
    }
}

class MyThread88 extends Thread{
    Object o1;
    Object o2;

    public MyThread88(Object o1,Object o2){
        this.o1=o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized(o2){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(o1){

            }
        }
    }
}

实现原理:
t1先占有o1对象的锁,而t2先占有o2对象的锁,
然后t1、t2线程遇到第二个synchronized关键字时,
由于t1和t2各自占有了o1、o2的对象锁,
因此t1和t2线程只能等待着,因此被称为死锁。

7)关于Object类中的wait和notify方法(生产者和消费者模式)
7.1、什么是“生产者和消费者”模式
生产线程负责生产,消费线程负责消费
生产线程和消费线程要达到均衡
实现这种模式需要使用wait和notify方法

7.2、wait和notify方法不是线程对象的方法,是普通Java对象都有的方法

7.3、wait和notify方法建立在线程同步的基础上。因为多线程要同时操作一个仓库,才能实现生产者和消费者模式

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

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

7.6、notifyAll方法作用:唤醒o对象上处于等待的所有线程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值