黑马程序员 六、线程技术

简介: Java帮帮-IT资源分享网  六、黑马程序员—线程技术 第六篇  1、进程和线程 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中 可以有多个线程。比如在 Windows 系统中,一个运行的 xx.exe 就是一个进程。 Java 程序的进程里有几个线程:主线程, 垃圾回收线程(后台线程) 线程是指进程中的一个执行任务(控
 六、黑马程序员—线程技术
第六篇
 
1、进程和线程
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中
可以有多个线程。比如在 Windows 系统中,一个运行的 xx.exe 就是一个进程。
Java 程序的进程里有几个线程:主线程, 垃圾回收线程(后台线程)
线程是指进程中的一个执行任务(控制单元),一个进程中可以运行多个线程,多个线程可共享
数据。
多进程:操作系统中同时运行的多个程序;
多线程:在同一个进程中同时运行的多个任务;
一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个控制单元。
并发运行。如:多线程下载软件。
可以完成同时运行,但是通过程序运行的结果发现,虽然同时运行,但是每一次结果都不一
致。
因为多线程存在一个特性:随机性。
造成的原因:CPU 在瞬间不断切换去处理各个线程而导致的。
可以理解成多个线程在抢 cpu 资源。
我的总结:
多线程下载:此时线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多
线程也就是同时开起好几个下载通道.当服务器提供下载服务时,使用下载者是共享带宽的,
在优先级相同的情况下,总服务器会对总下载线程进行平均分配。不难理解,如果你线程
多的话,那下载的越快。现流行的下载软件都支持多线程。
多线程是为了同步完成多项任务,不是为了提供运行效率,通过提高资源使用效率来提
高系统的效率.
线程是在同一时间需要完成多项任务的时候实现的.
线程与进程的比较
线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程
元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。
在引入了线程的操作系统中,通常一个进程都有若干个线程,至少需要一个线程。
进程与线程的区别:
1.进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的。
2.线程的堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可
以影响的。
 
2、创建线程方式
1、继承 Thread 类
子类覆写父类中的 run 方法,将线程运行的代码存放在 run 中。
建立子类对象的同时线程也被创建。
通过调用 start 方法开启线程。
2、实现 Runnable 接口
子类覆盖接口中的 run 方法。
通过 Thread 类创建线程,并将实现了 Runnable 接口的子类对象作为参数传递给 Thread
类的构造函数。
Thread 类对象调用 start 方法开启线程。
可使用匿名内部类来写
Eg:
package july7;
//线程的两种方法
class MyThread extends Thread{
private String name;
public MyThread(String name) {
super();
this.name = name;
}
public void run(){
System.out.println(name+"启动!");
}
}
class YourThread implements Runnable{
private String name;
public YourThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+" 第
"+i+"次启动!");
}
}
}
public class Demo1 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
if(i == 50){
new MyThread("刘昭").start();
new Thread(new YourThread(""),"章泽天").start();
}
}
}
}
我的总结:
Thread 类中 run()和 start()方法的区别如下:
run()方法:在本线程内调用该 Runnable 对象的 run()方法,可以重复多次调用;
start()方法:启动一个线程,调用该 Runnable 对象的 run()方法,不能多次启动一个
线程;
 
3、两种进程创建方式比较
A extends Thread:
简单
不能再继承其他类了(Java 单继承)
同份资源不共享
A implements Runnable:(推荐)
多个线程共享一个目标资源,适合多线程处理同一份资源。
该类还可以继承其他类,也可以实现其他接口。
我的总结:
实现方式,因为避免了单继承的局限性,所以创建线程建议使用第二种方式。

Eg:
package july7;
//线程卖票的例子
class SellTicket extends Thread{
private String name;
private int num = 50;
public SellTicket(String name) {
super();
this.name = name;
}
public void run(){
for (int i = 1; i <= num; i++) {
System.out.println(name+"卖出了第"+i+"张票!");
}
}
}
class MySell implements Runnable{
private int num = 50;
@Override
public void run() {
for (int i = 1; i <= num; i++) {
System.out.println(Thread.currentThread().getName()+" 卖
出了第"+i+"张票!");
}
}
}
public class Demo2 {
public static void main(String[] args) throws Exception {
new SellTicket("A").start();
new SellTicket("B").start();
new SellTicket("C").start();
new Thread(new MySell(),"D").start();
new Thread(new MySell(),"E").start();
new Thread(new MySell(),"F").start();
for (int i = 10; i > 0; i--) {
System.out.println(i);
Thread.sleep(1000);
}
}
}
我的总结:
为什么要覆盖 run 方法呢?
Thread 类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存
储功能就是 run 方法.
也就是说 Thread 类中的 run 方法,用于存储线程要运行的代码。
 
4、线程的生命周期
Thread 类内部有个 public 的枚举 Thread.State,里边将线程的状态分为:
NEW-------新建状态,至今尚未启动的线程处于这种状态。
RUNNABLE-------运行状态,正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED-------阻塞状态,受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING-------冻结状态,无限期地等待另一个线程来执行某一特定操作的线程处于
这种状态。
TIMED_WAITING-------等待状态,等待另一个线程来执行取决于指定等待时间的操作
的线程处于这种状态。
TERMINATED-------已退出的线程处于这种状态。
图片 
我的总结:
如何停止线程?
只有一种,run 方法结束。 开启多线程运行,运行代码通常是循环结构。 只要控制
住循环,就可以让 run 方法结束,也就是线程结束。
 
5、控制线程
join 方法:调用 join 方法的线程对象强制运行,该线程强制运行期间,其他线程无法运行,
必须等到该线程结束后其他线程才可以运行。
有人也把这种方式成为联合线程
join 方法的重载方法:
join(long millis):
join(long millis,int nanos):
通常很少使用第三个方法:
程序无须精确到一纳秒;
计算机硬件和操作系统也无法精确到一纳秒;
Eg:
package july7;
class MyThreadDemo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+" 正
在运行!"+i);
if(i == 25){
try {
new Thread(new MyThreadDemo(),"刘昭").join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class DemoRe10 {
public static void main(String[] args) {
new Thread(new MyThreadDemo(),"刘昭").start();
new Thread(new MyThreadDemo(),"章泽天").start();
}
}
Daemon
后台线程:处于后台运行,任务是为其他线程提供服务。也称为“守护线程”或“精灵线程”。
JVM 的垃圾回收就是典型的后台线程。
特点:若所有的前台线程都死亡,后台线程自动死亡。
设置后台线程:Thread 对象 setDaemon(true);
setDaemon(true)必须在 start()调用前。否则出现 IllegalThreadStateException 异常;
前台线程创建的线程默认是前台线程;
判断是否是后台线程:使用 Thread 对象的 isDaemon()方法;
并且当且仅当创建线程是后台线程时,新线程才是后台线程。
sleep
线程休眠:
让执行的线程暂停一段时间,进入阻塞状态。
sleep(long milllis) throws InterruptedException:毫秒
sleep(long millis,int nanos)
throws InterruptedException:毫秒,纳秒
调用 sleep()后,在指定时间段之内,该线程不会获得执行的机会。
控制线程之优先级
每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关。
并非线程优先级越高的就一定先执行,哪个线程的先运行取决于 CPU 的调度;
默认情况下 main 线程具有普通的优先级,而它创建的线程也具有普通优先级。
Thread 对象的 setPriority(int x)和 getPriority()来设置和获得优先级。
MAX_PRIORITY : 值是 10
MIN_PRIORITY : 值是 1
NORM_PRIORITY : 值是 5(主方法默认优先级)
yield
线程礼让:
暂停当前正在执行的线程对象,并执行其他线程;
Thread 的静态方法,可以是当前线程暂停,但是不会阻塞该线程,而是进入就绪状态。所
以完全有可能:某个线程调用了 yield()之后,线程调度器又把他调度出来重新执行。
我的总结:用到时查询 api!
 
6、多线程安全问题
导致安全问题的出现的原因:
多个线程访问出现延迟。
线程随机性。
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
我们可以通过 Thread.sleep(long time)方法来简单模拟延迟情况。
我的总结:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还
没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不
可以参与执行。
Eg:在前面的卖票例子上,在每卖票的前面加上模拟延时的语句!
package july7;
class SellDemo implements Runnable{
private int num = 50;
@Override
public void run() {
for (int i = 0; i < 200; i++) {
if(num > 0){
try {
//因为它不可以直接调用getName()方法,所以必须要获取当前线
程。
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 卖 出 第
"+num--+"张票!");
}
}
}
}
public class Demo3 {
public static void main(String[] args) {
SellDemo s = new SellDemo();
new Thread(s,"A").start();
new Thread(s,"B").start();
new Thread(s,"C").start();
}
}
输出:这样的话,会出现买了第 0,甚至-1 张票的情况!
 
7、多线程安全问题的解决方法
三种方法:
同步代码块:
synchronized(obj)
{
//obj 表示同步监视器,是同一个同步对象
/**.....
TODO SOMETHING
*/
}
同步方法
格式:
在方法上加上 synchronized 修饰符即可。(一般不直接在 run 方法上加!)
synchronized 返回值类型 方法名(参数列表)
{
/**.....
TODO SOMETHING
*/
}
同步方法的同步监听器其实的是 this
静态方法的同步
同步方法
同步代码块
static 不能和 this 连用
静态方法的默认同步锁是当前方法所在类的.class 对象
同步锁
jkd1.5 后的另一种同步机制:
通过显示定义同步锁对象来实现同步,这种机制,同步锁应该使用 Lock 对象充当。
在实现线程安全控制中,通常使用 ReentrantLock(可重入锁)。使用该对象可以显示地加锁和
解锁。
具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,
但功能更强大。
public class X {
private final ReentrantLock lock = new ReentrantLock();
//定义需要保证线程安全的方法
public void m(){
//加锁
lock.lock();
try{
//... method body
}finally{
//在 finally 释放锁
lock.unlock();
}
}
}
修改后的例子:
//同步代码块
package july7;
class SellDemo implements Runnable{
private int num = 50;
@Override
public void run() {
for (int i = 0; i < 200; i++) {
synchronized (this) {
if(num > 0){
try {
//因为它不可以直接调用getName()方法,所以必须要获取当前线
程。
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 卖 出 第
"+num--+"张票!");
}
}
}
}
}
public class Demo3 {
public static void main(String[] args) {
SellDemo s = new SellDemo();
new Thread(s,"A").start();
new Thread(s,"B").start();
new Thread(s,"C").start();
}
}
//同步方法
package july7;
//同步方法
class FinalDemo1 implements Runnable {
private int num = 50;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
gen();
}
}
public synchronized void gen() {
for (int i = 0; i < 100; i++) {
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
"卖出了第"
+ num-- + "张票!");
}
}
}
}
public class Demo6 {
public static void main(String[] args) {
FinalDemo1 f = new FinalDemo1();
new Thread(f, "A").start();
new Thread(f, "B").start();
new Thread(f, "C").start();
}
}
//线程同步锁
package july7;
import java.util.concurrent.locks.ReentrantLock;
//同步锁
class FinalDemo2 implements Runnable {
private int num = 50;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
gen();
}
}
public void gen() {
lock.lock();
try{
//for (int i = 0; i < 100; i++) {
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第"
+ num-- + "张票!");
}
//}
}finally{
lock.unlock();
}
}
}
public class Demo7 {
public static void main(String[] args) {
FinalDemo2 f = new FinalDemo2();
new Thread(f, "A").start();
new Thread(f, "B").start();
new Thread(f, "C").start();
}
}
 
8、线程通信
有一个数据存储空间,划分为两部分,一部分用于存储人的姓名,另一部分用于存储人的性别;
我们的应用包含两个线程,一个线程不停向数据存储空间添加数据(生产者),另一个线程从数
据空间取出数据(消费者);
因为线程的不确定性,存在于以下两种情况:
若生产者线程刚向存储空间添加了人的姓名还没添加人的性别,CPU 就切换到了消费者线
程,消费者线程把姓名和上一个人的性别联系到一起;
生产者放了若干数据,消费者才开始取数据,或者是消费者取完一个数据,还没等到生产者
放入新的数据,又重复的取出已取过的数据;
生产者和消费者
wait():让当前线程放弃监视器进入等待,直到其他线程调用同一个监视器并调用 notify()或
notifyAll()为止。
notify():唤醒在同一对象监听器中调用 wait 方法的第一个线程。
notifyAll():唤醒在同一对象监听器中调用 wait 方法的所有线程。
这三个方法只能让同步监听器调用:
在同步方法中: 谁调用
在同步代码块中: 谁调用
wait()、notify()、notifyAll(),这三个方法属于 Object 不属于 Thread,这三个方法必须由同
步监视对象来调用,两种情况:
1.synchronized 修饰的方法,因为该类的默认实例(this)就是同步监视器,所以可以
在同步方法中调用这三个方法;
2.synchronized 修饰的同步代码块,同步监视器是括号里的对象,所以必须使用该
对象调用这三个方法;
可要是我们使用的是 Lock 对象来保证同步的,系统中不存在隐式的同步监视器对象,那么就
不能使用者三个方法了,那该咋办呢?
此时,Lock 代替了同步方法或同步代码块,Condition 代替了同步监视器的功能;
Condition 对象通过 Lock 对象的 newCondition()方法创建;
里面方法包括:
await(): 等价于同步监听器的 wait()方法;
signal(): 等价于同步监听器的 notify()方法;
signalAll(): 等价于同步监听器的 notifyAll()方法;
例子:设置属性
容易出现的问题是:
名字和性别不对应!
线程通信,很好!
package july7;
class Person{
private String name;
private String sex;
private Boolean isimpty = Boolean.TRUE;//内存区为空!
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public void set(String name,String sex){
synchronized (this) {
while(!isimpty.equals(Boolean.TRUE)){//不为空的话等待消费者
消费!
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;//为空的话生产者创造!
this.sex = sex;
isimpty = Boolean.FALSE;//创造结束后修改属性!
this.notifyAll();
}
}
public void get(){
synchronized (this) {
while(!isimpty.equals(Boolean.FALSE)){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(" 姓 名 "+getName()+ ", "+" 性 别
"+getSex());
isimpty = Boolean.TRUE;
this.notifyAll();
}
}
}
class Producer implements Runnable{
private Person p;
public Producer(Person p) {
super();
this.p = p;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if( i % 2 == 0){
p.set("刘昭", "男");
}else{
p.set("章泽天", "女");
}
}
}
}
class Consumer implements Runnable{
private Person p;
public Consumer(Person p) {
super();
this.p = p;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
p.get();
}
}
}
public class Demo9 {
public static void main(String[] args) {
Person p = new Person();
new Thread(new Producer(p)).start();
new Thread(new Consumer(p)).start();
}
}

目录
相关文章
|
4月前
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
74 3
|
1月前
|
Java 数据库连接 数据库
不同业务使用同一个线程池发生死锁的技术探讨
【10月更文挑战第6天】在并发编程中,线程池是一种常用的优化手段,用于管理和复用线程资源,减少线程的创建和销毁开销。然而,当多个不同业务场景共用同一个线程池时,可能会引发一系列并发问题,其中死锁就是最为严重的一种。本文将深入探讨不同业务使用同一个线程池发生死锁的原因、影响及解决方案,旨在帮助开发者避免此类陷阱,提升系统的稳定性和可靠性。
49 5
|
6月前
|
安全 Java 调度
Java语言多线程编程技术深度解析
Java语言多线程编程技术深度解析
310 1
|
1月前
|
网络协议 安全 Java
难懂,误点!将多线程技术应用于Python的异步事件循环
难懂,误点!将多线程技术应用于Python的异步事件循环
64 0
|
2月前
|
监控 Java
线程池中线程异常后:销毁还是复用?技术深度剖析
在并发编程中,线程池作为一种高效利用系统资源的工具,被广泛用于处理大量并发任务。然而,当线程池中的线程在执行任务时遇到异常,如何妥善处理这些异常线程成为了一个值得深入探讨的话题。本文将围绕“线程池中线程异常后:销毁还是复用?”这一主题,分享一些实践经验和理论思考。
135 3
|
3月前
|
API Windows
揭秘网络通信的魔法:Win32多线程技术如何让服务器化身超级英雄,同时与成千上万客户端对话!
【8月更文挑战第16天】在网络编程中,客户/服务器模型让客户端向服务器发送请求并接收响应。Win32 API支持在Windows上构建此类应用。首先要初始化网络环境并通过`socket`函数创建套接字。服务器需绑定地址和端口,使用`bind`和`listen`函数准备接收连接。对每个客户端调用`accept`函数并在新线程中处理。客户端则通过`connect`建立连接,双方可通过`send`和`recv`交换数据。多线程提升服务器处理能力,确保高效响应。
56 6
|
3月前
三个线程交替打印ABC:技术深度解析与实战应用
【8月更文挑战第14天】在并发编程中,实现多个线程之间的精确协同工作是一项既具挑战性又极具实用价值的任务。今天,我们将深入探讨一个经典问题:如何使用三个线程交替打印字符A、B、C,且每个字符连续打印三次,之后循环进行。这个问题不仅考验了我们对线程同步机制的理解,还锻炼了我们在复杂并发场景下的设计能力。
74 0
|
4月前
|
并行计算 Java 大数据
Java中的高效并行计算与多线程编程技术
Java中的高效并行计算与多线程编程技术
|
5月前
|
Java
【技术瑜伽师】Java 线程:修炼生命周期的平衡之道,达到多线程编程的最高境界!
【6月更文挑战第19天】Java多线程编程犹如瑜伽修行,从创建线程开始,如`new Thread(Runnable)`,到启动线程的活跃,用`start()`赋予生命。面对竞争与冲突,借助同步机制保证资源访问的有序,如`synchronized`关键字。线程可能阻塞等待,如同瑜伽的静止与耐心。完成任务后线程终止,整个过程需密切关注状态变换,以求多线程间的和谐与平衡。持续修炼,如同瑜伽般持之以恒,实现高效稳定的多线程程序。
28 3
|
5月前
|
Java 开发者
【技术成长日记】Java 线程的自我修养:从新手到大师的生命周期修炼手册!
【6月更文挑战第19天】Java线程之旅,从新手到大师的进阶之路:始于创建线程的懵懂,理解就绪与运行状态的成长,克服同步难题的进阶,至洞悉生命周期的精通。通过实例,展示线程的创建、运行与同步,展现技能的不断提升与升华。
39 2