Java多线程——NWU_LK

Thread的三种实现方案

任何实现方法都必须使用start方法来启动线程

  1. 继承Thread类,实现run方法
class MyThread extends Thread{
    private String name;
    public MyThread(String name){
        this.name=name;
    }
    @Override
    public void run() {
        for (int i=0;i<5;i++){
            System.out.println(this.name+"hahahha");
        }
    }
}
public static void main(String[] args) {
    new MyThread("AAA").start();
    new MyThread("BBB").start();
    new MyThread("CCC").start();
}
  1. 实现Runable接口,因为Thread类实现了Runable方法,可以接收的参数为Runable
class MyThread implements Runnable{
    private String name;
    public MyThread(String name){
        this.name=name;
    }
    @Override
    public void run() {
        for (int i=0;i<5;i++){
            System.out.println(this.name+"hahahha");
        }
    }
}
public static void main(String[] args) {
    new Thread(new MyThread("AAA")).start();
    new Thread(new MyThread("BBB")).start();
    new Thread(new MyThread("CCC")).start();
}
  1. 实现Callable接口,因为futureTask实现了RunnableFuture接口,而该接口又继承了Runable接口和future接口。Runable接口负责多线程,future借口负责返回值,因此将callable传入futureTask,然后再将futureTask传入Thread即可实现多线程。
class MyThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i=0;i<5;i++){
            System.out.println("hahahha");
        }
        return "线程执行完毕";
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    FutureTask<String> task=new FutureTask<String>(new MyThread());
    new Thread(task).start();
    System.out.println(task.get());
}
线程的方法

线程命名:new Thread(Runable,线程名字),如果不设置名字则会自动生成一个不重复的名字

获取线程名字:Thread.currentThread().getName()

线程的休眠:sleep(毫秒[,纳秒]),但是有可能在休眠时候产生中断异常,可以捕获的异常

判断是否中断:isInterupted(),即判断打断标记是否为true,正常线程被打断为true

判断是否中断:Thread.interupted(),即判断打断标记是否为true,正常线程被打断为true,该方法会将打断标记设置为false

线程中断:interupt(),可以正常运行的线程或者打断处于阻塞状态的线程,比如wait、sleep、join。打断正常运行的线程打断标记设置为true,但是程序不会停止。打断阻塞状态的线程打断标记重新设置为false,程序会停止

线程强制执行:join(),在其他的线程中加入某个需要执行的线程

线程礼让:yield(),但每次判断只会礼让一次

设置守护线程:setDaemon(true)设置守护线程,当整体程序执行完毕后,守护线程无论执行完与否都会停止,Java的GC就是最大的守护线程。

如何优雅的停止线程

在这里插入图片描述
循环判断当前线程是否被打断,如果被打断的话则执行后续的操作;如果未打断的话则让其睡眠几秒,如果在睡眠期间被打断会抛出异常,此时打断标记会变为false,则设置打断标记为true,并且下一次判断时打断标记就是true,可以执行后续操作。如果睡眠之后被打断,那么下一次进入循环则直接判断。

class Monitor{
    private Thread monitor;
    public void start(){
        monitor=new Thread(()->{
            while (true){
                Thread current=Thread.currentThread();
                if (current.isInterrupted()){
                    System.out.println("结束");
                    break;
                }
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    current.interrupt();
                    e.printStackTrace();
                }
            }
        });
        monitor.start();
    }

    public void end(){
        monitor.interrupt();
    }
}
wait-notify原理

在这里插入图片描述
这两种状态下的线程都不会占用CPU

  • Waiting:调用了wait方法会进入该状态
  • Blocked:获取不到锁的线程的状态

调用了owner中的线程发现条件不满足,调用wait方法进入WaitSet。当被唤醒的时候,会进入到EntryList中重新竞争锁。

调用wait和notify、notifyall方法之前必须先获得对象的锁。

线程优先级

优先级越高的线程越有可能先执行,但不是必定先执行

优先等级:

  • 最高:MAX_PRIORITY,值为10
  • 中等:NORM_PRIORITY,值为5(线程默认优先级)
  • 最低:MIN_PRIORITY,值为1

设置优先级:setPriority(int x)

获取优先级:getPriority()

线程的6大状态及转换

在这里插入图片描述

  • NEW:表示线程刚刚创建,还没有start
  • RUNNABLE:表示已经start,包含了操作系统层面的运行状态、就绪状态、阻塞状态
  • BLOCKED:线程竞争锁失败时进入到该状态
  • WAITING:表示该线程进入了monitor对象的WaitSet,再等待状态
  1. 当调用对象的wait方法时从runnable进入waiting状态,当被notify、notifyAll或interrupt时,如果获取到了锁则进入runnable状态,竞争不到锁时会进入blocked状态
  2. 当前线程调用了某个线程的join方法之后,当前线程从runnable进入waiting,当对应线程执行完毕或者调用了当前线程的interrupt方法后会让目标线程进入runnale
  3. 当前线程调用LockSupport.park方法会让当前线程进入waiting状态,调用LockSupport.unpark(目标线程)方法再回到runnable状态
  • TIME_WAITING:超时等待
  1. 调用wait(时间)时,线程状态变为TIME_WAITING,当到了时间或者调用了notify、notifyAll或interrupt时会竞争锁,如果获取到了锁则进入runnable状态,竞争不到锁时会进入blocked状态
  2. 调用join(time)会让该线程进入TIME_WAITING,时间到了或者掉用了interrupt时会变为runnable
  3. 调用了sleep(time)时进入该状态,睡眠过后变为runnable
  4. 调用LockSupport.parkNanos(nanos)或LockSupport.parkUtil(time)时进入该状态,调用unpark或者interrupt时进入runnable状态
  • TERMINATED:线程代码执行完毕后的状态
synchronized优化

在java HotSpot虚拟机里,每个对象都有对象头(包括class指针和MarkWord)。MarkWord平时存储这个对象的哈希值、分代年龄,当加锁时,这些信息就根据情况被替换为标记位(即锁的类型)、锁记录指针、重量级锁指针、线程ID等内容。每个线程的栈帧都包含一个锁记录的结构,内部可以存储锁定对象的Markword。

  • 轻量级锁:如果一个对象有多个对象访问,但是多个线程的访问时间是错开的,即没有锁的竞争,那么线程对该对象上锁时加轻量级锁可以优化(当关闭了偏向锁时,当在一个线程内锁同一对象即发生了锁重入时也仍然是轻量级锁)。给对象加锁时,首先检查对象加锁状态,若为01,表示无锁,那么线程会首先将对象的markword复制到锁记录中,然后用CAS将对象头的Mark替换为线程的锁记录地址。如果成功的替换,那么上锁成功。对象状态变为00(轻量级锁),当同步代码块执行完毕后,解锁时再将对象的状态改为01,

  • 重量级锁:如果一个对象有多个对象访问,但是多个线程的访问时间有重叠,有锁的竞争,那么线程对该对象上锁时从轻量级锁变为重量级锁。当线程在用CAS修改对象的Markword时,如果失败的话那么会发生锁膨胀,将轻量级锁升级为重量级锁。将对象的状态改为10(线程阻塞中),并且在对象头里加入重量级锁的指针(该指针为了找到要唤醒的线程)。当上一个线程执行完毕后,会尝试解锁,但是因为锁已经升级为重量级锁,因此会解锁失败,接着会释放重量级锁,唤起阻塞的线程竞争。

  • 自旋锁:自旋锁可以继续优化重量级锁, 当众多的线程竞争重量级锁时,其中的一个获取了锁,那么其他的线程不会立即阻塞(因为阻塞需要保持当前状态,耗时),而是会自旋重试获取重量级锁。当重试到了一定的次数后才会阻塞。java6之后的自旋次数是自适应的,根据实际情况会调整。自旋会占用CPU时间,因此只适用于多核CPU

  • 偏向锁: 轻量级锁在有锁重入的情况时,每次重入仍然要耗时执行CAS操作,java6引入了偏向锁来做进一步的优化,只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的,就表示没有竞争,不用重新CAS。当有其他线程要对这个对象加锁时,会撤销偏向锁,变为轻量级锁。当撤销次数超过20次时,会将该对象继续转变为偏向锁。当偏向次数超过40次时,JVM会认为偏向是错误的,因此会撤销该类的所有对象的偏向锁。
    缺点:撤销偏向锁要将锁升级为轻量级锁,这个过程需要STW;其次访问对象的hashcode也会撤销偏向锁,调用对象的wait方法也会撤销偏向锁等

其他优化:

  • 减少上锁时间,即被锁的代码越少越好
  • 减小锁的粒度,比如ConcurrentHashMap
  • 锁粗化,详见StringBuffer
  • 锁清除:比如某个加锁对象是方法内部的局部变量,那么它不会被其他线程访问到,因此这时即时编译器就会忽略掉所有的同步操作。
  • 读写分离
生产者消费者模型
class Producer implements Runnable {
    private Resource resource;
    public Producer(Resource resource){
        this.resource=resource;
    }
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            try {
                resource.add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Consumer implements Runnable {
    private Resource resource;
    public Consumer(Resource resource){
        this.resource=resource;
    }
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            try {
                resource.rem();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Resource{
    private int num=0;
    public synchronized void add() throws InterruptedException {
        while (this.num>0){
            super.wait();
        }
        Thread.sleep(500);
        this.num++;
        System.out.println("num="+this.num);
        super.notifyAll();
    }
    public synchronized void rem() throws InterruptedException {
        while (this.num<1){
            super.wait();
        }
        this.num--;
        System.out.println("num="+this.num);
        super.notifyAll();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值