Java线程的详细介绍

线程的详细介绍


前言

线程的现象:
多线程,说白了就是多条执行路径,原来是一条路径(就如单线程),就主路径(main),现在是多条路径。就相当于高速路。原来是一条路,因为车多了,为提高使用效率,充分使
用这条道路,中间加了个栅栏, 变成了多条车道。


提示:以下是本篇文章正文内容,下面案例可供参考

一、常用概念

2.1. 程序
Java源程序和字节码文件被称为“程序” ( Program ),是
一个静态的概念为了能够达到效果,我们需要电脑去执行读取程序,并执行相应的操作。
2.2. 进程
执行中的程序叫做进程(Process),是一个动态的概念。
为了使计算机程序得以运行,计算机需要加载代码,同
时也要加载数据。
进程是程序的一次动态执行过程, 占用特定的地址空
间。
每个进程由3部分组成:cpu,data,code。每个进程都
是独立的,保有自己的cpu时间,代码和数据,即便
用同一份程序产生好几个进程,它们之间还是拥有自
己的这3样东西。
多任务(Multitasking)操作系统将CPU时间动态地划分
给每个进程,操作系统同时执行多个进程,每个进程
独立运行。以进程的观点来看,它会以为自己独占
Cpu的使用权。
进程的查看:
Windows系统: Ctrl+Alt+Del
Unix系统: ps or top
2.3. 线程
线程(英语:thread)是操作系统能够进行运算调度的
最小单位。它被包含在进程之中,是进程中的实际运作
单位。一条线程指的是进程中一个单一顺序的控制流,
一个进程中可以并发多个线程,每条线程并行执行不同
的任务。
Threads run at the same time, independently of one
another
一个进程可拥有多个并行的(concurrent)线程
一个进程中的线程共享相同的内存单元/内存地址空
间可以访问相同的变量和对象,而且它们从同一堆中
分配对象通信、数据交换、同步操作
由于线程间的通信是在同一地址空间上进行的,所以
不需要额外的通信机制,这就使得通信更简便而且信
息传递的速度也更快。
2.4. 多线程优缺点
2.4.1. 优点
资源利用率更好;程序设计在某些情况下更简单;程序响
应更快
2.4.2. 缺点
设计更复杂,虽然有一些多线程应用程序比单线程的
应用程序要简单,但其他的一般都更复杂。在多线程
访问共享数据的时候,这部分代码需要特别的注意。
线程之间的交互往 往非常复杂。不正确的线程同步产
生的错误非常难以被发现,并且重现以修复。
上下文切换的开销 当 CPU 从执行一个线程切换到执
行另外一个线程的时候,它需要 先存储当前线程的本
地的数据,程序 指针等,然后载入另一个线程的本地
数据,程序指针 等,最后才开始执行。这种切换称
为“上下文切 换”(“context switch”)。CPU 会在一 个上
下文中执行一个线程,然后切换到另外一个上下文中
执 行另外一个线程。上下文切换 并不廉价。如果没
有必要,应该减少上下文切换的发生。

二、创建线程

编写多线程程序是为了实现多任务的并发执行,从而能够更好地与用户交互。一般有四种方法,Thread , Runnable , Callable ,使用 Executor 框架来创建线程池。

2.1. 继承Thread类实现

  1. 创建线程类: 继承 Thread类 +重写 run() 方法
  2. 构造线程类对象: 创建 子类的对象
  3. 启动线程: 通过子类对象调用 start() 方法
    创建 Thread 子类的一个实例并重写 run 方法, run
    方法会在调用 start() 方法之后自动被执行

代码如下(示例):

public class TestThread {
public static void main(String[] args) {
// 创建线程类对象
SomeThread oneThread = new
SomeThread();
// 启动线程
oneThread.start();
}
}
// 创建线程类
class SomeThead extends Thread{
@Override
public void run()
{
//do something here
}
}

至此,一个线程就创建完成了。
这种方式的特点:那就是如果我们的类已经从一个类继
承(如小程序必须继承自 Applet 类),则无法再继承
Thread 类,异常只能捕获。

3.2. 实现Runnable接口实现

  1. 创建实现 Runnable 接口的实现类 + 重写 run() 方
public static void main(String[] args) {
// 创建线程类对象
SomeThread oneThread = new
SomeThread();
// 启动线程
oneThread.start();
}
}
// 创建线程类
class SomeThead extends Thread{
@Override
public void run()
{
//do something here
}
}

  1. 创建一个实现类对象
  2. 利用实现类对象创建Thread类对象
  3. 启动线程
public class TestThread2 implements Runnable {
SomeRunnable r1 = new SomeRunnable();
Thread thread1 = new Thread(r1);
thread1.start();
Thread thread2 = new Thread(new
SomeRunnable());
thread2.start();
}
// 创建Runnable子类
class SomeRunnable implements Runnable{
@Override
public void run()
{
//do something here
}
}

至此,一个线程就创建完成了。
线程的执行流程很简单,当执行代码oneThread.start();
时,就会执行oneRunnable对象中的void run();方法,该
方法执行完成后,线程就消亡了。

3.3. 实现Callable接口实现(了解)

  1. 创建实现 Callable 接口的实现类 + 重写 call() 方
  2. 创建一个实现类对象
  3. 由 Callable 创建一个 FutureTask 对象
  4. 由 FutureTask 创建一个 Thread 对象
  5. 启动线程
public class CallAbleTest {
public static void main(String[] args)
throws Exception{
MyCallable callable = new
MyCallable();
// 将Callable包装成FutureTask,
FutureTask也是一种Runnable
FutureTask<Integer> futureTask = new
FutureTask<>(callable);
// 将FutureTask包装成Thread
new Thread(futureTask).start();
System.out.println(futureTask.isDone());
System.out.println(futureTask.get());
}
}
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
}
}

4.线程的五种状态

我们在现实生活中,思考问题、发现问题、处理问题,
这是一个完成一件事或者处理一个问题经历的中间过
程。在程序世界也一样,要完成一件事情线程从出生到
消亡会经历一个流程,中间会有不同状态的转换。

  1. 新建状态
    使用 new 关键字和 Thread 类或其子类建立一个线程
    对象后,该线程对象就处于新建状态。它保持这个状
    态直到程序 start() 这个线程。
  2. 就绪状态
    当线程对象调用了start()方法之后,该线程就进入就
    绪状态。就绪状态的线程处于就绪队列中,要等待
    JVM里线程调度器的调度。
  3. 运行状态
    如果就绪状态的线程获取 CPU 资源,就可以执行
    run(),此时线程便处于运行状态。处于运行状态的线
    程最为复杂,它可以变为阻塞状态、就绪状态和死亡
    状态。
  4. 阻塞状态
    如果一个线程执行了sleep(睡眠)、suspend(挂
    起)等方法,失去所占用资源之后,该线程就从运行
    状态进入阻塞状态。在睡眠时间已到或获得设备资源
    后可以重新进入就绪状态。可以分为三种:
    等待阻塞:运行状态中的线程执行 wait() 方法,
    使线程进入到等待阻塞状态。
    同步阻塞:线程在获取 synchronized同步锁失败
    (因为同步锁被其他线程占用)。
    其他阻塞:通过调用线程的 sleep() 或 join() 发出
    了 I/O请求时,线程就会进入到阻塞状态。当
    sleep() 状态超时,join() 等待线程终止或超时,或
    者 I/O 处理完毕,线程重新转入就绪状态。
  5. 死亡状态
    一个运行状态的线程完成任务或者其他终止条件发生
    时,该线程就切换到终止状态。

5.1停止线程

死亡状态是线程生命周期中的最后一个阶段。线程死亡
的原因有两个。一个是正常运行的线程完成了它的全部
工作;另一个是线程被强制终止,如通过执行 stop 或
destroy 方法来终止一个线程。但是,不要调用 stop,
destory 方法 ,太暴力,一盆冷水让其停止。

5.2. 阻塞状态

处于运行状态的线程在某些情况下,如执行了sleep(睡
眠)方法,或等待I/O设备等资源,将让出CPU并暂时停
止自己的运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞
的原因消除时,如睡眠时间已到,或等待的I/O设备空闲
下来,线程便转入就绪状态,重新到就绪队列中排队等
待,被系统选中后从原来停止的位置开始继续运行。
有三种方法可以让我们暂停Thread执行:
sleep方法:sleep() 方法需要指定等待的时间,它可
以让当前正在执行的线程在指定的时间内暂停执行,
进入阻塞状态,该方法既可以让其他同优先级或者高
优先级的线程得到执行的机会,也可以让低优先级的
线程得到执行机会。但是 sleep() 方法不会释放“锁标
t1.start(); //就绪状态
for(int i=0;i<1000;i++){
System.out.println(i);
}
ttc.terminate();
System.out.println(“ttc stop!”);
}
}
志”,也就是说如果有 synchronized 同步块,其他线
程仍然不能访问共享数据。
yield方法: yield() 方法和 sleep() 方法类似,也不会释
放“锁标志”,区别在于,它没有参数,即 yield() 方法
只是使当前线程重新回到可执行状态,所以执行
yield() 的线程有可能在进入到可执行状态后马上又被
执行。让出CPU的使用权,从运行态直接进入就绪
态。让CPU重新挑选哪一个线程进入运行状态。
join方法: 方法会使当前线程等待调用 join() 方法的线
程执行结束之后,才会继续往后执行。

6. 线程同步和死锁问题

6.1. 线程安全
在一般情况下,创建一个线程是不能提高程序的执行效
率的,所以要创建多个线程。但是多个线程同时运行的
时候可能调用线程函数,在多个线程同时对同一个内存
地址进行写入,由于CPU时间调度上的问题,写入数据
会被多次的覆盖,所以就有可能造成数据的不准确。

/**
* 测试同步问题
* @author Administrator
*
*/
public class TestSync {
public static void main(String[] args) {
Account a1 = new Account(100,"高");
Drawing draw1 = new Drawing(80,a1);
Drawing draw2 = new Drawing(80,a1);
draw1.start(); //你取钱
draw2.start(); //你老婆取钱
}
}
/*
* 简单表示银行账户, 将来打算多个线程共用的资源
*/
class Account {
int money;
String aname;
public Account(int money, String aname) {
super();
this.money = money;
this.aname = aname;
}
}
/**
* 模拟提款操作
* @author Administrator
*
*/
class Drawing extends Thread{
int drawingNum; //取多少钱
Account account; //要取钱的账户
int expenseTotal; //总共取的钱数
public Drawing(int drawingNum,Account
account) {
super();
this.drawingNum = drawingNum;
this.account = account;
}
@Override
public void run() {
5.2. 线程同步 synchronized
if(account.money-drawingNum<0){
return;
}
try {
Thread.sleep(1000); //判断完后阻
塞。其他线程开始运行。
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=drawingNum;
expenseTotal+=drawingNum;
System.out.println(this.getName()+"--
账户余额:"+account.money);
System.out.println(this.getName()+"--
总共取了:"+expenseTotal);
}
}

结果:
Thread-0–账户余额:20
Thread-1–账户余额:-60
Thread-0–总共取了:80
Thread-1–总共取了:80

7.1线程同步 synchronized

if(account.money-drawingNum<0){
return;
}
try {
Thread.sleep(1000); //判断完后阻
塞。其他线程开始运行。
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=drawingNum;
expenseTotal+=drawingNum;
System.out.println(this.getName()+"–
账户余额:"+account.money);
System.out.println(this.getName()+"–
总共取了:"+expenseTotal);
}
}
结果:
Thread-0–账户余额:20
Thread-1–账户余额:-60
Thread-0–总共取了:80
Thread-1–总共取了:80
线程同步:即当有一个线程在对内存进行操作时,其他
线程都不可以对这个内存地址进行操作,直到该线程完
成操作, 其他线程才能对该内存地址进行操作,而其他
线程又处于等待状态。
同步就是协同步调,按预定的先后次序进行运行。如:
你说完,我再说。
“同”字从字面上容易理解为一起动作
其实不是,“同”字应是指协同、协助、互相配合。
在Java里面,通过 synchronized 进行同步的保证。它
包括两种用法:synchronized 方法和 synchronized 块
7.2 synchronized 方法
通过在方法声明中加入 synchronized关键字来声明
synchronized 方法。如:
public synchronized void accessVal(int
newVal);
synchronized 方法控制对类成员变量的访问:每个对象
对应一把锁,每个 synchronized 方法都必须获得调用该
方法的对象的锁方能执行,否则所属线程阻塞,方法一
旦执行,就独占该锁,直到从该方法返回时才将锁释
放,此后被阻塞的线程方能获得 该锁,重新进入可执行
状态。
synchronized 方法的缺陷:若将一个大的方法声明为
synchronized 将会大大影响效率,典型地,若将线程类
的方法 run() 声明为 synchronized ,由于在线程的整个
生命期内它一直在运行,因此将导致它对本类任何
synchronized 方法的调用都永远不会成功。当然我们可
以通过将访问类成员变量的代码放到专门的方法中,将
其声明为 synchronized ,并在主方法中调用来解决这一
问题,但是 Java 为我们提供了更好的解决办法,那就是
synchronized 块。
7.3synchronized 块
在代码块前加上 synchronized 关键字,并指定加锁的
对象

synchronized(syncObject){
//允许访问控制的代码
}
/**
* 测试同步问题
* @author Administrator
*
*/
public class TestSync {
public static void main(String[] args) {
Account a1 = new Account(100,"高");
Drawing draw1 = new Drawing(80,a1);
Drawing draw2 = new Drawing(80,a1);
draw1.start(); //你取钱
draw2.start(); //你老婆取钱
}
}
/*
* 简单表示银行账户
*/
class Account {
int money;
String aname;
public Account(int money, String aname) {
super();
this.money = money;
this.aname = aname;
}
}
/**
* 模拟提款操作
* @author Administrator
*
*/
class Drawing extends Thread{
int drawingNum; //取多少钱
Account account; //要取钱的账户
int expenseTotal; //总共取的钱数
public Drawing(int drawingNum,Account
account) {
super();
this.drawingNum = drawingNum;
this.account = account;
}
@Override
public void run() {
draw();
}
// 改进使用 双重检查
void draw(){
synchronized (account) {
if(account.money-drawingNum<0){
return;
}
try {
Thread.sleep(1000); //判断完后
阻塞。其他线程开始运行。
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=drawingNum;
expenseTotal+=drawingNum;
}
System.out.println(this.getName()+"--
账户余额:"+account.money);
System.out.println(this.getName()+"--
总共取了:"+expenseTotal);
}
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值