——- android培训、java培训、期待与您交流! ———
7、多线程
7.1 概念
总览
一个程序同时执行多个任务。通常,每一个任务称为一个线程(thread),它是线程控制的简称。可以同时运行一个以上线程的程序称为:多线程程序(multithreaded)。
多进程与多线程有哪些区别呢?
本质的区别在于每个进程拥有自己的一整套变量,而线程则共享数据。
进程:正在进行中的程序(直译)。
线程:进程中一个负责程序执行的控制单元(执行路径)。
P.S.
1、一个进程中可以有多个执行路径,称之为多线程。
2、一个进程中至少要有一个线程。
3、开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
其实,多个应用程序同时执行都是CPU在做着快速的切换完成的。这个切换是随机的。CPU的切换是需要花费时间的,从而导致了效率的降低。
JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
1. 执行main函数的线程,该线程的任务代码都定义在main函数中。
2. 负责垃圾回收的线程。
线程状态
线程可以有如下6种状态:
• New(新生)
• Runnable(可运行)
• Blocked(被阻塞)
• Waiting(等待)
• Timed waiting(计时等待)
• Terminated(被终止)
1.1、新生线程
当用new操作符创建一个新线程时,如new Thread(r),**该线程还没有开始运行。**这意味着
它的状态是new。当一个线程处于新生状态时,程序还没有开始运行线程中的代码。在线程运
行之前还有一些簿记工作要做。
1.2、可运行线程
一旦调用start方法,线程处于runnable状态。一个可运行的线程可能正在运行也可能没有
运行,这取决于操作系统给线程提供运行的时间。(Java的规范说明没有将它作为一个单独状
态。一个正在运行中的线程仍然处于可运行状态。)
记住,在任何给定时刻,一个可运行的线程可能正在运行也可能没有运行(这就是为什么
将这个状态称为可运行而不是运行)。
1.3、 被阻塞线程和等待线程
当线程处于被阻塞或等待状态时,它暂时不活动。它不运行任何代码且消耗最少的资源。
直到线程调度器重新激活它。细节取决于它是怎样达到非活动状态的。
1.4 、被终止的线程
线程因如下两个原因之一而被终止:
• 因为run方法正常退出而自然死亡。
• 因为一个没有捕获的异常终止了run方法而意外死亡。
特别是,可以调用线程的stop方法杀死一个线程。该方法抛出ThreadDeath 错误对象,由此
杀死线程。但是,stop方法已过时,不要在自己的代码中调用它。
2、线程属性
线程的各种属性,包括:线程优先级、守护线程、线程组以及处理未捕获
异常的处理器。
1、线程优先级
在Java程序设计语言中,每一个线程有一个优先级。默认情况下,一个线程继承它的父线
程的优先级。可以用setPriority方法提高或降低任何一个线程的优先级。可以将优先级设置为
在MIN_PRIORITY(在Thread类中定义为1)与MAX_PRIORITY(定义为10)之间的任何值。
NORM_PRIORITY被定义为5。
注意
果确实要使用优先级,应该避免初学者常犯的一个错误。如果有几个高优先级
的线程没有进入非活动状态,低优先级的线程可能永远也不能执行。每当调度器决定运
行一个新线程时,首先会在具有高优先级的线程中进行选择,尽管这样会使低优先级的
线程完全饿死。
2、守护线程
可以通过调用 t.SetDaemon(true);
将线程转换为守护线程(daemon thread)。这样一个线程没有什么神奇。守护线程的惟一用途
是为其他线程提供服务。计时线程就是一个例子,它定时地发送“时间嘀嗒”信号给其他线程
或清空过时的高速缓存项的线程。当只剩下守护线程时,虚拟机就退出了,
由于如果只剩下守护线程,就没必要继续运行程序了。
守护线程有时会被初学者错误地使用,他们不打算考虑关机(shutdown)动作。但是,
这是很危险的。守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚
至在一个操作的中间发生中断。
3、未捕获异常处理器
线程的run方法不能抛出任何被检测的异常, 但是,不被检测的异常会导致线程终止。
在这种情况下,线程就死亡了。
4、线程组
线程组是一个可以统一管理的线程集合。默认情况下,创建的所有线程属于相同
的线程组,但是,也可能会建立其他的组
7.2、创建多线程
例1:通过实现 Runnnable 接口 创建多线程
package XianChen;
/**
*
* 1. 定义一个类实现Runnable接口
* 2. 覆盖接口中的run方法。
* 3. 通过Thread的引用对象创建线程。
* 4. 调用Thread 的方法 start()启动线程
* 5. 系统并调用线程的任务run方法执行。
*
*/
public class LiZi {
public static void main(String[] args) {
// 开启新线程 将要运行的 对象传入
Thread t1 = new Thread(new XianCh());
Thread t2 = new Thread(new XianCh());
// 启动新线程
t1.start();
t2.start();
}
}
class XianCh implements Runnable{
// 覆写 抽象方法 run()
public void run() {
for(int x=0;x<100;x++)
System.out.println(Thread.currentThread().getName()+"……"+x);
}
}
/*
* 运行结果:
Thread-1……1
Thread-0……2
Thread-1……2
Thread-0……3
Thread-1……3
Thread-0……4
Thread-1……4
Thread-0……5
Thread-1……5
*/
例2:通过 继承Thread类或其子类创建多线程
package XianChen;
/**
*
* 1. 定义一个类继承Thread类。
* 2. 覆盖Thread类中的run方法。
* 3. 直接创建Thread的子类对象创建线程。
* 4. 调用start方法开启线程并调用线程的任务run方法执行。
*
*/
public class LiZi {
public static void main(String[] args) {
XianCh2 x1 = new XianCh2();
XianCh2 x2 = new XianCh2();
x1.start();
x2.start();
}
}
class XianCh implements Runnable{
// 覆写 抽象方法 run()
public void run() {
for(int x=0;x<100;x++)
System.out.println(Thread.currentThread().getName()+"……"+x);
}
}
class XianCh2 extends Thread{
// 覆写 抽象方法 run()
public void run() {
for(int x=0;x<100;x++)
System.out.println(Thread.currentThread().getName()+"……"+x);
}
}
/**
* 运行结果
Thread-0……0
Thread-1……0
Thread-0……1
Thread-1……1
Thread-0……2
Thread-1……2
Thread-0……3
Thread-1……3
Thread-1……4
*/
注:
1、可以通过Thread的getName方法获取线程的名称,名称格式:Thread-编号(从0开始)。
2、Thread在创建的时候,该Thread就已经命名了。
3、run方法就是封装自定义线程运行任务的函数,run方法中定义的就是线程要运行的任务代码。
4、开启线程是为了运行指定代码,所以需要实现Runnable(),并复写run方法,将运行的代码定义在run方法中即可。
5、一个类继承Thread,不适合资源共享。但是如果实现了Runable,则能实现资源共享。
6、public void start()Java 虚拟机调用该线程的 run 方法。
7.3、等待唤醒
1、涉及的方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
notifyAll:唤醒的是线程池中的所有线程
注意:
1:这些方法都需要定义在同步中。
2:因为这些方法必须要标示所属的锁。
你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。
3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。
2、wait和sleep区别:
分析这两个方法:从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但是不释放锁。
3、代码演示
package XianChen;
public class LiZi{
public static void main(String[] args) {
Rec r = new Rec();
// 开启新线程 将要运行的 对象传入
Sell s= new Sell(r);
Get g = new Get(r);
Thread t1 = new Thread(s);
Thread t2 = new Thread(g);
// 启动新线程
t1.start();
t2.start();
new LiZi().run();
}
public synchronized void run() {
while(true){
try {
System.out.println(Thread.currentThread().getName()+"我要sleep 1s ……");
// sleep 是半阻塞方式,等待一段时候会自动启动,不需要唤醒
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"我醒了……");
System.out.println(Thread.currentThread().getName()+"我要等待1s ===");
// wait(time) 类似 sleep 在等待一段时间未被唤醒会自动启动
wait(1000);
System.out.println(Thread.currentThread().getName()+"等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Rec {
// 定义锁对象
private Object objA=new Object();
public void sell() {
synchronized(objA){
try {
System.out.println(Thread.currentThread().getName()+"我将要开始等待");
// 唤醒当前对象其他等待的线程
// 用当前锁的对象 调用 线程方法,否则会爆IllegalMonitorStateException 异常
objA.notify();
Thread.sleep(1000);
// 自身线程等待
objA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"我被唤醒");
}
}
public void get(){
synchronized(objA){
try {
System.out.println(Thread.currentThread().getName()+"我将要开始等待");
// 唤醒当前对象其他等待的线程
objA.notify();
Thread.sleep(1000);
// 自身线程等待
objA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"我被唤醒");
}
}
}
class Sell implements Runnable{
private Rec r=null;
Sell(Rec r){
this.r = r;
}
public void run() {
System.out.println(Thread.currentThread().getName()+"run");
while(true){
r.sell();
}
}
}
class Get implements Runnable{
private Rec r=null;
Get(Rec r){
this.r=r;
}
public void run() {
System.out.println(Thread.currentThread().getName()+"run");
while(true){
r.get();
}
}
}
/*
运行结果:
Thread-0run
main我要sleep 1s ……
Thread-1run
Thread-0我将要开始等待
main我醒了……
Thread-1我将要开始等待
main我要等待1s ===
main等待结束
Thread-0我被唤醒
Thread-0我将要开始等待
main我要sleep 1s ……
Thread-1我被唤醒
Thread-1我将要开始等待
main我醒了……
main我要等待1s ===
main等待结束
Thread-0我被唤醒
Thread-0我将要开始等待
*/
4、注意事项:
1、wait 、notify、notifyAll b必须与锁 配套使用,不能单独存在
否则会报:IllegalMonitorStateException 异常
2、wait()可以被当前对象,或者锁对象调用,但要解锁必须用相同对象才行
比如: objA .wait(); 必须用 objA.natify() 或objA.notifyAll()解锁
3、notify(),唤醒的是当前对象所调用的线程池中,最早wait()的线程,具有不确定性。
4、notifyAll(),唤醒的是当前对象所调用的线程池中所有的线程
6、Lock
概述:
解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。
之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。
而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法: await()、signal()、signalAll()体现新版本对象的好处。
涉及内容包括
ReentrankLock():创建一个 ReentrantLock 的实例
await(): 所在线程等待
signal(): 配合Condition,唤醒指定的单个线程
signalAll(): 配合Condition,唤醒所有指定线程
Lock(): 获取锁
unLock(): 释放锁
newCondition: 返回绑定到此 Lock 实例的新 Condition 实例。
ReentrantLock示范
package XianChen;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LiZi {
// 锁
Lock lockdemo = new ReentrantLock();
// 锁状态
Condition con_0 =lockdemo.newCondition();
Condition con_1 =lockdemo.newCondition();
Boolean flag =true;
// 建立唯一数据对象
ArrayList<String> li = new ArrayList<String>();
// 利用内部类的形式,存放线程代码
Runnable xian_0= new Runnable(){
public void run() {
while(flag){
// 上锁
lockdemo.lock();
try{
// 将字符串存入指定集合
String str = li.size()+"by"+Thread.currentThread().getName()+"\r\n";
Thread.sleep(100);
li.add(str);
// 判断集合容量,超出时停止
if(li.size()>=10){
flag =false;
System.out.println(li.toString());
// 停止前,唤醒等待所有线程
con_1.signalAll();
// con_0.signalAll();
return;
}
// 唤醒指定状态的线程
con_1.signal();
// 挂起自己
con_0.await();
}catch(Exception e){}
//最终动作
finally{
lockdemo.unlock();
}
}
}
};
Runnable xian_1= new Runnable(){
public void run() {
while(flag){
lockdemo.lock();
try{
String str = li.size()+"by"+Thread.currentThread().getName()+"\r\n";
Thread.sleep(100);
li.add(str);
if(li.size()>=10){
flag =false;
System.out.println(li.toString());
con_0.signalAll();
return;
}
con_0.signal();
con_1.await();
}catch(Exception e){}
finally{
lockdemo.unlock();
}
}
}
};
public static void main(String[] args) {
new LiZi().test();
}
public void test(){
// 开启新线程
new Thread(xian_0).start();
new Thread(xian_1).start();
}
}
/*
输出结果:
[0byThread-0
, 1byThread-1
, 2byThread-0
, 3byThread-1
, 4byThread-0
, 5byThread-1
, 6byThread-0
, 7byThread-1
, 8byThread-0
, 9byThread-1
]
*/
ReentrantReadWriteLock 示范(转)
package XianChen;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Lockers
* 在多线程编程里面一个重要的概念是锁定,如果一个资源是多个线程共享的,为了保证数据的完整性,
* 在进行事务性操作时需要将共享资源锁定,这样可以保证在做事务性操作时只有一个线程能对资源进行操作,
* 从而保证数据的完整性。在5.0以前,锁定的功能是由Synchronized关键字来实现的。
*
*/
public class LockDemo{
public static void main(String[] args) throws Exception{
Lockers.testLockTest();
System.out.println("---------------------");
Lockers.testReadWriteLockTest();
}
}
class Lockers {
/**
* 测试Lock的使用。在方法中使用Lock,可以避免使用Synchronized关键字。
*/
public static class LockTest {
Lock lock = new ReentrantLock();// 锁
double value = 0d; // 值
int addtimes = 0;
/**
* 增加value的值,该方法的操作分为2步,而且相互依赖,必须实现在一个事务中
* 所以该方法必须同步,以前的做法是在方法声明中使用Synchronized关键字。
*/
public void addValue(double v) {
lock.lock();// 取得锁
System.out.println("LockTest to addValue: " + v + " "
+ new SimpleDateFormat("hh:mm:ss").format(System.currentTimeMillis()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
this.value += v;
this.addtimes++;
lock.unlock();// 释放锁
}
public double getValue() {
return this.value;
}
}
public static void testLockTest() throws Exception{
final LockTest lockTest = new LockTest();
// 新建任务1,调用lockTest的addValue方法
Runnable task1 = new Runnable(){
public void run(){
lockTest.addValue(55.55);
}
};
// 新建任务2,调用lockTest的getValue方法
Runnable task2 = new Runnable(){
public void run(){
System.out.println("value: " + lockTest.getValue());
}
};
// 新建任务执行服务
ExecutorService cachedService = Executors.newCachedThreadPool();
Future future = null;
// 同时执行任务1三次,由于addValue方法使用了锁机制,所以,实质上会顺序执行
for (int i=0; i<3; i++){
future = cachedService.submit(task1);
}
// 等待最后一个任务1被执行完
future.get();
// 再执行任务2,输出结果
future = cachedService.submit(task2);
// 等待任务2执行完后,关闭任务执行服务
future.get();
cachedService.shutdownNow();
}
/**
* ReadWriteLock内置两个Lock,一个是读的Lock,一个是写的Lock。
* 多个线程可同时得到读的Lock,但只有一个线程能得到写的Lock,
* 而且写的Lock被锁定后,任何线程都不能得到Lock。ReadWriteLock提供的方法有:
* readLock(): 返回一个读的lock
* writeLock(): 返回一个写的lock, 此lock是排他的。
* ReadWriteLockTest很适合处理类似文件的读写操作。
* 读的时候可以同时读,但不能写;写的时候既不能同时写也不能读。
*/
public static class ReadWriteLockTest{
// 锁
ReadWriteLock lock = new ReentrantReadWriteLock();
// 值
double value = 0d;
int addtimes = 0;
/**
* 增加value的值,不允许多个线程同时进入该方法
*/
public void addValue(double v) {
// 得到writeLock并锁定
Lock writeLock = lock.writeLock();
writeLock.lock();
System.out.println("ReadWriteLockTest to addValue: " + v + " "
+ new SimpleDateFormat("hh:mm:ss").format(System.currentTimeMillis()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
try {
// 做写的工作
this.value += v;
this.addtimes++;
} finally {
// 释放writeLock锁
writeLock.unlock();
}
}
/**
* 获得信息。当有线程在调用addValue方法时,getInfo得到的信息可能是不正确的。
* 所以,也必须保证该方法在被调用时,没有方法在调用addValue方法。
*/
public String getInfo() {
// 得到readLock并锁定
Lock readLock = lock.readLock();
readLock.lock();
System.out.println("ReadWriteLockTest to getInfo "
+ new SimpleDateFormat("hh:mm:ss").format(System.currentTimeMillis()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
try {
// 做读的工作
return this.value + " : " + this.addtimes;
} finally {
// 释放readLock
readLock.unlock();
}
}
}
public static void testReadWriteLockTest() throws Exception{
final ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
// 新建任务1,调用lockTest的addValue方法
Runnable task_1 = new Runnable(){
public void run(){
readWriteLockTest.addValue(55.55);
}
};
// 新建任务2,调用lockTest的getValue方法
Runnable task_2 = new Runnable(){
public void run(){
System.out.println("info: " + readWriteLockTest.getInfo());
}
};
// 新建任务执行服务
ExecutorService cachedService_1 = Executors.newCachedThreadPool();
Future future_1 = null;
// 同时执行5个任务,其中前2个任务是task_1,后两个任务是task_2
for (int i=0; i<2; i++){
future_1 = cachedService_1.submit(task_1);
}
for (int i=0; i<2; i++){
future_1 = cachedService_1.submit(task_2);
}
// 最后一个任务是task_1
future_1 = cachedService_1.submit(task_1);
// 这5个任务的执行顺序应该是:
// 第一个task_1先执行,第二个task_1再执行;这是因为不能同时写,所以必须等。
// 然后2个task_2同时执行;这是因为在写的时候,就不能读,所以都等待写结束,
// 又因为可以同时读,所以它们同时执行
// 最后一个task_1再执行。这是因为在读的时候,也不能写,所以必须等待读结束后,才能写。
// 等待最后一个task_2被执行完
future_1.get();
cachedService_1.shutdownNow();
}
}
/*
输出结果:
LockTest to addValue: 55.55 05:12:40
LockTest to addValue: 55.55 05:12:41
LockTest to addValue: 55.55 05:12:42
value: 166.64999999999998
---------------------
ReadWriteLockTest to addValue: 55.55 05:12:43
ReadWriteLockTest to addValue: 55.55 05:12:44
ReadWriteLockTest to getInfo 05:12:45
ReadWriteLockTest to getInfo 05:12:45
info: 111.1 : 2
info: 111.1 : 2
ReadWriteLockTest to addValue: 55.55 05:12:46
*/