Thread类的基本用法

创建子类,继承自Thread并且重写run方法:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        // 最基本的创建线程的办法.
        Thread t = new MyThread();
        //调用了start方法才是真正的在系统中创建了线程,执行run方法
        t.start();
    }
}

创建一个类,实现Runnable接口再创建Runnable是实例传给Thread

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hello");
    }
}
public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}

匿名内部类:

创建了一个匿名内部类,继承自Thread类,同时重写run方法,再new出匿名内部类的实例

public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("hello");
            }
        };
        t.start();
    }
}

new的Runnable,针对这个创建的匿名内部类,同时new出的Runnable实例传给Thread的构造方法

public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
        t.start();
    }
}

lambda表达式 lambda代替Runnable

public class Demo6 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            System.out.println("hello");
        });
        t.start();
    }
}

线程指标

  • 1.isDaemon();是否后台线程 后台线程不影响进程退出,不是后台线程会影响进程退出
  • 2.isAlive();是否存活 在调用start前系统中是没有对应线程的,run方法执行完后线程就销毁了,t对象可能还存在
  • 3.isinterrupted();是否被中断

run和start的区别:run单纯的只是一个普通方法描述了任务的内容 start则是一个特殊的方法,内部会在系统中创建线程

中断线程

线程停下来的关键是要让对应run方法执行完,对于main线程来说main方法执行完了才会终止

1.手动设置标志位

在线程中控制这个标志位就能影响到这个线程结束,但是此处多个线程共用一片虚拟空间,因此main线程修改的isQuit和t线程判断的isQuit是同一个值

public class Demo10 {
    // 通过这个变量来控制线程是否结束.
    private static boolean isQuit = false;
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!isQuit) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        // 就可以在 main 线程中通过修改 isQuit 的值, 来影响到线程是否退出
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // main 线程在 5s 之后, 修改 isQuit 的状态.
        isQuit = true;
    }
}

2.使用Thread中内置的一个标志位来判定

Thread.interruted()这是一个静态方法 Thread.currentThread().isInterrupted()这是一个实例方法,其中currentThread能够获取到当前线程的实例

public class Demo7 {
    public static void main(String[] args)  {
        Thread t = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 当触发异常之后, 立即就退出循环~
                    System.out.println("这是收尾工作");
                    break;
                }
            }
        });
        t.start();
        try{
            Thread.sleep(5000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        // 在主线程中, 调用 interrupt 方法, 来中断这个线程.
        // t.interrupt 的意思就是让 t 线程被中断!!
        t.interrupt();
    }
}

需要注意的是调用这个方法t.interrupt()可能会产生两种情况:

  • 1)如果t线程处在就绪就设置线程的标志位为true
  • 2)如果t线程处在阻塞状态(sleep),就会触发一个InterruptExeception

线程等待

多个线程之间调度顺序是不确定的,有时候我们需要控制线程之间的顺序,线程等待就是一种控制线程执行顺序的手段,此处的线程等待只要是控制线程结束的先后顺序。
哪个线程中的join,哪个线程就会阻塞等待直到对应的线程执行完毕为止。

  • t.join();调用这个方法的线程是main线程,针对t这个对象调用的此时就是让main等待t。代码执行到join这一行就停下了,让t先结束然后main继续。
  • t.join(10000);join提供了另一个版本为带一个参数的,参数为等待时间10s之后join直接返回不再等待

Thread.currentThread()

能够获取当前线程的应用,哪个线程调用的currentThread就获取到哪个线程的实例 对比this如下:

对于这个代码来说,通过继承Thread的方法来创建线程。此时run方法中直接通过this拿到的就是当前Thread的实例

public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
                System.out.println(this.getName());
            }
        };
        t.start();
    }
}

然而此处this不是指向Thread类型,而是指向Runnable,Runnable只是一个单纯的任务没有name属性,要想拿到线程名字只能通过Thread.currentThread()

public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                //err
                //System.out.println(this.getName());
                //right
                System.out.println(Thread.currentThread().getName());
            }
        });
        t.start();
    }
}

进程状态

针对系统层面:

  • 就绪
  • 阻塞

java中Thread类进一步细化:

  • NEW:把Thread对象创建好了但是还没有调用start
  • TERMINATED:操作系统中的线程已执行完毕销毁,但是Thread对象还在获取到的状态
  • RUNNABLE:就绪状态,处在该状态的线程就是在就绪队列中,随时可以调度到CPU上
  • TIME_WAITING:调用了sleep就会进入到该状态,join(超时时间) BLOCKED:当前线程在等待锁导致了阻塞
  • WAITING:当前线程在等待唤醒

状态转换图:

线程安全问题

定义:操作系统中线程调度是随机的,导致程序的执行可能会出现一些bug。如果因为调度随机性引入了bug线程就是不安全的,反之则是安全的。
解决方法:加锁,给方法直接加上synchronized关键字,此时进入方法就会自动加锁,离开方法就会自动解锁。当一个线程加锁成功的时候,其他线程尝试加锁就会触发阻塞等待,阻塞会一直持续到占用锁的线程把锁释放为止。

synchronized public void increase() {
        count  ;
}

线程不安全产生的原因:

  • 1.线程是抢占式执行,线程间的调度充满随机性。
  • 2.多个线程对同一个变量进行修改操作
  • 3.针对变量的操作不是原子的
  • 4.内存可见性也会影响线程安全(针对同一个变量t1线程循环进行多次读操作,t2线程少次修改操作,t1就不会从内存读数据了而是从寄存器里读)
  • 5.指令重排序,也是编译器优化的一种操作,保证逻辑不变的情况下调整顺序,解决方法synchronized。

内存可见性解决方法:

  • 1.使用synchronized关键字 使用synchronized不光能保证指令的原子性,同时也能保证内存的可见性。被synchronized包裹起来的代码编译器就不会从寄存器里读。
  • 2.使用volatile关键字 能够保证内存可见性,禁止编译器作出上述优化,编译器每次执行判定相等都会重新从内存读取。

synchronized用法

在java中每个类都是继承自Object,每个new出来的实例里面一方面包含自己安排的属性,另一方面包含了“对象头”即对象的一些元数据。加锁操作就是在这个对象头里面设置一个标志位。

1.直接修饰普通的方法

使用synchronized的时候本质上是对某个“对象”进行加锁,此时的锁对象就是this。加锁操作就是在设置this的对象头的标志位,当两个线程同时尝试对同一个对象加锁的时候才有竞争,如果是两个线程在针对两个不同对象加锁就没有竞争。

class Counter{
	public int count;
	synchronized public void increase(){
	count  ;
	}
}

2.修饰一个代码块

需要显示制定针对那个对象加锁(java中的任意对象都可以作为锁对象)

public void increase(){
    synchronized(this){
    count  ;
    }
}

3.修饰一个静态方法

相当于针对当前类的类对象加锁,类对象就是运行程序的时候。class文件被加载到JVM内存中的模样。

synchronized public static void func(){
}

或者

public static void func(){
    synchronized(Counter.class){

    }
}

监视器锁monitor lock

可重入锁就是同一个线程针对同一个锁,连续加锁两次,如果出现死锁就是不可重入锁,如果不会死锁就是可重入的。因此就把synchronized实现为可重入锁,下面的例子里啊连续加锁操作不会导致死锁。可重入锁内部会记录所被哪个线程占用也会记录加锁次数,因此后续再加锁就不是真的加锁而是单纯地把技术给自增。

synchronized public void increase(){
    synchronized(this){
        count  ;
    }
}

死锁的其他场景

  • 1.一个线程一把锁
  • 2.两个线程两把锁
  • 3.N个线程M把锁(哲学家就餐问题,解决方法:先拿编号小的筷子)

死锁的四个必要条件(前三个都是锁本身的特点)

  • 1.互斥使用,一个锁被另一个线程占用后其他线程就用不了(锁的本质,保证原子性)
  • 2.不可抢占,一个锁被一个线程占用后其他线程不可把这个锁给挖走
  • 3.请求和保持,当一个线程占据了多把锁之后,除非显示的释放否则这些锁中都是该线程持有的
  • 4.环路等待,等待关系成环(解决:遵循固定的顺序加锁就不会出现环路等待)

java线程类:

  • 不安全的:ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StringBuilder
  • 安全的:Vector,HashTable,ConcurrentHashMap,StringBuffer,String

volatile

禁止编译器优化保证内存可见性,产生原因:计算机想执行一些计算就需要把内存的数据读到CPU寄存器中,然后再从寄存器中计算写回到内存中,因为CPU访问寄存器的速度比访问内存快很多,当CPU连续多次访问内存结果都一样,CPU就会选择访问寄存器。

JMM(Java Memory Model)Java内存模型

就是把硬件结构在java中用专业的术语又重新抽象封装了一遍。

  • 工作内存(work memory)其实指的不是内存,而是CPU寄存器。
  • 主内存(main memeory)这才是主内存。
  • 原因:java作为一个跨平台编程语言要把硬件细节封装起来,假设某个计算机没有CPU或者内存同样可以套到上述模型中。

寄存器,缓存和内存之间的关系

CPU从内存取数据太慢,因此把数据直接放到寄存器里来读,但寄存器空间太紧张于是又搞了一个存储空间,比寄存器大比内存小速度比寄存器慢比内存快称为缓存。寄存器和缓存统称为工作内存。

寄存器,缓存和内存之间的关系图

  • 存储空间:CPU<L1<L2<L3<内存
  • 速度:CPU>L1>L2>L3>内存
  • 成本:CPU>L1>L2>L3>内存

volatile和synchronized的区别

  • volatile只是保证可见性不保证原子性,只是处理一个线程读和一个线程写的过程。
  • synchronized都能处理

wait和notify

等待和通知处理线程调度随机性问题的,join也是一种控制顺序的方式更倾向于控制线程结束。wait和notify都是Object对象的方法,调用wait方法的线程就会陷入阻塞,阻塞到有线程通过notify来通知。

public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        System.out.println("wait前");
        object.wait();
        System.out.println("wait后");
    }
}

wait内部会做三件事;

  • 1.先释放锁
  • 2.等待其他线程的通知
  • 3.收到通知后重新获得锁并继续往下执行

因此想用wait/notify就得搭配synchronized

public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized (object){
            System.out.println("wait前");
            object.wait();
            System.out.println("wait后");
        }
    }
}

注意:wait notify都是针对同一对象来操作的,例如现在有一个对象o,有10个线程都调用了o.wait,此时10个线程都是阻塞状态。如果调用了o.notify就会把10个线程中的一个线程唤醒。而notifyAll就会把所有10个线程全都给唤醒,此时就会竞争锁。

到此这篇关于Java多线程Thread类的使用及注意事项的文章就介绍到这了,更多相关Java Thread 内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Java多线程Thread类的使用及注意事项的更多相关文章

  1. iOS:核心图像和多线程应用程序

    我试图以最有效的方式运行一些核心图像过滤器.试图避免内存警告和崩溃,这是我在渲染大图像时得到的.我正在看Apple的核心图像编程指南.关于多线程,它说:“每个线程必须创建自己的CIFilter对象.否则,你的应用程序可能会出现意外行为.”这是什么意思?我实际上是试图在后台线程上运行我的过滤器,所以我可以在主线程上运行HUD(见下文).这在coreImage的上下文中是否有意义?

  2. ios – 意外的核心数据多线程违规

    我正在使用苹果的并发核心数据调试器.-com.apple.CoreData.ConcurrencyDebug1有时候我得到__Multithreading_Violation_AllThatIsLeftToUsIsHonor__,即使我几乎肯定线程没有被违反.这是发生异常的代码的一部分(代码是扩展NSManagedobject的协议的一部分):代码在上下文的执行:块中执行.这里是线程信息:和调试器

  3. ios – UIGraphicsBeginImageContextWithOptions和多线程

    我对UIGraphicsBeginImageContextWithOptions和线程有点困惑,因为根据UIKitFunctionReferenceUIGraphicsBeginImageContextWithOptions应该只在主线程上调用.当被调用时,它创建一个基于位图的上下文,可以使用CoreGraphics的函数或者像-drawInRect这样的方法来处理:对于UIImage,-draw

  4. Swift之dispatch_source实现多线程定时关闭功能

    由于在项目中需要用到定时关闭音频功能,本来打算用NSTimer的,可是写起来并不是那么精简好用,所以又在网上找到相关的实例,结合自己项目需要,就写出了如下代码,还请大家指教,废话不多说:

  5. swift 多线程实现

  6. swift_多线程基础_最简单用法GCD, NSOperationQueue, NSThread

    ////ViewController.swift//study1-1//Createdbyadminon15/12/28.//copyright2015年admin.Allrightsreserved.//importUIKitclassViewController:UIViewController{@IBOutletvarmyLable:UILabel?@IBActionfuncclickBut

  7. swift__多线程GCD详解

    有以下*-disPATCH_QUEUE_PRIORITY_HIGH:*-disPATCH_QUEUE_PRIORITY_DEFAULT:多用默认*-disPATCH_QUEUE_PRIORITY_LOW:*-disPATCH_QUEUE_PRIORITY_BACKGROUND:*第二个参数为预留参数,一般为0*/letmyQueue:dispatch_queue_t=dispatch_get_global_queue//用异步的方式运行队列里的任务dispatch_async//-------------

  8. Swift - 多线程实现方式3 - Grand Central DispatchGCD

    dispatchqueue可以是并发的或串行的。dispatch_suspend后,追加到DispatchQueue中尚未执行的任务在此之后停止执行。6//创建并行队列conQueue:dispatch_queue_t=dispatch_queue_create//暂停一个队列dispatch_suspend//继续队列dispatch_resume6,dispatch_once一次执行保证dispatch_once中的代码块在应用程序里面只执行一次,无论是不是多线程。注意,我们不能(直接)取消我们已经提

  9. 【Swift】三种多线程处理方式

    )Threadbtn.frame=CGRectMakeThreadbtn.setTitle//普通状态下的文字Threadbtn.setTitle//触摸状态下的文字letmethod:Selector=methodarr[index!]Threadbtn.addTargetself.view.addSubview;}}overridefuncdidReceiveMemoryWarning(){super.didReceiveMemoryWarning()}//1.NSThread线程functestNS

  10. Swift多线程之GCD

    学自:http://www.jianshu.com/p/2598a4e9c139

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部