JAVA多线程笔记

JAVA多线程笔记

线程的基本概念
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

线程的状态
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

  1. 新建状态(New):新创建了一个线程对象。
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
    (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
  5. 结束状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

Java中多线程的实现

在java中要想实现多线程,有两种手段,一种是继承Thread类,另外一种是实现接口.(Runable接口和Callable接口,并与Future、线程池结合使用)。在Java中,推荐使用线程池方式实现多线程。

继承Thread实现

以下是一个简单的例子

class MyThread extends Thread {    
    @Override    
    public void run() {       
        //TO DO sth.    
    }
}

开启线程,调用线程对象的start方法即可,注意不是调用run方法,调用run方法也行,但是就不是新开线程的方式运行了,而是普通的方法调用。真正是调用start() 方法。

public static void main(String[] args) 
{    
    new MyThread().start();
}

实现java.lang.Runnable接口

先说一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法:

public interface Runnable {
    public abstract void run();
}

采用Runnable也是非常常见的一种,我们只需要重写run方法即可。下面也来看个实例。

class MyThread implements Runnable {    
    @Override    
    public void run() {       
        //TO DO sth.    
    }
}

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

public static void main(String[] args) {    
    new Thread(new MyThread()).start();
}

实现java.util.concurrent.Callable接口

Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call():

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

那么怎么使用Callable呢?一般情况下是配合ExecutorService来使用的,也可以结合Thread使用。由于需要返回和取消,因此还需要一个参数来传递状态。

Future
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

Future类位于java.util.concurrent包下,它是一个接口:

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。

FutureTask

FutureTask实现了RunnableFuture接口,我们先来看一下FutureTask的实现:

public class FutureTask<V> implements RunnableFuture<V>

而RunnableFuture又实现了Runnable, Future接口

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

所以实现方式

class MyThread implements Callable {    
    @Override    
    public Object call() throws Exception {         System.out.println(Thread.currentThread().getName());        
    return 1;    
    }
}

调用可以方式1:使用线程池

public void testCallable1() throws ExecutionException, InterruptedException {    
    ExecutorService pool =  Executors.newSingleThreadExecutor();    
    MyThread task = new MyThread();    
    FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
    pool.submit(futureTask);    
    System.out.println(futureTask.get());
}

调用方式2:使用线程对象

MyThread task = new MyThread();   
 FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
 Thread thread = new Thread(futureTask).start();
 System.out.println(futureTask.get());

实现线程方式的比较

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去处理同一个资源

  2. 可以避免java中的单继承的限制

  3. 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

  4. 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

线程同步

synchronized关键字

synchronized关键字的作用域有三种:

  1. 在实例方法前面加上synchronized关键字,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法。

  2. 在类方法前面加上synchronized关键字,synchronized static bMethod(){}可以防止多个线程同时访问这个synchronized方法。

  3. synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

值得注意的是,synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。

并发包的lock工具

在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。

ReentrantLock

ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力

ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例

lock() : 获得锁

unlock() : 释放锁

ReentrantLock lock = new ReentrantLock();
lock.lock();
int syncNum = 1;
try {    
     syncNum++ ;
} finally {    
     lock.unlock();
}

注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于会大幅度降低程序运行效率,不推荐使用 。

其他的一些方法实现同步
(1)原子变量实现线程同步

在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。

(2)使用volatile关键字

(3)使用ThreadLocal避免并发问题

(4)util.concurrent下实现了很多线程安全的集合类,可以使用

同步可能产生的问题
线程僵死

如果一个线程等待一个永远不会释放的锁,那么线程就会一直无法运行。

死锁

只要您拥有多个进程或者线程,而且它们要争用对多个锁的独占访问,那么就有可能发生死锁。如果有一组进程或线程,其中每个都在等待一个只有其它进程或线程才可以执行的操作,那么就称它们被死锁了。

性能问题

同步由于需要获得锁才能运行,如果多个线程竞争,那么就会导致性能下降。但是需要保证线程同步的正确性,再进行锁的优化。

线程相关的方法

  1. sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

  2. join():指等待t线程终止。

join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

  1. yield():暂停当前正在执行的线程对象,并执行其他线程。

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

  1. setPriority(): 更改线程的优先级。

线程优先级最大是10,最小是1,默认是5。
MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10

  1. interrupt():让出入阻塞的线程抛出一个中断信号
    不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出,从而结束线程,但是如果你try了这个异常,那么这个线程还是不会中断的!
    注意如果线程没有阻塞,那么是不会抛出InterruptedException的。

  2. setDaemon():指明某个线程是守护程序线程

守护程序线程作为在程序中创建的后台线程,如果程序中所有的非守护线程执行完成,那么程序就会推出。例如垃圾回收的线程等也是后台线程。

  1. Obj.wait(),与Obj.notify()

Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作。
从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。

wait和sleep区别

共同点:

  1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
  2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
    如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
    需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。

不同点:

  1. Thread类的方法:sleep(),yield()等
    Object的方法:wait()和notify()等
  2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
    sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,699评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,124评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,127评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,342评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,356评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,057评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,654评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,572评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,095评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,205评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,343评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,015评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,704评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,196评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,320评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,690评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,348评论 2 358

推荐阅读更多精彩内容

  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,355评论 3 87
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,963评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,458评论 1 15
  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 4,738评论 12 45
  • 林炳文Evankaka原创作品。转载自http://blog.csdn.net/evankaka 本文主要讲了ja...
    ccq_inori阅读 655评论 0 4