Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信
1、Java使用Thread类代表线程。
所有的线程对象必须是Thread类或其子类的实例。
当线程继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName()方法返回当前线程的名字,因此可以直接调用getName()方法返回当前线程的名字。
Thread.currentThread():该方法总是返回当前正在执行的线程对象。
2、创建线程方式1:继承Thread类创建线程类
这种方式创建线程的步骤一般为:
1》定义Thread类的子类,并重写该类的run()方法,该方法作为线程的线程执行体。
2》创建Thread子类的实例,即线程对象。
3》调用线程对象的start()方法启动线程。
举个例子:
public class extendsThread extends Thread{ ...
@Override
public void run(){ ...
//do something
System.out.println(this.getName());
} public static void main(String[] agrs){ //创建并启动第一个线程
new extendsThread().start();
//创建并启动第二个线程
new extendsThread().start();
}
}
3、创建线程方式2:实现Runnable接口
这种方式创建线程的步骤一般为:
1》定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2》创建Runnable接口实现类的实例,并将该实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。本质是Thread对象负责执行实现类对象的run()方法体。
3》调用线程对象的start()方法来启动该线程。
举个例子:
public class ImplRunnable implements Runnable{ ...
@Override
public void run(){ ...
//do something
System.out.println(Thread.currentThread().getName());
} public static void main(String agrs){ ...
System.out.println(Thread.currentThread().getName()); ImplRunnable target=new ImplRunnable();
//创建并启动第一个线程
new Thread(target,"Thread1").start();
//创建并启动第二个线程
new Thread(target,"Thread2").start();
}
}
4、使用Callable和Future创建线程:创建有返回值的线程
这种方式创建线程的步骤一般为:
1》创建Callable接口的实现类,并实现call()方法,该方法作为线程执行体,且该方法有返回值。然后创建该实现类的实例。
2》使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
3》使用FutureTask对象作为Thread对象的target创建并启动新线程。
4》调用FutureTask对象的get()方法获取子线程执行结束后的返回值。
举个例子:
public class ImplCallable implements Callable<Integer>{ ...
@Override
public Integer call(){ ...
//do something
System.out.println(Thread.currentThread().getName());
//return a value.
return 1;
} public static void main(String[] agrs) throws InterruptedException, ExecutionException{ ...
//创建并启动一个线程
FutureTask<Integer> target=new FutureTask<Integer>(new ImplCallable());
new Thread(target,"Thread3 with value.").start();
//获取返回值
System.out.println(target.get());
}
}
5、线程的生命周期
线程进入阻塞状态:
线程的死亡:
当主线程结束时,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来之后,它就拥有和主线程相同的地位,它不会受主线程的影响。可以调用线程对象的isAlive()方法,当线程处于就绪、运行、阻塞三种状态时,该方法返回true;当线程处于新建、死亡两种状态时,该方法返回false。不要试图对一个已经死亡的线程调用start()方法使它重新启动,死亡的线程不能再次作为线程执行。
6、控制线程
1》join():线程。Thread的静态方法。
A线程调用B线程的join()方法,A线程将被阻塞,直至B线程执行结束。join()方法有如下三种重载方式:
1>join():等待被join的线程执行完毕。
2>join(long millis):等待被join的线程的事时间最长为millis毫秒,如果millis毫秒被join的线程依然没有结束,则不再等待。
3>join(long millis,int nanos):等待被join的线程的时间最长为millis毫秒加nanos毫秒。(一般计算机硬件、操作系统本身无法做到这么精确)。
2》setDaemon(true):后台线程、守护线程、精灵线程
如果所有的前台线程都死亡了,后台线程也会自动死亡。调用Thread对象的setDaemon(true)方法可以将指定线程设置成后台线程。
Thread还提供了isDaemon()方法,用于判断指定线程是否为后台线程。
前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。
3》sleep():让线程睡眠。Thread的静态方法。
在指定的睡眠时间内,即使系统中没有其他线程可执行,处于sleep()中的线程也不会执行。sleep()方法通常用来暂停线程。
4》yield():线程让步。Thread的静态方法。可以让线程暂停,但不会阻塞线程,线程会进入就绪状态,有可能立即又得到执行。当线程执行yield()方法后,优先级与之相等或者比它高的线程才会得到执行的机会。
比较:
5》改变线程的优先级
每个线程默认的优先级与创建它的父线程的优先级相同。默认情况下,main线程具有普通优先级。
Thread提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其中setPriority(int newPriority)方法的参数可以是一个整数,范围1~10,值越大优先级越高。Thread也提供了三个静态常量设置的值:
7、线程同步:方式(1)synchronized的同步代码块、同步方法;方式(2)同步锁(Lock或ReadWriteLock接口的实现类)。
1》同步代码块:
//其中的obj就是同步监视器。
//线程在执行到下面的代码块的时候,必须先获得对同步监视器的锁定。
//任何时刻只能有一个线程可以获得同步监视器的锁定,同步代码块执
//行结束,同步监视器自动被释放锁定。
sychronized(obj){
...
}
2》同步方法:
使用sychronized修饰的某个方法(可能是实例方法,也可能是静态方法)。
1>修饰实例方法:无需显式指定同步监视器,同步方法的同步监视器就是this,即调用该方法的实例对象。
2>修饰静态方法:。。。
==》注意:sychronized可以修饰代码块、成员方法。但是不能修饰构造器、成员变量等。
==》为了减少线程安全问题带来的性能影响:
1>不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步(可以参考哪些资源会被改变)。
2>如果可变类有两种环境:单线程、多线程环境。可以考虑对该类提供两个版本,在不同的环境中使用不同的版本。
(典型单线程、多线程线程安全版本:StringBuilder(非线程安全版本)是单线程环境下使用;StringBuffer(线程安全版本)在多线程环境下使用)
释放同步监视器的锁定,这个不能显式地控制,一般的时机为:
可大概总结为线程要从同步代码块、同步方法的代码块中退出(被迫或正常执行结束),这时候就会自动释放同步监视器。
3》同步锁(Lock):
Lock对象充当同步对象。
1)Java 5提供了Lock、ReadWriteLock两个根接口,并为:
1>Lock==>ReetrantLock(可重入锁)实现类:
2>ReadWriteLock==>ReetrantReadWriteLock(可重入读写锁)实现类:提供了Writing、ReadingOptimistic、Reading三种锁模式。
可重入锁,指的是一个线程可以对已被加锁的ReetrantLock / ReetrantReadWriteLock对象再次加锁,ReetrantLock / ReetrantReadWriteLock对象会维持一个计数器来追踪lock()方法的嵌套调用。线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。
2)Java 8新增StampedLock类,在大多数场景中可以替代ReetrantReadWriteLock(可重入读写锁)。
常用的锁是ReetrantLock(可重入锁),其一般使用格式:
public class A{
//定义锁对象
private final ReentrantLock lock=new ReentrantLock();
...
public void method(){
//加锁对象
lock.lock();
try{
//需要保证线程同步的代码
...
}
finally{
//释放锁
lock.unlock();
}
}
}
总结:同步代码块、同步方法使用的是与资源竞争相关的、隐式的同步监视器,且强制要求加锁和锁释放要出现在一个块结构中,而且获得了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。
==》死锁:
两个线程相互等待对方释放同步监视器时就会发生死锁,简单的例子A等待B的筷子吃面然后才能空出盘子,但B在等待A的盘子盛面吃了才可以给A筷子,相互之间等待需要的资源,就形成了死锁。出现了死锁,所有线程处于阻塞态,无法继续。
8、线程通信
1》传统的线程通信
Object类提供了wait()、notify()、notifyAll()三个方法,这三个方法并不属于Thread类,而是属于Object类。且三个方法必须由同步监视器对象来调用,可分为两种情况:
关于上面的wait()、notify()、notifyAll()三个方法:
使用举例:
public class A{
...
public sychronized void getMoney(){ try{ if(!flag){ wait();
}
else{ ...
flag=false;
notifyAll();
}
}
catch{
...
}
} public sychronized void setMoney(){ try{ if(!flag){ wait();
}
else{ ...
flag=true;
notifyAll();
}
}
catch{
...
}
}
}
2》使用Condition控制线程通信
这种情况针对不使用sychronized关键字来保证同步(即不是同步代码块或者同步方法的方式),而是使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用上面的三个方法(wait()、notify()、notifyAll())进行线程间通信了。
针对使用Lock对象保证同步的情况,Java提供了Condition类来保持协调。
使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,也可以唤醒其他处于等待的线程。
使用举例:
public class A{
//定义锁对象
private final Lock lock=new ReentrantLock();
//指定Lock对象的对应的Condition
private final Condition cond=lock.new Condition();
...
public void getMoney(){
//加锁
lock.lock();
try{ if(!flag){ cond.await();
}
else{ ...
flag=false;
cond.signalAll();
}
}
catch{
...
}
finally{
//释放锁
lock.unlock();
}
} public void setMoney(){ //加锁
lock.lock();
try{ if(!flag){ cond.await();
}
else{ ...
flag=true;
cond.signalAll();
}
}
catch{
...
}
finally{
//释放锁
lock.unlock();
}
}
}
3》使用阻塞队列(BlockingQueue)控制线程通信
Java 5提供了一个BlockingQueue接口,Queue的子接口,主要用作线程同步工具。特征:当生产者试图往BlockingQueue放入元素时,如果队列已满,则线程被阻塞;如果消费者线程试图从BlockingQueue取元素时,如果队列已空,则线程被阻塞。
。。。详细使用参考相关的API。
9、线程组合未处理的异常
Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信的更多相关文章
- iOS开发UI篇—使用storyboard创建导航控制器以及控制器的生命周期
iOS开发UI篇—使用storyboard创建导航控制器以及控制器的生命周期 一.基本过程 新建一个项目,系统默认的主控制器继承自UIViewController,把主控制器两个文件删掉. 在stor ...
- Java多线程系列--“基础篇”02之 常用的实现多线程的两种方式
概要 本章,我们学习“常用的实现多线程的2种方式”:Thread 和 Runnable.之所以说是常用的,是因为通过还可以通过java.util.concurrent包中的线程池来实现多线程.关于线程 ...
- Java多线程的三种实现方式
java多线程的三种实现方式 一.继承Thread类 二.实现Runnable接口 三.使用ExecutorService, Callable, Future 无论是通过继承Thread类还是实现Ru ...
- Java多线程(三)如何创建线程
点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...
- “全栈2019”Java多线程第三章:创建多线程之实现Runnable接口
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java多线程系列--“基础篇”04之 synchronized关键字
概要 本章,会对synchronized关键字进行介绍.涉及到的内容包括:1. synchronized原理2. synchronized基本规则3. synchronized方法 和 synchro ...
- Java多线程系列--“基础篇”08之 join()
概要 本章,会对Thread中join()方法进行介绍.涉及到的内容包括:1. join()介绍2. join()源码分析(基于JDK1.7.0_40)3. join()示例 转载请注明出处:http ...
- Java多线程系列--“基础篇”09之 interrupt()和线程终止方式
概要 本章,会对线程的interrupt()中断和终止方式进行介绍.涉及到的内容包括:1. interrupt()说明2. 终止线程的方式2.1 终止处于“阻塞状态”的线程2.2 终止处于“运行状态” ...
- Java多线程系列--“基础篇”10之 线程优先级和守护线程
概要 本章,会对守护线程和线程优先级进行介绍.涉及到的内容包括:1. 线程优先级的介绍2. 线程优先级的示例3. 守护线程的示例 转载请注明出处:http://www.cnblogs.com/skyw ...
随机推荐
- JQ的简单使用(基础)——————JQ
JQ基础--JQ的简单使用 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /&g ...
- python 安装opencv及问题解决
正常安装模式 pip install opencv-python==3.4.5.20 pip install opencv-contrib-python==3.4.5.20 -i http://pyp ...
- Puppeteer实现自动登录
Puppeteer是用JS对Chrome Dev Tools的实现,可以用来操作Chrome浏览器,适用于爬虫.自动化等领域. 以下是自己实现自动化登录的代码(基于ES6) const puppete ...
- Taro -- 微信小程序wxParse达到html转换wxml
Taro微信小程序可以用wxParse来达到html转换wxml的效果:https://github.com/NervJS/taro-components-test/blob/master/src/p ...
- Sass:@error
@error 和 @warn.@debug 功能是如出一辙. @mixin error($x){ @if $x < 10 { width: $x * 10px; } @else if $x == ...
- 线程数设置和CPU数的关系
一般说来,大家认为线程池的大小经验值应该这样设置:(其中N为CPU的个数) 如果是CPU密集型应用,则线程池大小设置为N+1 如果是IO密集型应用,则线程池大小设置为2N+1(因为io读数据或者缓存的 ...
- LeetCode--050--Pow(x,n)
实现 pow(x, n) ,即计算 x 的 n 次幂函数. 示例 1: 输入: 2.00000, 10 输出: 1024.00000 示例 2: 输入: 2.10000, 3 输出: 9.26100 ...
- 16 :IDEA快速键
ctrol+z ctrol+shift+z 重做 复制,粘贴,删除,(行操作,光标放在那里就可以操作,不要全选择) 注:特别:查询出来,文件是可以直接编辑的 crtol+F double +shif ...
- 继续写高精!noip2012国王游戏。。。
国王游戏 题目描述: 恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右手上面分别写下一个整数,国王自己也在左.右手上各写一个整数.然后,让这 n 位大臣排成一排,国王 ...
- Ubuntu安装及sshd服务安装,yum安装等总结
vm网络选择自定义.指定的虚拟网络,自动桥连. 1.设置root初始密码 ubuntu安装好后,root初始密码(默认密码)不知道,需要设置.1.先用安装时候的用户登录进入系统2.输入:sudo ...