Java多线程
什么是线程?
线程是相对于进程而言的,通常在计算机中,一个程序就是一个进程,而一个进程中至少有一个或多个的线程来完成该程序相关的功能。
举个不是很恰当的例子:例如乐队表演是一个进程,那么主唱和鼓手和和声等都可以理解为一个线程,他们共同来完成演奏一首曲子的工作。
## 什么是线程的并行和并发?
并行:指的是在同一台机器上,多个CPU同时执行同一段功能代码来完成功能;
并发:指的是在同一台机器上,CPU利用调度算法,以超高的速度切换进程来执行同一段代码来完成功能(让人感觉和并行一样);
理解:(图片源自网络)
## Java中创建线程的3中方法 方法1 - 继承Thread类 ``` Java public class Main extends Thread{
@Override
public void run() {
System.out.println(this.getName());
super.run();
}
public static void main(String[] args) {
System.out.println("主线程" + Thread.currentThread().getName());
new Main().start();
new Main().start();
new Main().start();
/**
* 输出结果:
* 主线程main
* Thread-0
* Thread-1
* Thread-2
*/
}
}
方法2 - 实现Runnable接口
``` Java
public class Main implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
System.out.println("主线程" + Thread.currentThread().getName());
Main main = new Main();
new Thread(main).start();
new Thread(main).start();
new Thread(main).start();
/**
* 输出结果:
* Thread-0
* Thread-1
* Thread-2
*/
}
}
方法3 - 实现Callable接口(jdk 1.5)
public class Main implements Callable<String>{
/**
* 可以抛异常
* 可以有返回值
*/
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return null;
}
public static void main(String[] args) throws Exception {
System.out.println("主线程" + Thread.currentThread().getName());
Main main = new Main();
// 使用FutureTask来包装Callable对象
new Thread(new FutureTask<>(main), "线程0").start();
new Thread(new FutureTask<>(main), "线程1").start();
new Thread(new FutureTask<>(main)).start();
/**
* 输出结果:
* 主线程main
* 线程0
* 线程1
* Thread-0
*/
}
}
总结:
推荐使用Runnable或者Callable,因为在Java中,类只能实现单继承,所以如果使用继承Thread来实现多线程的话,并不利于该类的扩展。
## 线程的状态 新建:刚new出来的Thread对象; 就绪:第一种情况是刚调用start()方法的线程,第二种情况就是阻塞完后的线程,第三种情况主动调用yield()方法; 运行:获取到CPU执行权的线程,处于工作状态; 阻塞: 1. 等待阻塞 -- 运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态; 2. 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态; 3. 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态; 死亡:执行完run方法之后任务结束了或者因异常终止退出了run()方法;
图解:(图片源自网络)
## 终止线程的方法 方法1:使用退出标志,使线程正常退出; 方法2:使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能导致死锁发生); 方法3:使用interrupt方法中断线程;
方法1 -- 标记退出
public class Main extends Thread{
volatile boolean flag = false;
@Override
public void run() {
while( !flag ) { System.out.println("::"); }
System.out.println("线程结束!!!");
}
public static void main(String[] args) throws Exception {
Main a = new Main();
a.start();
//终止操作
a.flag = true;
//a.join(); //多个线程的使用使用join来抢占资源关闭
}
}
方法2 -- stop方法退出
thread.stop(); //不推荐使用
方法3 -- interrupt方法退出
使用该方法终止线程需要符合以下其中一种状态:
1. 终止线程处于阻塞状态,如sleep(),在该状态下可以使用interrupt()方法终止线程,且在终止的时候sleep会抛出一个InterruptedException异常(注意:如果线程的阻塞是I/O阻塞,需要先close(),后interrupt()才生效,否则不生效)
2. 使用isInterrupted()方法检查
interrupt()方法:
public class Main extends Thread{
volatile boolean flag = false;
@Override
public void run() {
try {
System.in.read(); // I/O阻塞:当通道还没关闭时,interrupt()不生效,解决方法是关闭通道close()
// sleep(500000); // sleep阻塞:interrupt()生效
} catch (Exception e) {
System.out.println(e.getMessage());
return;
}
while( true ) {System.out.println("::");}
}
public static void main(String[] args) throws Exception {
Main a = new Main();
a.start();
a.interrupt();
a.join();
System.out.println("线程已经退出!");
}
}
isInterrupted()方法:(结合interrupt()方法使用)
public class Main extends Thread{
volatile boolean flag = false;
@Override
public void run() {
while( true ) {
System.out.println("::");
if( Thread.currentThread().isInterrupted() ) {
System.out.println(Thread.currentThread().getName() + "退出");
return;
}
}
}
public static void main(String[] args) throws Exception {
Main a = new Main();
a.start();
a.interrupt();
// a.join();
}
}
总结:
interrupt()和isInterrupted()实质底层调用的都是同一个方法,该方法是一个本地方法(native修饰)
源码如下:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
//本地方法
private native boolean isInterrupted(boolean ClearInterrupted);
线程的yield()和join()方法
方法说明:
void join():先让该线程执行,直到终止;
void join(long millis):先让该线程执行,指定可以执行的时间;
static void yield():暂停当前正在执行的线程对象,并执行其他线程;
join()方法例子:
public class Main extends Thread{
private String name;
public Main(String name) {
this.name = name;
}
@Override
public void run() {
synchronized(this) {
for(int i=0; i<=20; i++) {
System.out.println( name + "正在执行任务..." + i );
}
}
}
public static void main(String[] args) throws InterruptedException {
Main main1 = new Main("线程1");
Main main2 = new Main("线程2");
Main main3 = new Main("线程3");
main1.start();
main1.join(); //当main1.join();不管运行多少次都是main1先完成任务其他线程才能工作
main2.start();
main3.start();
}
}
yield()方法例子:
public class Main extends Thread{
private String name;
public Main(String name) {
this.name = name;
}
@Override
public void run() {
synchronized(this) {
for(int i=0; i<=20; i++) {
System.out.println( name + "正在执行任务..." + i );
}
}
}
public static void main(String[] args) throws Exception {
Main main1 = new Main("线程1");
Main main2 = new Main("线程2");
Main main3 = new Main("线程3");
main1.start();
main1.yield(); //礼让,注意:yield()后不一定该线程就不会执行,只是在调用的时候让步一下
main2.start();
main3.start();
}
}
线程的优先级
线程的优先级,从字面上就很好理解,就是线程执行的优先权,权力越大,执行的频度就越高,但还是要注意的是,这只是理论的情况,再次强调CPU抢占式任务调度。
什么是抢占式和非抢占式:http://blog.csdn.net/u013176681/article/details/39256191
以下是Thread类为线程提供的3个优先级别:
public static final int MAX_PRIORITY : 值为10
public static final int MIN_PRIORITY : 值为1
public static final int NORM_PRIORITY : 值为5
使用例子:
public class Main extends Thread{
private String name;
public Main(String name) {
this.name = name;
}
@Override
public void run() {
synchronized(this) {
for(int i=0; i<=20; i++) {
System.out.println( name + "正在执行任务..." + i );
}
}
}
public static void main(String[] args) throws Exception {
Main main1 = new Main("线程1");
Main main2 = new Main("线程2");
Main main3 = new Main("线程3");
//那么理论上,执行的优先级是 main1 > main2 >main3 当然这只是理论,但实际执行不一定
main1.start();
main1.setPriority(Thread.MAX_PRIORITY);
main2.start();
main2.setPriority(Thread.NORM_PRIORITY);
main3.start();
main1.setPriority(Thread.MIN_PRIORITY);
}
}
## 线程同步 ### 什么是线程同步?
线程同步,指的是在多个线程同时操作数据的时候能够保持数据的一致性和准确性;
通常线程同步问题会出现在非原子操作中,如 i++ , --i 这种非原子操作,线程如果不同步那么操作的数据就会不一致导致计算结果出错;
注意:
1. 使用锁,锁锁住的是一个对象而不是代码块;
2. 当synchronized中抛出异常,锁会被释放(当线程抛出异常,一般要做的事就是对事务的回滚);
### 解决线程同步的方法
方法1:使用synchronized方法
public class Main implements Runnable{
private int i = 2000;
//同步方法:这是锁是this
public synchronized void show() {
while( true ) {
try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
if( i<=0 ) { //如果小于等于0就退出
return ;
}
System.out.println(Thread.currentThread().getName() + "::" + i-- );
}
}
@Override
public void run() {
show();
}
public static void main(String[] args) throws Exception {
Main main = new Main();
Thread a = new Thread(main);
Thread b = new Thread(main);
Thread c = new Thread(main);
Thread d = new Thread(main);
a.start();
b.start();
b.setPriority(Thread.MAX_PRIORITY);
c.start();
c.setPriority(Thread.NORM_PRIORITY);
d.start();
d.setPriority(Thread.MAX_PRIORITY);
}
}
方法2:使用synchronized代码块
public class Main implements Runnable{
private int i = 2000;
public void show() {
while( true ) {
try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
if( i<=0 ) { //如果小于等于0就退出
return ;
}
System.out.println(Thread.currentThread().getName() + "::" + i-- );
}
}
//同步代码块
@Override
public void run() {
//锁是字节码文件对象
synchronized( Main.class ) {
show();
}
}
public static void main(String[] args) throws Exception {
Main main = new Main();
Thread a = new Thread(main);
Thread b = new Thread(main);
Thread c = new Thread(main);
Thread d = new Thread(main);
a.start();
b.start();
b.setPriority(Thread.MAX_PRIORITY);
c.start();
c.setPriority(Thread.NORM_PRIORITY);
d.start();
d.setPriority(Thread.MAX_PRIORITY);
}
}
方法3:显示锁ReentrantLock - JDK 1.5
public class Main implements Runnable {
//显示锁
static final Lock lock = new ReentrantLock();
private int i = 200;
public void show() {
while( true ) {
//注意:上锁和解锁要在while内 否则出现IllegalMonitorStateException异常
lock.lock(); //上锁
try {
Thread.sleep(50);
if (i <= 0) return;
System.out.println( Thread.currentThread().getName() + "::" + i--);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //解锁
}
}
}
@Override
public void run() {
show();
}
public static void main(String[] args) throws Exception {
Main main1 = new Main();
Thread a = new Thread(main1);
Thread b = new Thread(main1);
Thread c = new Thread(main1);
// 开启线程
a.start();
b.start();
c.start();
}
}
synchronized和ReentrantLock区别
这两种方式最大区别就是对于synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现;而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成 -- 来自CSDN博主 记忆力不好
注意:新版本 JVM对synchronized进行了内部的优化,所以推荐使用synchronized(减少获取锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁)可以参考:http://blog.csdn.net/wodewutai17quiet/article/details/78187386
### 类锁和对象锁 类锁:当使用synchronized修饰静态方法时或者使用 Class对象锁的时候,就会使用类锁,因为静态的方法中是没有this引用的,那么这时锁定的就是一个类; 对象锁:当使用synchronized修饰非静态方法或者使用synchronized(非class对象)代码块时,就会使用对象锁,该锁对当前对象生效;
类锁例子:
public class Main implements Runnable{
private static int i = 20;
//类锁锁定
public static synchronized void show() {
/* 等同于synchronized(Main.class){....} */
while( true ) {
try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
if( i<=0 ) { //如果小于等于0就退出
return ;
}
System.out.println(Thread.currentThread().getName() + "::" + i-- );
}
}
@Override
public void run() {
show();
}
public static void main(String[] args) throws Exception {
Main main1 = new Main();
Main main2 = new Main();
Main main3 = new Main();
Thread a = new Thread(main1);
Thread b = new Thread(main2);
Thread c = new Thread(main3);
//开启线程
a.start();
b.start();
b.setPriority(Thread.MAX_PRIORITY);
c.start();
c.setPriority(Thread.NORM_PRIORITY);
}
}
对象锁例子:
public class Main implements Runnable{
private int i = 20;
//对象锁锁定
public synchronized void show() {
while( true ) {
try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
if( i<=0 ) { //如果小于等于0就退出
return ;
}
System.out.println(Thread.currentThread().getName() + "::" + i-- );
}
}
@Override
public void run() {
show();
}
public static void main(String[] args) throws Exception {
Main main1 = new Main();
Thread a = new Thread(main1);
Thread b = new Thread(main1);
Thread c = new Thread(main1);
//开启线程
a.start();
b.start();
b.setPriority(Thread.MAX_PRIORITY);
c.start();
c.setPriority(Thread.NORM_PRIORITY);
}
}
synchronized和volatile
synchronized:既能保证共享资源 线程的可见性 又可以保证共享资源的 原子性;
volatile:只能保证共享资源对现成的可见性,不能保证原子性,即volatile不能修饰 i++ 这种非原子性操作(当线程取volatile修饰的变量的时候,会从共享内存中取最新的数据);
区别:
volatile不需要加锁,比synchronized更加轻量,效率更高,原因是不会导致线程的阻塞,但并不能保证操作的原子性。
## 死锁 ### 什么是死锁? 其实死锁指的是一种现象;即线程之间锁的相互等待,导致线程停止工作;
死锁例子
public class Main2 {
public static void main(String[] args) {
DeadLock lock = new DeadLock();
T1 t1 = new T1(lock); //m1方法
T2 t2 = new T2(lock); //m2方法
new Thread( t1 ).start();
new Thread( t2 ).start();
}
}
class T1 implements Runnable{
private DeadLock lock;
public T1(DeadLock lock) {
this.lock = lock;
}
@Override
public void run() {
lock.m1();
}
}
class T2 implements Runnable{
private DeadLock lock;
public T2(DeadLock lock) {
this.lock = lock;
}
@Override
public void run() {
lock.m2();
}
}
class DeadLock {
//锁
private static Object o1 = new Object();
private static Object o2 = new Object();
public void m1() {
synchronized(o1) {
System.out.println("o1");
synchronized(o2) {
System.out.println("o2");
}
}
}
public void m2() {
synchronized(o2) {
System.out.println("o2");
synchronized(o1) {
System.out.println("o1");
}
}
}
}
## 后台线程 ### 什么是后台线程? 后台线程也称作精灵线程,是一种为前台进程提供服务的进程,后台线程并不是不可或缺的线程,即实质可有可无。 举个通俗而不恰当的例子,就好比古装电视剧,皇上出巡,身边的侍卫一样,这里皇上就是主进程,侍卫就是后台进程,只要环境是安全的,就算士兵死不在也是没有问题的,因为并不是必要的; 注意:后台线程只在前台线程存在的时候才存在,即如果前台线程都结束了,后台线程就会停止
后台进程的基本使用
public class Main extends Thread{
@Override
public void run() {
for(int i=0; i<10; i++) {
try { Thread.sleep( 200 ); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println( "我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: " + i);
}
}
public static void main(String[] args) {
//精灵线程
Deamon deamon = new Deamon();
Thread deamonThread = new Thread( deamon );
deamonThread.setDaemon( true ); //精灵线程必须在线程开始之前设置
deamonThread.start();
//主线程
Main main = new Main();
Thread mainThread = new Thread( main );
mainThread.start();
}
}
//精灵进程
class Deamon implements Runnable{
@Override
public void run() {
while(true) {
try { Thread.sleep( 200 ); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("我是精灵线程,我为大佬呐喊 666!!!");
}
}
}
输出结果:(前台进程结束,精灵线程就会被JVM停止)
我是精灵线程,我为大佬呐喊 666!!!
我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 0
我是精灵线程,我为大佬呐喊 666!!!
我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 1
我是精灵线程,我为大佬呐喊 666!!!
我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 2
我是精灵线程,我为大佬呐喊 666!!!
我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 3
我是精灵线程,我为大佬呐喊 666!!!
我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 4
我是精灵线程,我为大佬呐喊 666!!!
我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 5
我是精灵线程,我为大佬呐喊 666!!!
我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 6
我是精灵线程,我为大佬呐喊 666!!!
我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 7
我是精灵线程,我为大佬呐喊 666!!!
我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 8
我是精灵线程,我为大佬呐喊 666!!!
我是前台线程,我们上前杀敌,后台线程请为我们呐喊助威!:: 9
## 线程的异常捕获 步骤1:实现UncaughtExceptionHandler线程异常处理器接口 步骤2:调用thread.setUncaughtExceptionHandler( 异常处理器实例对象 );
例子:
public class Main extends Thread{
@Override
public void run() {
throw new RuntimeException();
}
public static void main(String[] args) {
Main main = new Main();
Thread mainThread = new Thread( main );
mainThread.setUncaughtExceptionHandler( new myUncaughtExceptionHandler() );
mainThread.start();
}
}
//线程异常处理器
class myUncaughtExceptionHandler implements UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("出现异常的线程 :: " + t.getName() );
System.out.println("出现的异常为 :: " + e );
}
}
## 线程通信 ### 什么是线程通信? 可以简单理解为线程与线程之间的协作。即什么是线程的通信,就是线程之间是如何协作玩完成任务的;
线程通信常用的方法
第一组来自于Object类(推荐):
void notify():唤醒正在等待的一个线程,注意:不一定是等待最久的哪一个(CPU的抢占式)
void notifyAll():唤醒所有等待的线程
void wait():让线程进入阻塞状态(等待阻塞)
void wait(long timeout):让线程进入阻塞状态(等待阻塞),时间为timeout,超时后就会进入就绪状态
第二组来自于Condition类:
void await():相当于wait()
void signal():相当于notify()
void signalAll():相当于notifyAll()
线程通信例子 -- 生产者与消费者
//商品Computer
class Computer {
private int count = 0; //电脑的台数
private boolean flag = true; //是否生产标记
//生成方法
public synchronized void add() {
while(!flag) { //生产完进入等待
try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
++count;
System.out.println(Thread.currentThread().getName() + "生产者在生产::" + count + "++++++++++++++++++++++++");
flag = false; //生产完
this.notifyAll(); //唤醒等待的消费者
}
//消费方法
public synchronized void remove() {
while(flag) { //消费完进入消费
try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "消费者在消费::" + --count + "---");
flag = true; //消费完
this.notifyAll(); //唤醒等待的生产者
}
}
//生产者Producer
class Producer implements Runnable {
private Computer com;
public Producer(Computer com) { this.com = com; }
@Override
public void run() {
while(true)
com.add();
}
}
//消费产者Consumer
class Consumer implements Runnable {
private Computer com;
public Consumer(Computer com) { this.com = com; }
@Override
public void run() {
while(true)
com.remove();
}
}
//测试类
public class Main {
public static void main(String[] args) throws Exception {
Computer com = new Computer();
Producer producer = new Producer(com);
Consumer consumer = new Consumer(com);
//3个线程生产
new Thread(producer).start();
new Thread(producer).start();
new Thread(producer).start();
//5个线程消费
new Thread(consumer).start();
new Thread(consumer).start();
new Thread(consumer).start();
new Thread(consumer).start();
new Thread(consumer).start();
}
}
以上是一种比较低级的解决方式,但是又是最基础。如果在真正解决生产和消费这种问题的时候,可以常识使用BlockingQueue接口,该接口的实现例如:
- ArrayBlockingQueue:有界队列,固定大小,填满后会阻塞,如果当该队列为空时,消费群获取,那么队列就会挂起消费者;
- LinkedBlockingQueue:无界队列;
使用例子:
//商品Computer
class Computer {
int count = 1;
BlockingQueue<Integer> bq = null;
public Computer(BlockingQueue<Integer> bq) {
this.bq = bq;
}
//生成方法
public void add() {
try {
Thread.sleep(300);
bq.put( count++ );
} catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "生产者才生产:::"+ count + "+++");
}
//消费方法
public void remove() {
Integer take = 0;
try {
Thread.sleep(300);
take = bq.take();
} catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "消费者在消费:::"+ take + "-----------------------");
}
}
//生产者Producer
class Producer implements Runnable {
private volatile boolean flag = true; //默认运行
private Computer com;
public Producer(Computer com) { this.com = com; }
@Override
public void run() {
while(flag)
com.add();
}
//停止生产方法
public void stop() {
this.flag = false;
}
}
//消费产者Consumer
class Consumer implements Runnable {
private Computer com;
public Consumer(Computer com) { this.com = com; }
@Override
public void run() {
while(true)
com.remove();
}
}
//测试类
public class Main {
public static void main(String[] args) throws Exception {
BlockingQueue<Integer> bq = new ArrayBlockingQueue<>(1); //队列的容量,0开始
Computer com = new Computer(bq);
Producer producer = new Producer(com);
Consumer consumer = new Consumer(com);
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); //用多少创建多少
//3个线程生产
newCachedThreadPool.execute(producer);
newCachedThreadPool.execute(producer);
newCachedThreadPool.execute(producer);
//5个线程消费
newCachedThreadPool.execute(consumer);
newCachedThreadPool.execute(consumer);
newCachedThreadPool.execute(consumer);
newCachedThreadPool.execute(consumer);
newCachedThreadPool.execute(consumer);
// producer.stop();
newCachedThreadPool.shutdown(); //关闭线程池
}
}
## 线程池 ### 什么是线程池? 线程池,可以理解为一个用于存放线程的地方。 用到线程的时候就到线程池中取,所以并不需要自己手动创建,而线程完成任务后会自动返回线程池,等待下一个任务的到来(也有存活被设置闲置时间的线程,即线程闲置一定时间自动关闭)。 需要知道的是创建线程是消耗系统资源的,那么当线程并发很高的时候,就会使系统性能急剧下降,甚至崩溃。 所以线程池就解决了这一点,线程一开始就会创建好指定数量的线程,存放在池中供任务使用,线程池对线程的创建数量有所限制,因此并不会出现以上的情况;