多线程创建使用,生命周期,线程安全问题解决,定时器实现,wait,notify方法

线程的创建与启动Thread类有两个常用构造方法Thread()/Thread(Runnable)

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法----适用于此类有父类的情况下

多线程常用方法

Thread.currentThread()//获取当前线程的线程信息
Thread.currentThread().getName()//获取当前线程的线程名称
myThread.setName("mmmmmmmmmm");//设置此线程名称 
this.getId()//获取此线程id
myThread.getName();//获取线程名称
this.getName()//获取当前线程名称,如果是用Thread(Runnable)构造方法传参创建的线程,则获取当前线程名称是传入参数的线程名称
myThread.isAlive()//获取线程活动状态
Thread.sleep(1000);//让当前线程睡眠多少毫秒,和谁调用没关系
myThread.join()//此时myThread线程开始执行,结束后当前线程才开始执行
Thread.yield()//线程让步,放弃cpu执行权,不是阻塞,是从运行到就绪状态,让其他线程去执行
myThread.setPriority();//设置线程优先级1-10,优先级越高,获得cpu资源越多。并不能保证优先级高的线程先运行优先级具有继承性,一般情况采用默认的优先级即可,默认值5
myThread.interrupt();//打断睡眠线程,依赖异常处理机制,会抛出异常
myThread.setDaemon(true);//设置线程为守护线程:当线程只剩下守护线程的时候,JVM就会退出,必须在myThread.start()之前设置

注意:在子线程的run方法中,如果有编译时异常需要处理,只能捕获处理,不能抛出异常


线程生命周期

getState()获取线程当前生命周期,有以下几种:

  • NEW新建状态,创建线程对象,在调用start()之前的状态
  • RUNNABLE()可运行状态,包括READY(就绪)和RUNNING(运行),READY状态可以被线程调度器进行调度至RUNNING状态。Thread.yield()可以将状态从RUNNING转换至READY
  • BLOCKED阻塞状态,线程发起阻塞的I/O操作,或者申请由其他线程占用的独占资源,线程会转换为BLOCKED阻塞状态,此时不会占用CPU资源,当阻塞I/O执行结束或线程获得了申请的资源,线程可以转换为RUNNABLE
  • WAITING等待状态,线程执行了object.wait(),thread.join()方法会把线程转换为WAITING等待状态,执行object.notify()方法,或者加入的线程执行完毕,当前线程会转换为RUNNABLE 状态
  • TIMED_WAITING状态,与WAITING类似,都是等待状态,区别在于此状态的线程如果没有在指定时间内完成期望的操作,该线程会自动转换为RUNNABLE
  • TERMINATED终止状态,线程结束

线程状态图
线程状态图


多线程编程优势及存在的风险

优势:

  • 提高系统吞吐率,多线程使一个进程有多个并发
  • 提高响应性,Web服务器采用专门的线程负责用户的请求处理,缩短用户等待时间
  • 充分利用多核处理器资源,多线程的方式。

问题与风险:

  • 线程安全问题,多线程共享数据时,如果没有采取正确的并发访问控制措施,就可能产生数据一致性问题,如读取脏数据,丢失数据更新
  • 线程活性问题,由于程序自身的缺陷或者由资源稀缺性导致线程一直处于非RUNNABLE状态,常见活性故障:
    • 死锁(Deadlock)
    • 锁死(Lockout)
    • 活锁(Livelock)
    • 饥饿(Starvation)
  • 上下文切换,处理器从执行一个线程转到执行另外一个线程
  • 可能会由一个线程导致JVM意外终止,其他线程也无法执行

线程安全问题:原子性,可见性,有序性

  1. 原子性(Atomic) :
    1. 使用锁,锁具有排他性,保证共享变量在某一时刻只能被一个线程访问
    2. 利用处理器的CAS指令,直接在硬件(处理器和内存)层次实现,看作是硬件锁
  2. 可见性:一个线程对共享变量进行更新后,其他线程可能无法立即读取到更新后的结果,如果其他线程可以读取到,称这个线程对共享变量的更新对其他线程可见,否则称为不可见
  3. 有序性:多线程下,重排序会引起问题

什么时候数据在多线程并发的环境下会存在安全问题呢?

  1. 多线程并发
  2. 有共享数据
  3. 共享数据有修改的行为

Java三大变量:

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

哪些变量存在线程安全问题?

1、局部变量永远不会存在线程安全问题,因为局部变量不共享,常量也不存在线程安全问题

2、实例变量在堆中,堆只有一个 静态变量在方法区中,方法区也只有一个。所以他们都是多线程共享,存在线程安全问题

开发中解决线程安全问题:

  1. 尽量使用局部变量

  2. 创建多个对象,一个线程对应一个对象,对象不共享

  3. synchronized,使用线程同步机制----这种会降低程序运行速率

    synchronized(){
        
    }//小括号中填写想要让哪些多线程同步,填写他们共享的数据
    

synchronized出现在实例方法上缺点?

  1. 这时候只能锁this,所以这种方法不灵活
  2. 此时整个方法体都需要同步,可能无故扩大同步范围,降低程序效率。

synchronized出现在实例方法上优点?

  1. 代码简洁
  2. 如果此时共享对象就是this,而且需要同步的代码块是整个方法体,建议用这种方式

线程安全举例 Vector Hashtable

线程不安全举例 ArrayList HashMap HashSet

**类锁:**synchronized出现在静态方法上,此时无论创建多少个对象,都需要进行排队等待,类锁,一个类只有一把锁,所有对象都是根据类创建的,所以他们都需要进行等待。

死锁 鹬蚌相争,这种不会报错也没有异常

终断线程方法:

  1. stop方法,会丢失数据,古老方法。过时,不适用
  2. interrupt,
    1. 处于运行状态,需要设置和接收interrupt判断是否终断
    2. 处于阻塞状态,会抛出异常,catch并不会自动结束程序,因为它处理了异常,程序还是会按照正常顺序运行并判断是否结束
    3. 两种状态都会执行结束他们程序之后才会结束
  3. 设置属性,如flag,需要终断时,设置为false

守护线程:

myThread.setDaemon(true);//设置线程为守护线程,当线程只剩下守护线程的时候,JVM就会退出,必须在myThread.start()之前设置

定时器实现

  1. 多线程的sleep()方法
  2. java.util.Timer类
  3. 实现Callable接口----优点:可以获取线程返回值,缺点:效率低,在获取执行结果时,当前线程受阻塞

Timer类,定时器实现代码:

public class TimerTest {
    public static void main(String[] args) throws ParseException {
        //定义时间
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date parse = sdf.parse("2020-12-22 19:06:40");
        //创建定时器对象
        Timer timer = new Timer();
        //        Timer timer=new Timer(true);守护线程的方式
        //注意定时任务TimerTask是抽象类,需要单独写定时任务类,第一个参数是定时任务对象,第二个是执行时间,第三个是多久间隔执行一次
        timer.schedule(new DingShi(), parse);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("这是匿名内部类的方法");
            }
        }, parse);
    }
}
class DingShi extends TimerTask {
    @Override
    public void run() {
        System.out.println("定时器执行任务");

    }
}

用Callable接口实现定时器代码:

public class TimerCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //必须传入一个Callable接口实现类对象
        FutureTask futureTask = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {//call方法相当于run()只是这个有返回值
                int a = 10;
                Thread.sleep(1000);
                return a;
            }
        });
        Thread thread = new Thread(futureTask);
        thread.start();
        Object o = futureTask.get();//获取返回值
        System.out.println(o);
        System.out.println("main");
    }
}

Object类的wait方法和notify方法,注意不是线程对象的方法,是普通java对象都有的方法

wait方法让正在此对象上活动的线程进入无限期等待状态,直到被唤醒

notify方法唤醒对象上处于等待状态的线程,notifyAll是唤醒对象上所有处于等待状态的线程

两种方法都建立在synchronized线程同步的基础上

注意:

  1. wait方法会释放之前占有的锁
  2. notify方法只会通知,不会释放之前占有的锁

生产者和消费者模式一个生产,一个消费,可以用wait和notify结合使用实现
代码练习

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值