【Java】多线程_学习笔记

多线程

1、进程

进程:当一个程序进入内存运行时,它就成为了进程。进程具有独立性、动态性、并发性。

A、独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。

B、动态性:进程与程序的区别在于,程序是一个静态的指令集合,而进程是一个正在运行的指令集合。进程有时间的概念,有自己的生命周期和各种不同的状态,而程序不具备这种概念。

C、并发性:多个进程可以在单个处理器上并发执行,相互之间不会有影响。

2、线程

线程:线程是轻量级进程,是进程的执行单元,是进程的组成部分。线程具有独立性、并发性、共享性、抢占性。

A、独立性:每个线程也是独立运行的。

B、并发性:多个线程可以并发执行。

C、共享性:线程可以拥有自己的堆栈、计数器、局部变量等,但不拥有系统资源,同一进程里的多个线程共享父进程的系统资源。

D、抢占性:线程的执行是抢占式的,当前线程任何时候都可能被挂起,以便其他线程运行,一个线程也能结束另一个线程。

说明:一个程序运行后至少有一个进程,一个进程里至少有一个线程。

3、继承Thread类创建线程

创建线程方法:

public class classname extends Thread

{

public void run()

{

//重写父类的run()方法的方法体

}

public static void main(String[] args)

{

new classname().start();

}

}

步骤:

A、定义Thread类的子类,重写run()方法,该run()方法的方法体代表了线程需要完成的任务,所以run()方法称为线程执行体。

B、创建Thread子类的实例,即创建了线程的对象。

C、调用线程对象的start()方法来启动该线程。

说明:

A、Java使用Thread类代表线程,所有的线程的对象都必须是Thread类或其子类的实例。

B、main方法代表主线程。

C、Thread.currentThread():这是一个类方法,返回当前执行的线程对象。

D、getName():这是一个实例方法,返回调用该方法的线程的名字。

E、setName(String name):该方法可以为线程设置名字,线程名字默认为Thread-0、Thread-1……

F、使用继承Thread类的方式创建线程时,多个线程无法共享线程类的实例变量。

4、实现Runnable接口创建线程类

public class Classname implements Runnable

{

public void run()

{

//业务代码

}

public static void main(String[] args)

{

Classname cl = new Classname;

new Thread(cl,“新线程”).start();

}

}

步骤:

A、定义Runnable接口的实现类,并重写该接口的run()方法,run()同样是该线程的线程执行体。

B、创建Runnable实现类的实例,并以此实例作为Thread的Target来创建Thread对象,该Thread对象才是真正的线程对象。

C、调用线程对象的start()方法来启动该线程。

说明:

A、在实现接口的类的run()方法中要获取当前线程对象必须使用类名调用,即Thread.currentThread(),而继承父类的run()方法中,可以使用this调用。

B、实现Runnable接口的线程,可以共享线程类(实际是Target类)的实例变量,因为继承类创建两个线程时会创建两个继承类实例,不同实例的实例变量指向的内存区域不同,而实现接口类创建线程时,提供的只是自己的引用来辅助Thread创建对象,这个引用可以重复使用,所以不会创建多个接口类对象,所以接口类对象里实例自然是指向同一存储区域。

5、使用CallableFuture创建线程

public class Classname

{

public static void main(String[] args)

{

FutureTask<Integer> task = new Future<Integer>

(

(Callable<Integer>)() ->

{

int j = 0;

//业务代码

return j;

}

)

new Thread(task,”有返回值的线程”).start();

try

{

System.out.println(“子线程的返回值:”+task.get());

}

catch(Exception ex)

{

ex.printStackTrace();

}

}

}

步骤:

A、创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例。因为Callable接口在Java8中是函数式接口,所以可以直接使用Lambda创建Callable对象。

B、使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。

C、使用FutureTask对象作为Thread对象的target创建并启动新线程。

D、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

6、线程的生命周期

说明:在线程的生命周期中,一共有5中状态,新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。

7、新建状态

A、使用new关键字创建线程后即处于新建状态。

B、由JVM虚拟机为其分配内存,初始化成员变量值。

C、此时线程对象没有动态特性,也不会执行。

8、就绪状态

A、当线程对象调用start()方法后,该线程就处于就绪状态。

B、JVM为其创建方法调用栈和程序计数器。

C、此时线程可以运行了,但没有运行,何时运行,取决于JVM线程调度器的调度。

9、运行状态

A、处于就绪状态的线程,获得了CPU,开始执行run()方法,此时处于运行状态。

B、一个CPU在任何时刻只能运行一个线程。

10、阻塞状态

A、阻塞状态就是说运行状态被中断,系统资源被收回,其他线程启动。

B、线程调用sleep()方法主动放弃所占用的处理器资源,进入阻塞状态。

C、线程调用阻塞式IO方法,在该方法返回之前,该线程被阻塞。

D、线程试图获得一个同步监视器,但该监视器被其他线程持有,进入阻塞状态。

E、线程在等待某个通知(notify)。

F、程序调用线程的suspend()方法将该线程挂起,进入阻塞状态,该方法容易导致死锁,应尽量避免使用。

H、该线程阻塞后,其他线程获得执行机会,该线程会在合适的时候进入就绪状态,重新开始。

I、调用sleep()方法时间到了,线程接触阻塞进入就绪状态。

J、线程调用的阻塞式IO方法已经放回,线程解除阻塞进入就绪状态。

K、线程成功获得了试图取得的同步监视器,线程解除阻塞进入就绪状态。

M、线程等待某个通知时,其他线程发出了一个通知,线程解除阻塞进入就绪状态。

N、处于挂起状态的线程被调用了resume()恢复方法,线程解除阻塞进入就绪状态。

11、死亡状态

A、run()或call()方法执行结束,线程死亡。

B、线程抛出一个未捕获的Exception或Error,线程死亡。

C、直接调用该线程的stop()方法结束该线程,线程死亡,但容易死锁,不建议使用。

E、主线程结束不会对其他线程造成影响。

F、测试某个线程是否死亡,调用线程对象的isAlive()方法,处于就绪、运行、阻塞状态时,返回TRUE,处于新建、死亡状态时,返回FALSE。

G、不要对死亡的线程调用start()方法来试图启动它,死亡就是死亡。

H、对新建状态的线程两次调用start()方法也是错误的。

12、控制线程

(1)join线程

A、Thread类提供了一个join()方法,线程1调用线程2的join()方法,线程1被阻塞,线程2将执行,线程2执行完毕后,线程1继续执行。

B、join():等待被join的线程执行完成。

C、join(long millis):等待被执行join的线程的时间最长为millis毫秒,超过不等。

D、join(long millis,int nanos):等待被join的线程的时间最长millis毫秒+nanos微秒。

(2)后台线程

A、在后台运行的、为其他线程提供服务的线程,称为后台线程(Daemon Thread)、守护线程或精灵线程。

B、前台线程都死亡,后台线程会自动死亡。

C、调用Thread对象的setDaemon(true)方法,将指定线程设置称为后台线程。

D、Thread类提供isDaemon()方法,用于判断指定线程是否是后台线程。

(3)线程睡眠:sleep()

想让当前执行的线程暂停一段时间,进入阻塞状态,可以通过调用Thread类的静态方法sleep方法实现,sleep()有两种重载形式。

A、static void sleep(long millis):让当前执行的线程暂停millis毫秒,并进入阻塞状态,受系统计时器和线程调度器精度影响。

B、static void sleep(long millis,int nanos):让当前执行的线程暂停millis毫秒+nanos微秒,并进入阻塞状态,受系统计时器和线程调度器精度影响。

C、睡眠时间内,即使没有其他线程运行,该线程也不会的到运行,因此sleep()方法常用于暂停线程执行。

(4)线程让步:yield

A、yield()方法也是Thread类提供的类方法,也可以让当前线程暂停,但它不会阻塞当前线程,只是让它进入就绪状态,等待线程调度器的重新调用。

B、调用了yield()方法的线程,只有优先级与当前线程相同或者更高,才有获得执行的机会。

(5)yield()方法和sleep()方法的区别:

sleep():不理会其他线程优先级。

    A、优先级

yield():只给优先级相同或更高的执行。

sleep():调用后进入阻塞状态,经过阻塞时间后转入就绪状态。

B、阻塞

yield():强制转入就绪状态。

sleep():抛出InterruptedException异常,要么捕捉,要么显式声明抛出。

C、异常

yield():不抛出异常。

sleep():较好。

D、移植性

yield():较差,一般不建议使用yield()方法控制并发线程执行。

(6)改变线程优先级

A、线程执行时都有一定的优先级,优先级高的线程获得的执行机会更多。

B、每个线程默认优先级和创建它的父线程相同,例如main线程是普通优先级,那么,它创建的子线程的优先级也是普通级。

C、Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,newPriority可以是1-10里的一个整数,也可是如下常量:

a、MAX_PRIORITY:其值是10

b、NORM_PRIORITY:其值是5

c、MIN_PRIORITY:其值是1

D、尽量使用常量来设置优先级来保证可移植性。

13、线程同步

(1)当两个进程并发修改同一个文件时就有可能造成异常。

(2)多线程引入同步监视器解决这个问题,格式如下:

synchronized(obj)

{

//同步代码块

}

说明:obj就是同步监视器,上述代码的意思是,线程执行同步代码前,必须先获得同步监视器的锁定。

a、任何时刻,只有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对同步监视器的锁定。

b、任何对象都能作为同步监视器,但是推荐使用可能被并发访问的共享资源充当同步监视器。

c、使用同步监视器,可以保证并发线程同一时刻只有一个线程可以进入修改共享资源的代码区(也称为临界区),从而保证线程的安全性。

(3)同步方法:使用synchronized修饰的方法。对于synchronized修饰的实例方法(非static)而言,无须显式指定同步监视器,同步方法的监视器就是this,就是调用该方法的对象。

(4)不要对线程安全类的所以方法都进行同步,同步是以牺牲效率为代价的。

(5)释放同步监视器的锁定

任何线程进入同步代码块的时候都要先获得对同步监视器的锁定,但同步监视器的锁定的释放不是显性的,只有一下情况会释放锁定:

a、当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器。

b、当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行,当前线程将会释放同步监视器。

c、当前线程在同步代码块、同步方法中出现了未处理的Error或者Exception,导致了改代码块、该方法异常结束时,释放同步监视器。

d、当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,释放同步监视器。

以下情况,不会释放同步监视器:

a、线程执行同步代码块、同步方法时,程序调用了Thread.sleep()、Thread.yield()方法来暂停当前线程,不会释放同步监视器。

b、线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器。

14、同步锁(Lock)

在实现线程安全的过程中,比较常用的是ReentrantLock(可重入锁),使用该对象可以显式的加锁、释放锁,格式如下:

class x

{

private final ReentrantLock lock = new ReentrantLock();

public void m()

{

lock.lock();

try

{

//需要保证线程安全的代码

}

finally

{

lock.unlock();

}

}

}

15、死锁

死锁:当两个线程互相等待对方释放同步监视器时,就会进入死锁。

后果:整个程序既不会发生异常,也不会给出任何提示,所有线程处于阻塞状态,无法继续执行。

16、线程通信

(1)传统线程通信:

a、wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或者notifyAll()方法来唤醒。

wait():一直等待,直至都到通知唤醒。

wait(long millis):等待millis毫秒后,自动苏醒。

wait(long millis,int nanos):等待millis毫秒+nanos微秒后,自动苏醒。

调用wait方法时,线程会释放该同步监视器的锁定。

b、notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,那么选择性的唤醒一个线程,选择是任意的。唤醒的线程要被执行的条件是,当前线程使用wait()方法等待,放弃对同步监视器的锁定。

c、notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对同步监视器的锁定后,唤醒的线程才有被执行的机会。

synchronized修饰的同步方法的默认实例时this,可以在同步方法中直接调用。

synchronized修饰的同步代码块,同步监视器是括号里的对象,必须使用该对象来调用方法。

(2)使用Condition控制线程通信

当使用Lock对象来保证同步,系统中不存在隐式的同步监视器,所以不能使用wait、notify等方法来进行线程通信,这时需要一个Condition对象来代替同步监视器的功能,Condition对象是被绑在Lock对象上的,调用Lock对象里的newCondition()方法即可创建Condition对象。

a、await():和wait()类似,导致当前线程等待,有很多变体,比如long awaitNanos(long nanosTimeout)等,可以完成更丰富的等待操作。

b、signal():唤醒在此Lock对象上等待的单个线程,所有线程都在改Lock对象上等待,选择唤醒其中一个线程,选择是任意的。只有当前线程放弃对该Lock对象的锁定后(使用await()方法),才可以执行被唤醒的线程。

c、signalAll():唤醒在此Lock对象上等待的所有线程。只有当前线程放弃对该Lock对象的锁定后(使用await()方法),才可以执行被唤醒的线程。

(3)使用阻塞队列(BlockingQueue)控制线程通信

a、BlockingQueue是一个线程同步的接口,是Queue的子接口。

b、BlockingQueue特征:当生产者线程试图想BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞。当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。

c、程序的两个线程交替的往BlockingQueue中放入元素、取出元素,就可以很好的控制线程通信。

17、线程组和未处理的异常

A、Java使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理。

B、用户创建的线程都属于指定组,没有显示指定,则属于默认组。

C、默认情况下,子线程和创建它的父线程在同一个组内。

D、一旦某个线程加入指定组后,该线程一直属于该组,直至死亡。

E、ThreadGroup类提供两个构造器来创建实例:

a、ThreadGroup(String name):以指定的线程组名字来创建新的线程组。

b、ThreadGroup(ThreadGroup parent,String name):以指定的父线程和指定的名         字创建一个线程组。

c、可用getName()方法来获取线程组名字。

F、可用以下方法来操作整个线程组:

a、int activityCount():返回该线程组中活动的线程数目。

b、interrupt():中断此线程组中的所以线程。

c、isDaemon():判断该线程组是否是后台线程组。

d、setDaemon(boolean daemon):把该线程组设置成后台线程组。

e、setMaxPriority(int pri):设置线程组的最高优先级。

G、void uncaughtException(Thread t,Throwable e)

ThreadGroup里定义的该方法可以处理该线程内任意线程所抛出的未处理异常,t 是出现异常的线程,e代表该线程抛出的异常。

时间: 2024-08-05 03:18:41

【Java】多线程_学习笔记的相关文章

Java多线程技术学习笔记(二)

目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和wait的区别 停止线程的方式 守护线程 线程的其他知识点 一.线程间的通信示例 返目录回 多个线程在处理同一资源,任务却不同. 假设有一堆货物,有一辆车把这批货物往仓库里面运,另外一辆车把前一辆车运进仓库的货物往外面运.这里货物就是同一资源,但是两辆车的任务却不同,一个是往里运,一个是往外运. 下面

Java多线程技术学习笔记(一)

目录: 概述 多线程的好处与弊端 JVM中的多线程解析 多线程的创建方式之一:继承Thread类 线程的状态 多线程创建的方式之二:实现Runnable接口 使用方式二创建多线程的好处 多线程示例 线程安全问题现象 线程安全问题产生的原因 同步代码块 同步的好处与弊端 同步的前提 同步函数 验证同步函数的锁 单例模式的线程安全问题的解决方案 死锁示例 一.概述 目录 首先得了解进程,打开我们电脑的windows资源管理器,可以直观看到进程的样子: 进程直观上理解就是正在进行的程序.而每个进程包含

Java多线程编程(学习笔记)

一.说明 周末抽空重新学习了下多线程,为了方便以后查阅,写下学习笔记. 有效利用多线程的关键是理解程序是并发执行而不是串行执行的.例如:程序中有两个子系统需要并发执行,这时候需要利用多线程编程. 通过多线程的使用,可以编写出非常高效的程序.但如果创建了太多的线程,程序执行的效率反而会降低. 同时上下文的切换开销也很重要,如果创建太多的线程,CPU花费在上下文的切换时间将对于执行程序的时间. 二.Java多线程编程 概念 在学习多线程时,我们应该首先明白另外一个概念. 进程:是计算机中的程序关于某

java多线程使用学习笔记

初学Java多线程,后续继续改进 一,Callable Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务 Callable和Runnable的区别如下: 1.Callable定义的方法是call,而Runnable定义的方法是run. 2.Callable的call方法可以有返回值,而Runnable的run方法不能有返回值. 3.Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常.

Java基础_学习笔记_16_线程

1.进程与线程 进程,在多任务操作系统中,每个独立执行的程序称为进程,也就是“正在进行的程序”.进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配的最小单元. 线程,是进程中的一部分,是一个程序内部的一条执行线索.在网络或多用户环境下,一个服务器需要接受大量且不确定用户数量的并发请求,为每一个请求创建一个进程显然是行不通的,因此引入了线程.线程是最小的调度单元.通常在一程序中实现多段代码同时交替运行时,需要产生多个线程,并制定每个线程上所要运行的程序代码块,这就

Java基础_学习笔记_14_异常

1 class Test 2 { 3 public int devide(int x,int y) 4 { 5 int result=x/y; 6 return result; 7 } 8 } 9 class TestException 10 { 11 public static void main(String [] args) 12 { 13 Test t=new Test(); 14 t.devide(2,0); 15 System.out.println("the program is

Java基础_学习笔记_13_类的多态性(二)

1 class Animal 2 { 3 private String name; 4 Animal(String name) 5 { 6 this.name=name; 7 } 8 public void enjoy() 9 { 10 System.out.println("叫声..."); 11 } 12 } 13 class Cat extends Animal 14 { 15 private String eyeColor; 16 Cat(String n,String eye

java/android 设计模式学习笔记(一)---单例模式

前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使用的时候也会有一些坑. PS:对技术感兴趣的同鞋加群544645972一起交流 设计模式总目录 java/android 设计模式学习笔记目录 特点 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 单例模式的使用很广泛,比如:线程池(threadpool).缓存(cache).对

python基础教程_学习笔记23:图形用户界面

图形用户界面 丰富的平台 在编写Python GUI程序前,需要决定使用哪个GUI平台. 简单来说,平台是图形组件的一个特定集合,可以通过叫做GUI工具包的给定Python模块进行访问. 工具包 描述 Tkinter 使用Tk平台.很容易得到.半标准. wxpython 基于wxWindows.跨平台越来越流行. PythonWin 只能在Windows上使用.使用了本机的Windows GUI功能. JavaSwing 只能用于Jython.使用本机的Java GUI. PyGTK 使用GTK