线程基础 1. 线程的生命周期 1.1 新建状态: 使用 new 关键字和 thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。 1.2 就绪状态: 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待jvm里线程调度器的调度。 1.3 运行状态: 如果就绪状态的线程获取 cpu 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。 1.4 阻塞状态: 如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 i/o 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 i/o 处理完毕,线程重新转入就绪状态。 1.5 死亡状态: 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。 2. 线程的优先级和守护线程 2.1 线程的优先级 每一个 java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。 java 线程的优先级是一个整数,其取值范围是 1 (thread.min_priority ) - 10 (thread.max_priority )。 默认情况下,每一个线程都会分配一个优先级 norm_priority(5)。 2.2 守护线程 java中有两种线程:用户线程和守护线程。可以通过isdeamon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。 用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。 需要注意的是:jvm在“用户线程”都结束后会退出。 3. 创建线程 3.1 通过实现 runnable 接口 步骤:
创建类实现 runnable 接口 实现 run() 方法,线程实际运行的方法 实现 start() 方法,里面实例化线程对象(new thread(this, threadname)),调用线程对象的 start() 方法 代码实现
package com.ljw.thread;public class runnabledemo { public static void main(string[] args) { // 测试 runnabledemo r = new runnabledemo(); runnablethread r1 = r.new runnablethread(thread1); r1.start(); runnablethread r2 = r.new runnablethread(thread2); r2.start(); } class runnablethread implements runnable{ private string threadname; private thread t; public runnablethread(string name) { // todo auto-generated constructor stub threadname = name; system.out.println(创建线程 +threadname); } @override public void run() { system.out.println(正在运行线程:+threadname); try { for(int i=10;i>0;i--) { system.out.println(线程:+threadname+ 正在打印:+i); thread.sleep(50); } }catch(exception e) { e.printstacktrace(); } system.out.println(线程:+threadname+ 正在退出......); } public void start() { system.out.println(开始线程 +threadname); if(t == null) { t = new thread(this, threadname); t.start(); } } }} 3.2 通过继承 thread 类本身 步骤:
创建类继承 thread 类 下面与用runnable接口一样 3.3 通过 callable 和 future 创建线程 步骤:
创建 callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。 创建 callable 实现类的实例,使用 futuretask 类来包装 callable 对象,该 futuretask 对象封装了该 callable 对象的 call() 方法的返回值。 使用 futuretask 对象作为 thread 对象的 target 创建并启动新线程。 调用 futuretask 对象的 get() 方法来获得子线程执行结束后的返回值。 callable接口与runnable接口的区别:
callable中call方法可以有返回值,而runnable中的run方法没有返回值 代码实现
package com.ljw.thread;import java.util.concurrent.callable;import java.util.concurrent.futuretask;public class callablethreadtest implements callable { public static void main(string[] args) { callablethreadtest ctt = new callablethreadtest(); futuretask ft = new futuretask(ctt); for(int i = 0;i < 10;i++) { system.out.println(thread.currentthread().getname()+ 的循环变量i的值+i); if(i%2==0) { new thread(ft,有返回值的线程).start(); } } try { system.out.println(子线程的返回值:+ft.get()); } catch (interruptedexception e) { e.printstacktrace(); } catch (exception e) { e.printstacktrace(); } } @override public integer call() throws exception { int i = 0; for(;i<10;i++) { system.out.println(thread.currentthread().getname()+ +i); } return i; } } 4. synchronized关键字 4.1 概述 synchronized关键字是为了解决共享资源竞争的问题,共享资源一般是以对象形式存在的内存片段,但也可以是文件、输入/输出端口,或者是打印机。 要控制对共享资源的访问,得先把它包装进一个对象。然后把所有要访问的这个资源的方法标记为synchronized。 如果某个任务处于一个对标记为synchronized的方法的调用中,那么在这个线程从该方法返回之前,其他所有要调用类中任何标记为synchronized方法的线程都会被阻塞。 4.2 基本原则 第一条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象的该synchronized方法或者synchronized代码块的访问将被阻塞。 第二条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程仍然可以访问该对象的非同步代码块。 第三条:当一个线程访问某对象的synchronized方法或者synchronized代码块时,其他线程对该对象的其他的synchronized方法或者synchronized代码块的访问将被阻塞。 4.3 实例 两个相似的例子
实例1:实现接口runnable package com.ljw.thread;public class runnabletest { public static void main(string[] args) { class myrunnable implements runnable{ @override public void run() { synchronized (this) { for(int i=0;i<5;i++) { try { thread.sleep(100); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname() + 正在进行打印 +i); } } } } runnable runnable = new myrunnable(); thread t1 = new thread(runnable,t1); thread t2 = new thread(runnable,t2); t1.start(); t2.start(); }} 运行结果:
t1 正在进行打印 0t1 正在进行打印 1t1 正在进行打印 2t1 正在进行打印 3t1 正在进行打印 4t2 正在进行打印 0t2 正在进行打印 1t2 正在进行打印 2t2 正在进行打印 3t2 正在进行打印 4 结果说明:run()方法中存在synchronized(this)代码块,而且t1和t2都是基于myrunnable这个runnable对象创建的线程。这就意味着,我们可以将synchronized(this)中的this看做是myrunnable这个runnable对象;因此,线程t1和t2共享“myrunable对象的同步锁”。所以,当一个线程运行的时候,另外一个线程必须等待正在运行的线程释放myrunnable的同步锁之后才能运行。
实例2:继承thread类 public class threadtest { public static void main(string[] args) { class mythread extends thread{ public mythread(string name){ super(name); } @override public void run() { synchronized(this){ for(int i=0;i<10;i++){ try { thread.sleep(100); system.out.println(thread.currentthread().getname()+ 正在进行打印 +i); } catch (interruptedexception e) { e.printstacktrace(); } } } } } thread t1 = new mythread(t1); thread t2 = new mythread(t2); t1.start(); t2.start(); }} 运行结果:
t2 正在进行打印 0t1 正在进行打印 0t2 正在进行打印 1t1 正在进行打印 1t1 正在进行打印 2t2 正在进行打印 2t2 正在进行打印 3t1 正在进行打印 3t1 正在进行打印 4t2 正在进行打印 4 对比结果:发现实例1的两个线程是一个结束后,另一个才运行,实例2的是交叉运行,在run()方法中都有synchronized(this),为什么结果不一样?
分析:synchronized(this)中的this是指当前对象,即synchronized(this)所在类对应的当前对象。它的作用是获取获取当前对象的同步锁。对于实例2中的synchronized(this)中的this代表的是mythread对象,t1和t2是两个不同的mythread对象,因此t1和t2在执行synchronized(this)时获取的是不同对象的同步锁。对于实例1来说,synchronized(this)中的this代表的时候myrunnable对象,t1和t2是共同一个myrunnable对象,因此,一个线程获取了对象的同步锁,会造成另一个线程的等待。
4.4 synchronized方法和synchronized代码块 4.4.1 概述 synchronized方法是用synchronized修饰方法,这是一种粗粒度锁;这个同步方法(非static方法)无需显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。 synchronized代码块是用synchronized修饰代码块,这是一种细粒度锁。线程开始执行同步代码块之前,必须先获得对同步监视器的锁定,任何时候只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对同步监视器的锁定。虽然java允许使用任何对象作为同步监视器,但同步监视器的目的就是为了阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。 4.4.2 实例 public class snchronizedtest { public static void main(string[] args) { class demo { // synchronized方法 public synchronized void synmethod() { for(int i=0; i<1000000; i++) ; } public void synblock() { // synchronized代码块 synchronized( this ) { for(int i=0; i<1000000; i++) ; } } } }} 4.5 实例锁和全局锁 4.5.1 概述 实例锁:锁在某个实例对象上。如果该类是单例,那么该锁也是具有全局锁的概念。实例锁对应的就是synchronized关键字。 全局锁:该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。 4.5.2 实例 pulbic class something { public synchronized void issynca(){} public synchronized void issyncb(){} public static synchronized void csynca(){} public static synchronized void csyncb(){}} 假设,类something有两个实例(对象)分别为x和y。分析下面4组表达式获取锁的情况。
x.issynca()与x.issyncb()
不能同时访问,因为都是访问对象x的同步锁 x.issynca()与y.issynca()
可以同时访问,因为是访问不同对象(x和y)的同步锁 x.csynca()与y.csyncb()
不能同时访问,因为两个方法是静态的,相当于用something.csynca()和something.csyncb()访问,是相同的对象 x.issynca()与something.csynca()
可以同时访问,因为访问不同对象 5. volatile 关键字 5.1 volatile原理 java语言提供了一种稍微同步机制,即volatile变量,用来确保将变量的更新操作通知其他线程 在访问volatile变量是不会执行加锁操作,因此也就不会重新执行线程阻塞,volatile变量是一种比synchronized关键字轻量级的同步机制 当一个变量被volatile修饰后,不但具有可见性,而且还禁止指令重排。volatile的读性能消耗与普通变量几乎相同,但是写操作就慢一些,因为它要保证本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行 6. 线程等待和唤醒 6.1 常用方法 在object.java中,定义了wait(),notify()和notifyall()等接口 wait()方法的作用是让当前线程进入阻塞状态,同时会释放当前对象所持有的锁 notify()唤醒当前对象上的等待线程,notifyall()则是唤醒所有的线程 6.2 实例 package com.ljw.thread;public class waitdemo { public static void main(string[] args) { class threadtest extends thread{ @override public void run() { synchronized (this) { system.out.println(开始运行线程 +thread.currentthread().getname()); system.out.println(唤醒线程notify()); notify(); } } } threadtest thread1 = new threadtest(); thread1.start(); synchronized (thread1) { try { system.out.println(主线程进入阻塞,释放thread对象的同步锁,wait()); thread1.wait(); // wait()是让当前线程进入阻塞状态,wait()是在主线程中执行, } catch (interruptedexception e) { e.printstacktrace(); } } system.out.println(主线程继续进行); }} 7. 线程让步和休眠 7.1 线程让步 7.1.1 概述 在java线程中,yield()方法的作用是让步,它能让当前线程由“运行状态”进入到“就绪状态”,可能让其它同级别的线程获得执行权,但不一定,可能它自己再次由“就绪状态”进入到“运行状态” 7.1.2 实例 package com.ljw.thread;public class yieldtest { public static void main(string[] args) { class threada extends thread{ public threada(string name){ super(name); } @override public synchronized void run() { for(int i=0;i executors工具类(工厂类) /* * newfixedthreadpool(int threadcount) 创建固定数量的线程池 * newcachedthreadpool() 创建动态数量的线程池 */ executorservice es = executors.newfixedthreadpool(3); runnable task = new mytask(); // 提交任务 es.submit(task); es.submit(task); es.shutdown(); // 关闭线程池,则表示不在接收新任务,不代表正在线程池的任务会停掉 } }class mytask implements runnable{ @override public void run() { for(int i=0;i<100;i++) { system.out.println(thread.currentthread().getname()+ mytask +i); } }} 2. 线程安全与锁 2.1 重入锁和读写锁 package com.ljw.thread;import java.util.concurrent.callable;import java.util.concurrent.executorservice;import java.util.concurrent.executors;import java.util.concurrent.locks.reentrantlock;import java.util.concurrent.locks.reentrantreadwritelock;import java.util.concurrent.locks.reentrantreadwritelock.readlock;import java.util.concurrent.locks.reentrantreadwritelock.writelock;/** * reentrantlock类,重入锁:lock接口的实现类,与synchronized一样具有互斥锁功能 lock() 和 unlock() * reentrantreadwritelock类,读写锁:一种支持一写多读的同步锁,读写分离,分别分配读锁和写锁,在读操作远远高于写操作的环境中可以提高效率 * 互斥规则: * 写--写:互斥,阻塞 * 读--写:互斥,阻塞 * 读--读:不互斥,不阻塞 * */public class lockdemo { public static void main(string[] args) { executorservice es = executors.newfixedthreadpool(20); student s = new student();// reentrantlock rlock = new reentrantlock(); // 用reentrantlock加锁运行时间20008ms reentrantreadwritelock rwlock = new reentrantreadwritelock(); // 用读写锁分别对读写任务加锁运行时间3003ms readlock rl = rwlock.readlock(); writelock wl = rwlock.writelock(); // 写任务 callable writetask = new callable() { @override public object call() throws exception {// rlock.lock(); wl.lock(); try { thread.sleep(1000); s.setvalue(100); }finally {// rlock.unlock(); wl.unlock(); } return null; }}; // 读任务 callable readtask = new callable() { @override public object call() throws exception {// rlock.lock(); rl.lock(); try { thread.sleep(1000); s.getvalue(); }finally {// rlock.unlock(); rl.unlock(); } return null; }}; // 开始时间 long start = system.currenttimemillis(); for(int i=0;i<2;i++) { // 写任务执行 2 次 es.submit(writetask); } for(int i=0;i,v>,v>,v>,v>,v>,v>,v>,v> 2.2.2 copyonwritearraylist 线程安全的arraylist 读写分离,写加锁,读没锁,读写之间不互斥 使用方法与arraylist无异 2.2.3 copyonwritearrayset 基于 copyonwritearraylist 2.2.4 concurrenthashmap 初始容量默认为16段(segment),采用分段锁设计 不对整个map加锁,只对每个segment加锁 当多个对象访问同个segment才会互斥
陕西移动携手博世力士乐打造出了陕西省首个5G智慧工厂
基于Arduino实现的物联网压力监控设备
深入而全面:FPGA学习之独立按键检测
AMD:推出嵌入式处理器,计划进军嵌入式处理器新时代
中国移动推出5G远程双师课堂应用,推动教育均衡发展
Java线程学习基础详解
机器人码垛系统组成部分及要求
iphone8即将来袭!黄牛放弃华为小米,全面抢购苹果iPhone8!
pt100温度传感器怎么接plc(PT100的原理)
十进制计数管
苹果宣布秋季发布会将于9月7日举行
5G/4G工业路由器模块授时以及电力FTU/DTU的场景应用)
选热门品牌扩展产品就选创基USB-C集线器
FPGA的基本组成和设计流程
韩国发布全球首台基于区块链技术的电脑_三角形主机
区块链技术如何改变广告领域
泰捷WE30C电视盒子的质量怎么样,谈谈使用后的感受
罗技推出首款垂直鼠标 定价约合人民币690元
大普通信推出车规级高精度RTC--INS5A8900 &amp; INS5A8804
电阻+并联开关软启动电路设计