Java多线程(线程,线程安全,生产者与消费者模型,定时器)



前言

学习线程之前我们先了解一下其中的基本概念:
(1)什么是串行和并行?

串行是指多个任务时,各个任务按顺序执行,完成一个之后才能进行下一个 ;
并行指的是多个任务可以同时执行。异步是多个任务并行的前提条件。

(2)什么是并行和并发?

并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。

(3)什么是进程和线程?

进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间)
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

一、多线程

线程分为守护线程和用户线程

1.线程的特点

  • 线程是轻量级的进程
  • 线程没有独立的地址空间(内存空间)
  • 线程是由进程创建的(寄生在进程)
  • 一个进程可以拥有多个线程–>这就是我们常说的多线程编程

在java语言中,两个线程中的堆内存和方法区共享,但栈内存独立,一个线程一个栈,栈与栈互不干扰。因此局部变量永远不会存在线程安全问题

2.线程的运行状态

在这里插入图片描述

3.线程使用的三种方式

(1)实现Runnable接口

package com.Thread;
public class MyHomeWork implements Runnable{
    @Override
    public void run() {  //重写run方法
        System.out.println("爱java爱生活");
    }
    public static void main(String[] args) {
        MyHomeWork myHomeWork = new MyHomeWork();//实例化
        Thread thread = new Thread(myHomeWork);
        thread.start();
    }
}

(2)继承Thread类

package com.Thread;
public class MyHomeWork1 extends Thread{
    public void run(){    //方法实现
        System.out.println("爱生活爱java");
    }
    public static void main(String[] args) {
        MyHomeWork1 myHomeWork1 = new MyHomeWork1();//实例化
        myHomeWork1.start();
    }
}

(3)实现Callable接口
可以解决无法抛出异常和返回值的问题

package com.Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyHomeWork implements Callable {
    @Override
    public Object call() throws Exception {
        return "爱生活爱java";
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyHomeWork myHomeWork = new MyHomeWork();
        FutureTask futureTask = new FutureTask<>(myHomeWork);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println( futureTask.get());
    }
}

4.线程的优先级问题

①线程调度的两种模型
  • 抢占式调度模型:那个线程的优先级比较高,抢的CPU时间篇的概率就高一些(处于运行状态的时间多一些)java采用
  • 均分式调度模型:平分时间片
②优先级设置(通过调用Thread的方法)
thread.setPriority(int newPriority);

方法中的参数为一个int类型(默认值为5)

thread.setPriority(Thread.MAX_PRIORITY);        10
thread.setPriority(Thread.MIN_PRIORITY);        1
thread.setPriority(Thread.NORM_PRIORITY);       5

5.线程中的方法

(1)线程休眠:sleep方法(需要抓取异常)
try {  
    Thread.sleep(100);  //参数为休眠的时间
} catch (InterruptedException e) {
    e.printStackTrace();
}
(2)线程礼让:yield方法(礼让不代表放弃,还会争夺资源)

它只是给当前正处于运行状态下的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。

Thread.yield();
(3)线程联合:join方法(需要抓取异常)

join方法允许一个线程等待另一线程的完成。如果t是Thread正在执行其线程的对象,导致当前线程暂停执行,直到t的线程终止。

try {
     thread.join();
 } catch (InterruptedException e) {
    e.printStackTrace();
       } 
(4)线程睡眠结束:interrupt()方法
通过java的异常处理机制终止线程睡眠
 thread.interrupt();//线程睡眠结束,通过java的异常处理机制,
(5)获取线程的名字 (Thread的静态方法)

静态方法通过类名调用,此方法处于那个线程中,返回的就是那个线程的名字

String thread = Thread.currentThread().getName();
  • 主线程的默认名字为 main
  • 支线程的默认名字为Thread-0
(6)设置线程的名字(setName方法)
ThreadT1 t1 = new ThreadT1();
t1.setName("heng");
String name = t1.getName();//Thread-0线程默认名字

6.守护线程(后台线程)

守护线程的特点:①守护线程是一个死循环
               ②用户线程结束,守护线程自动结束
               ③主线程main是一个用户线程

代码实现:

public class DefendThread {
    public static void main(String[] args) {
        DefendTask defend = new DefendTask();
        defend.setDaemon(true);//在启动线程之前将其设置为守护线程
        defend.start();
        for (int i = 0; i <10 ; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
class DefendTask extends Thread{
    @Override
    public void run() {
        int i=0;
       while (true){
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           i++;
           System.out.println(Thread.currentThread().getName()+i);
       }
    }
}

二、线程安全问题

1.什么时候存在线程安全问题

  • 多线程并发
  • 有共享数据
  • 共享数据有修改的行为

2.线程冲突

当在不同线程中运行作用于相同数据的两个操作时,就会发生干扰。这意味着这两个操作由多个步骤组成,并且步骤顺序重叠。

3.线程同步(可以解决线程冲突的问题)

  • 同步语句(synchronized statements )
  • 同步方法(synchronized methods )
    举例:售票员案例
package com.Thread;
public class MyText  implements Runnable {
    public static int ticket = 100;
    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized ("") {
                if (ticket <= 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName()
                        + "卖了1张票,剩余" + --ticket + "张票");
            }
          //  didi();
        }
    }
//    public synchronized void didi(){
//        if (ticket <= 0) {
//            return;
//        }
//        System.out.println(Thread.currentThread().getName()
//                + "卖了1张票,剩余" + --ticket + "张票");
//    }
    public static void main(String[] args) {
        MyText myText = new MyText();
        Thread thread1 = new Thread(myText, "售票员1");
        thread1.start();
        Thread thread2 = new Thread(myText, "售票员2");
        thread2.start();
        Thread thread3 = new Thread(myText, "售票员3");
        thread3.start();
    }
}
  • 锁对象(java.util.concurrent.locks软件包所提供的锁)
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLock implements Runnable {
    public static int ticket = 100;
    ReentrantLock reentrantLock = new ReentrantLock();
    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            reentrantLock.lock();
            if (ticket <= 0) {
                return;
            }
            System.out.println(Thread.currentThread().getName() + "卖了1张票,剩余" + --ticket + "张票");
            reentrantLock.unlock();
        }
    }
    public static void main(String[] args) {
        ThreadLock threadLock = new ThreadLock();
        Thread thread1 = new Thread(threadLock, "售票员1");
        thread1.start();
        Thread thread2 = new Thread(threadLock, "售票员2");
        thread2.start();
        Thread thread3 = new Thread(threadLock, "售票员3");
        thread3.start();
    }
}

4.线程死锁

多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,互相等待,而永久处于阻塞状态。

死锁的解决办法:线程协调:wait和notify方法

class CoordinateB implements Runnable {
    @Override
    public void run() {
        synchronized ("B") {
            System.out.println(Thread.currentThread().getName() 
  + "持有了B锁,等待A锁。。。");
            synchronized ("A") {
                System.out.println(Thread.currentThread().getName() 
  + "持有了B锁和A锁");
                "A".notify();
            }
        }
    }
}
class CoordinateA implements Runnable {
    @Override
    public void run() {
        synchronized ("A") {
            System.out.println(Thread.currentThread().getName() 
  + "持有了A锁,等待B锁。。。");
            try {
                "A".wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized ("B") {
                System.out.println(Thread.currentThread().getName() 
  + "持有了A锁和B锁");
            }
        }
    }
}

三、生产者与消费者模式

在这里插入图片描述
代码实现:

public class ThreadTexttt {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        Thread thread = new Thread(new Producer(list));
        Thread thread1 = new Thread(new Consumer(list));
        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){
                    System.out.println("仓库已满,不需要生产");
                    try {
                        list.wait();//
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    Object o = new Object();
                    list.add(o);
                    System.out.println(Thread.currentThread().getName()+"生产了一个"+o);
                    list.notify();
                }
            }

        }
    }
}
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){
                 System.out.println("仓库空了");
                 try {
                     list.wait();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }else {

                 Object remove = list.remove(0);
                 System.out.println(Thread.currentThread().getName()+"消费了"+remove);
                 list.notify();
             }
         }
     }
    }
}

四、多线程经典面试题分析

1.阻塞问题:t1线程是否对t2有阻塞

public class ThreadText01 {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
//        MyClass myClass1 = new MyClass();
        MyThread myThread = new MyThread(myClass);
        myThread.setName("t1");
        MyThread myThread1 = new MyThread(myClass);
        myThread1.setName("t2");
        myThread.start();
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread1.start();
    }
}
class MyThread extends Thread{
    MyClass myClass;

    public MyThread(MyClass myClass) {
        this.myClass = myClass;
    }
    @Override
    public void run() {
       if(Thread.currentThread().getName().equals("t1")){
           myClass.doSome();
       }
       if (Thread.currentThread().getName().equals("t2")){
           myClass.doOther();
       }
    }
}

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

会,共享的是同一个对象的方法

如果MyClass myClass1 = new MyClass();将myClass1传t2?

不会阻塞,因为是两个对象,数据不共享

如果使两个方法变成静态方法,会出现阻塞吗?

会,在静态方法中使用synchronized的方式为类锁,类锁只有一把,不管多少个对象

2.sleep() 和 wait() 有什么区别?

  • 类的不同:sleep() 来自 Thread,wait() 来自 Object。
  • 释放锁:sleep() 不释放锁;wait() 释放锁。
  • 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒

五、定时器(java.util.Timer)

每隔多长时间执行此代码
new Timer().schedule(TimerTask task, Date firstTime, long period);
此方法需要三个参数   第一个参数:获取一个TimerTask对象  此类为抽象类,所以需要通过子类来实现
                    第二个参数:程序开始的时间
                    第三个参数:运行多长时间

代码展示:

public class TimerText {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        Date par = simpleDateFormat.parse("2020-10-02 11:32:00");
        System.out.println(par);
        timer.schedule(new TimerTx(),par,1000*10);
    }
}
class TimerTx extends TimerTask{

    @Override
    public void run() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss");
        String format = simpleDateFormat.format(new Date());
        System.out.println(format+"完成了一次数据备份");
    }
}
  • TimerTask为抽象类实现了Runnable接口
  • 可以看作是一个线程
  • 定时器也可通过SpringTask框架来完成
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

贫僧洗发爱飘柔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值