Java 传统线程技术

Java 多线程

在Java中,线程类Thread创建方式有两种:一是继承Thread类,重写run方法;二是,实现Runnable接口。大多数情况下,推荐使用第二种方式,实现runnable接口,这样可以很好的将任务与执行单元分离,更加突出面向对象的思想。

在JDK1.5之前,线程间互斥主依靠内置锁(监视器),而线程间通信则采用Object实例的wait,notify等方法。在JDK1.5之后,增加了很多线程新技术,如线程池、锁、信号量、条件、栅栏、阻塞队列、同步容器等。

1、       传统线程技术

1.1、  线程互斥

在Java中,提供了内置锁(监视器)的概念。每一个对象可以看作是与一把锁关联,在进入临界区前需要获取与对象所关联的锁,在离开临界区释放对象关联的锁。

Java提供了synchronized关键字来方便的获取和释放对象所关联的锁。在Java中synchronized关键字可以修饰方法或代码块,被synchronized关键字修饰的方法或代码块会在进入作用域前获取相应的锁,在离开作用域后释放相应的锁,如下

public
class
Ticket

{

private
int
tickets=10;

public
void
sale()

{

synchronized (this)

{

if (tickets>0)

{

tickets--;

}

}

}

}

通过上面的代码,我们知道使用synchronized代码块需要显示指定那个对象的关联锁。synchronized关键字既可以修饰实例方法,也可以修饰类方法,那么synchronized修饰实例方法获取的是那个对象的关联锁,而修饰类方法又获取的是那个对象的关联锁,如下

public
class
Factory

{

private
static int
count=10;

public
synchronized void
consume()

{

if (count>0)

{

count--;

}

}

public
synchronized static
void
show()

{

System.out.println("count="+count);

}

}

synchronized修饰实例方法时,获取的是该实例所关联的锁(对象锁);而修饰类方法其实获得是该类对应的Class对象的锁(类锁)。由于对象锁和类锁并不是一把锁,所以默认采用synchronized来修饰实例方法和类方法,这些方法是达不到互斥作用的,上面的互斥代码是有问题的。若想实例方法和类方法达到互斥效果,需要采用synchronized代码块,显示指定获取那个对象的关联锁(如同一把类锁,或同一个对象的关联锁),如下

public
class
Factory

{

private
static int
count=10;

private
static final
Object lock=new Object();

public
void
consume()

{

synchronized (lock)

{

if (count>0)

{

count--;

}

}

}

public
static void
show()

{

synchronized (lock)

{

System.out.println("count="+count);

}

}

}

1.2、  线程通信

synchronized关键字可以使线程间互斥,而线程间的通信需要使用Object实例的wait、notify等方法,但前提是不同的线程必须获取同一个实例的监视器,也就是说wait,notify方法必须在互斥块内使用,而且不同的线程所要获取的监视器是同一个监视器。

wait方法可以使一个线程等待,并释放该线程获得的监视器,直到被另一个持有同一个监视器的线程所唤醒;当条件满足时,需要在其他线程中使用notify或notifyAll方法来唤醒等待的线程,被唤醒的线程可以重新获取监视器对象。

在上面我们强调了不同线程间通信的前提是,这些线程所持有或申请持有的是同一个监视器。为了便于不同线程间可以获取同一个监视器,也为了任务和执行单元分离,在涉及多线程的编程时往往将要执行的一组任务封装到一个类中,如下(子线程先打印10句话,主线程接着打印100句;子线程再打印10句,主线程也接着打印100句;如此顺序子线程、主线程个执行50次):

public
class
Service

{

private 
boolean
isSubRunable=true;

public
void
sub() throwsInterruptedException

{

synchronized (this)

{

while (!isSubRunable)

{

this.wait();

}

for (int
i = 0; i < 10;
i++)

{

System.out.println("sub loopof "+i);

}

isSubRunable=false;

this.notifyAll();

}

}

public
void
main() throwsInterruptedException

{

synchronized (this)

{

while(isSubRunable)

{

this.wait();

}

for (int
i = 0; i < 100;
i++)

{

System.out.println("main loopof "+i);

}

isSubRunable=true;

this.notifyAll();

}

}

public
static void
main(String[]
args) throws InterruptedException

{

final Service
service=new Service();

new Thread(new Runnable()

{

@Override

public
void
run()

{

for (int
i = 0; i <50;
i++)

{

try

{

service.sub();

}

catch (InterruptedException
e)

{

}

}

}

}).start();

for (int
i = 0; i < 50;
i++)

{

service.main();

}

}

}

注意:

l   wait方法,使线程等待时可以释放获得的监视器;

l   sleep方法,也可以使线程等待一段时间后再执行,但是在等待期间并不会释放锁获得的监视器;

l   join:一直等待该线程执行完毕才去执行其他线程,也即调用join方法的线程获得CPU控制权,直到该线程执行完毕;在实际应用中,可以使一个线程等待另一个线程执行完毕,才开始执行;

l   yield:该方法指明该线程主要的任务已执行完毕,可以让出当次CPU控制权(让出CPU控制权后立即变为就绪态,参与下次CPU控制权的竞争),使其他同优先级的线程获得执行机会;注意yield并不会释放锁。

1.3、  假唤醒

当某些条件不满足时,我们需要使线程等待,直到这些条件得到满足。在进行条件判断时,为什么要使用循环语句,而不是if语句呢?

由于一些中断或唤醒会提前发生,所以在判断条件满足时需要采用循环语句,以防止提前唤醒。

1.4、  终止线程

在大多数情况下,我们不用显示终止线程,而是等待线程执行完毕。但是在与用户交互的应用中或出现一些其他突发状况,需要终止线程的执行,如用户点击了取消按钮,JVM马上要关闭等。

出现上述情况,该如何安全的终止线程,是立即终止,还是缓慢的终止(等待已提交的任务执行完毕,而不接受新的任务)?

一个线程是一条独立的执行路径,要终止线程的执行,正确的做法是线程自己内部终止本线程的运行。

1.4.1、interrupt

interrupt方法并不是中断一个线程的执行,而是将线程的中断标志设置为true,也即isInterrupted=true;相当于只是打了一个标记,告诉线程要中断了。但是,具体的中断操作是需要线程内部来实现的。

注意:调用interrupt方法将中断标志设置为true后,若调用该线程的sleep、wait、join等方法,将会清除中断标志位,并抛出InterruptedException。由于interrupt方法意味着将要中断线程,而sleep、wait、join等方法与interrupt方法是冲突的,并不能一起执行,所以会将中断标志清除,并抛出InterruptedException异常。

采用中断标志为来终止线程的执行,如下

public
static void
main(String[]
args)

{

Thread thread=
new
Thread(new Runnable()

{

@Override

public
void
run()

{

Thread currentThread=Thread.currentThread();

while (!currentThread.isInterrupted())

{

System.out.println(currentThread.getName()+" is running....");

try

{

Thread.sleep(1000);

}

catch (InterruptedException
e)

{

//需要再次调用interrupt

currentThread.interrupt();

e.printStackTrace();

}

}

}

});

//开启线程

thread.start();

for (int
i = 0; i < 20;
i++)

{

System.out.println("main thread is run loop of "+i);

try

{

if (i==10)

{

thread.interrupt();

}

Thread.sleep(1000);

}

catch (InterruptedException
e)

{

e.printStackTrace();

}

}

}

1.4.2、标志位

在实际应用中,往往采用自定义标志位来终止线程的执行,如下

public
class
Test

{

public
static void
main(String[]
args)

{

Task task=new Task();

//开启线程

new Thread(task).start();

for (int
i = 0; i < 20;
i++)

{

System.out.println("main thread is run loop of "+i);

try

{

if (i==10)

{

task.setStoped(true);

}

Thread.sleep(1000);

}

catch (InterruptedException
e)

{

e.printStackTrace();

}

}

}

}

class Task
implements
Runnable

{

privatevolatilebooleanisStoped=false;

@Override

public
void
run()

{

while (!isStoped)

{

System.out.println("task isrunning.......");

try

{

Thread.sleep(1000);

}

catch (InterruptedException
e)

{

e.printStackTrace();

}

}

}

public
boolean
isStoped()

{

return
isStoped;

}

public
void
setStoped(boolean
isStoped)

{

this.isStoped =
isStoped;

}

}

1.5、  线程封闭

若一些数据只在一个线程内共享,并不会被其他线程访问,可以采用线程封闭技术,将这些数据封闭在具体的执行线程中。在Java中,线程封闭技术采用ThreadLocal技术来实现,我们可以将只在一个线程内共享的数据放入ThreadLocal中,这样在获取数据时只会获取与本线程关联的数据,而不会获取其他线程的数据,这样可以避免线程同步的性能开销,如下

public
class
UserService

{

private ThreadLocal<Connection>
threadLocal=new ThreadLocal<UserService.Connection>(){

protected Connection initialValue()

{

return
new
Connection();

};

};

public Connection getCurrentConnection()

{

return
threadLocal.get();

}

static
class
Connection

{

}

}

1.6、  守护线程

在Java中线程分为用户线程和守护线程。只要有用户线程正在运行,JVM就不会退出;而守护线程是依附用户线程存在的,如负责垃圾收集的线程就是典型的守护线程,但所有用户线程执行完毕,负责垃圾收集的线程也没有存在的必要。

在Java中,默认开启的线程都为用户线程,若要使某个线程称为守护线程,可以在线程启动前,调用setDaemon方法。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-30 15:36:21

Java 传统线程技术的相关文章

JAVA多线程提高一:传统线程技术&amp;传统定时器Timer

前面我们已经对多线程的基础知识有了一定的了解,那么接下来我们将要对多线程进一步深入的学习:但在学习之前我们还是要对传统的技术进行一次回顾,本章我们回顾的则是:传统线程技术和传统的定时器实现. 一.传统线程技术 1.创建方式 1.继承thread类 Thread t = new Thread(){ @Override public void run() { } }; t.start(); 2.实现Runnable接口 Thread t1 = new Thread(new Runnable() {

【java并发】传统线程技术中的定时器技术

传统线程技术中有个定时器,定时器的类是Timer,我们使用定时器的目的就是给它安排任务,让它在指定的时间完成任务.所以先来看一下Timer类中的方法(主要看常用的TimerTask()方法): 返回值 方法名 方法描述 void schedule(TimerTask task, long delay) 安排在指定延迟后执行指定的任务. void schedule(TimerTask task, long delay, long period) 安排指定的任务从指定的延迟后开始进行重复的固定延迟执

Java多线程基础(四)Java传统线程同步通信技术

Java多线程基础(四)Java传统线程同步通信技术 编写代码实现以下功能 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次. 分析 1)子线程循环10次与主线程循环100次必须是互斥的执行,不能出现交叉,下面代码中通过synchronized关键字实现此要求: 2)子线程与主线程必须交替出现,可以通过线程同步通信技术实现,下面代码中通过bShouldSub变量实现此要求: 其他需要注意的地方 1)其中business变量必须声

传统线程技术回顾

/** * * @描述: 传统线程技术回顾 . * <p> * *   多线程机制会提高程序的运行效率? * ============================================== 不会,会更慢,因为CPU资源有限 为什么会有多线程下载呢? 是为了抢夺服务器带宽 ============================================== 不一定,多线程又不能提高CPU的主频,也就是单位时间能够执行的指令数目,如果是一个单线程的任务, CPU也只能处理单线

【java并发】传统线程技术中创建线程的两种方式

传统的线程技术中有两种创建线程的方式:一是继承Thread类,并重写run()方法:二是实现Runnable接口,覆盖接口中的run()方法,并把Runnable接口的实现扔给Thread.这两种方式大部分人可能都知道,但是为什么这样玩就可以呢?下面我们来详细分析一下这两种方法的来龙去脉. 1. 揭秘Thread中run()方法 上面我们看到这两种方式都跟run()方法有关,所以我们来看一下Thread的源码中run()方法到底都干了什么: @Override public void run()

1.传统线程技术的回顾-JDK5多线程

内容摘抄来自:传智播客 张孝祥 老师的<Java高新技术>视频,   并加入了个人总结和理解. 虽然我没有参加过任何培训班,但我很大一部分知识都来自于传智播客的网络分享视频. 十分真挚的感谢张老师的公开视频. 1 import org.junit.Test; 2 /** 3 * 传统线程回顾 4 * 多线程不一定会提高运行效率,和CPU设计和架构有关. 5 * 多线程下载是抢了服务器的资源,并不是自身的机器加快. 6 * @author LiTaiQing 7 */ 8 public clas

传统线程技术(一)

一. 传统线程创建方法 1. 覆盖Thread子类的run方法中编写详细代码 2. 在传递给Thread的Runnable对象的run方法中编写详细代码 二. 实现代码 public class TraditionalThread { public static void main(String[] args) { // 方法1:直接new一个Thread的子类.让子类run方法覆盖父类的run方法 Thread thread1 = new Thread() { @Override public

(黑马Java多线程与并发库高级应用)01 传统线程技术回顾

传统创建线程的两种方式 package cn.itcast.heima2; public class TraditionalThread { public static void main(String[] args) { // TODO Auto-generated method stub Thread thread = new Thread() { @Override public void run() { while (true) { try { Thread.sleep(500); }

传统线程的创建方式

传统线程技术回顾 public class TraditionalThread { /** * @param args */ public static void main(String[] args) { //第一种:new Thread() Thread thread = new Thread(){ @Override public void run() { while(true){ try { Thread.sleep(500); } catch (InterruptedException