您的位置:首页 > 职场人生

黑马程序员——Java基础---传统多线程

2014-10-15 14:53 375 查看
-------  android培训java培训、期待与您交流!
----------

一.了解进程和线程

1.在多任务系统中每个独立执行的程序称为进程,也就是“正在进行的程序”,我们现在使用的操作系统一般都是多任务的,即能够同时执行多个应用程序,实际情况是操作系统负责对CPU等设备的资源进行分配和管理,虽然这些设备某一时刻只能做一件事情,但已非常小的时间间隔交替执行多个程序,就可以给人以同时执行多个程序的感觉。

2.一个进程中又可以包含一个或多个线程,一个线程就是一个程序内部的一条执行线索,如果要一个程序中实现多段代码同时交替运行,就需要产生多个线程,并指定每个线程上所要运行的程序代码,这就是多线程。

3.多线程与单线程的对比

 


二.用Thread类创建线程

1.要将一段代码在一个新的线程上运行,该代码应该在一个类的run函数中,并且run函数所在的类是Thread类的子类。倒过来看,子类要覆盖Thread类中的run函数,在子类的run函数中调用想在新的线程上运行的子类对象的run方法。

2.启动一个新的线程,我们不是直接调用Thread类的子类对象的run方法,而是调用Thread子类对象的start方法(从Thread类继承到的);Thread类对象的start方法将产生一个新的线程,并在该线程上运行该Thread类对象中的run方法,根据面向对象的运行时的多态性,该线程上世纪运行的是Thread子类(也就是我们写的那个类)对象中的run方法。

3.由于线程的代码在run方法中,那么该方法执行完成后线程也就相应的结束了,因而我们可以通过控制run方法中循环的条件来控制线程的结束。

注:

<pre name="code" class="java">class TestThread extends Thread {
public void run() {
while (true) {
System.out.println("run()" + Thread.currentThread().getName());
}
}
}



public static void main(String[] args) {
new TestThread().start();
while (true) {
System.out.println("main()" + Thread.currentThread().getName());
}
}

三.后台线程与联合线程

1.如果我们在启动(调用了start方法)之前调用了setDaemon(true)方法,这个线程就变成了后台线程

2.对Java程序来说只要还有一个前台线程在运行,这个线程就不会结束,如果一个进程中只有后台线程运行,这个进程就会结束。

3.pp.join()的作用是把所对应的线程合并到调用pp.join();语句的线程中。

注3:

<pre name="code" class="java">class TestThread extends Thread {
public void run() {
while (true) {
System.out.println("run()" + Thread.currentThread().getName());
}
}
}



public static void main() throws Exception {
TestThread tt = new TestThread();
tt.start();
int index = 0;
while (true) {
if (index++ == 100) {
tt.join();
// 或tt.jion(10000);指定合并的时间,即主线程等待的时间
System.out
.println("mian():" + Thread.currentThread().getName());
}
}
}

四.使用Runnable接口创建多线程

1.适合多个相同代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想

2.可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能有两个父类,所有不能用继承Thread类的方式,那么这个类就只能采用实现Runnable接口的方式。

3.当线程需要被构造时,需要的代码和数据通过一个对象作为构造函数的参数传递进去这个对象就是实现了Runnable接口的类的实例。

4.事实上,几乎所有多线程应用都可用Runnable接口方式。

5.Thread类的构造方法:

Thread()

Thread(Runnable)

那么继承Thread类与实现Runnable接口实现的多线程有什么区别?

注5.实现四个线程卖100张票:

继承Thread类的方式:

<pre name="code" class="java">public class ExtendThread {
public static void main(String[] args) {
new TestThread1().start();
new TestThread1().start();
new TestThread1().start();
new TestThread1().start();
}
}
class TestThread1 extends Thread {
int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0)
System.out.println(Thread.currentThread()
.getName()+":"+ ticket--);
else
break;
}
}
}




结果无法实现。

修改一下继续:

<pre name="code" class="java">public class ExtendThread {
public static void main(String[] args) {
TestThread1 tt=new TestThread1();
tt.start();
tt.start();
tt.start();
tt.start();
<span style="white-space:pre">	</span>}
}



此种方式只能打开一个线程还是无法实现。

之后用实现Runnable接口的形式:

<pre name="code
ce8a
" class="java">public class implemet Runnable {
public static void main(String[] args) {
TestThread2 tt = new TestThread2();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}
class TestThread2 implements Runnable {
int ticket = 100;
public void run() {
while (true) {
if (ticket > 0)
System.out.println(Thread.currentThread()
.getName()+":"+ ticket--);
else
break;
}
}
}



比较:若为继承Thread类的方式,则每个线程各有100张票,共400张票,而不会共用100张票。若为实现Runnable接口,因为只创建一个数据对象,却创建4个线程,所以能够共用100张票。

五.多线程在实际中的应用

1.网络聊天程序的收发

1>如果一方从键盘上读取了数据发送给对方,程序运行到读取对方会送的数据,并一直等待对方回送数据,如果对没有回应,程序不能再做任何事情,至此程序处于阻塞状态,即使用户想正常终止程序运行都不可能,更不可能给对方发送一条消息,催促对方赶快回答这样的事情了。

2>如果程序没有事先从键盘上读取数据并向外发送,程序将一直在“从键盘上读取数据”处阻塞,即使有数据从网上发送过来,程序无法到达“读取对方回送的数据”处,程序不能收到别处先主动发过来的数据。

2.表记录复制的中途取消

3.WWW服务器为么一个来访者都建立专线服务

六.多线程的同步(多线程的安全)

1.什么是线程同步

注1:

public class ThreadSynchronized {
public static void main(String[] args) {
TestThread2 tt = new TestThread2();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}
class TestThread2 implements Runnable {
int ticket = 100;
public void run() {
while (true) {
if (ticket > 0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() //
.getName() + ":" + ticket--);
}
else
break;
}
}
}

线程出现安全问题,以为代码在执行到Thread.sleep(1);处时CPU切换到其他线程执行,当再切换回来继续执行时状态改变,从而出现执行结果错误

2.同步代码块

注2:解决上面的问题

class TestThread2 implements Runnable {
int ticket = 100;
String str=new String();
public void run() {
while (true) {
synchronized (str) {
if (ticket > 0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() //
.getName() + ":" + ticket--);
}
else
break;
}

}
}
}

在run()方法外的数据对象,有多个调用run()的线程所共享,由于每创建一个线程时,run()方法都会被调用一次,因此run()内创建的对象及数据由线程所独有而不共享。

同步代码块中的线程若想实现同步,括号中所用的对象必须是所有线程所共享的唯一对象。

3.同步函数

class TestThread2 implements Runnable {
int ticket = 100;
String str=new String();
public void run() {
while (true) {
sale();
if(ticket<=0)
break;
}
}
public synchronized void sale(){
if (ticket > 0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() //
.getName() + ":" + ticket--);
}
}
}


在函数上加上synchronized关键字,函数中的代码能够实现同步,这个同步时用的对象锁为this.

4.代码块与函数的同步

注4.当同步代码块也使用this作为对象锁时,可以实现和同步函数的同步

5.死锁问题

注六:线程同步使用的情况:

a>有共享数据

b>若在某一段代码中CPU被切换,切汇时会导致数据的不一致性

七.线程间的通信

1.wait():告诉当前线程放弃监视器并进入睡眠状态直到其他线程进入监视器并调用notify()为止。

2.notify():唤醒同一监视器中调用wait()的第一个线程。用于类似饭有一个空位后通知所有等候就餐的顾客中的第一个可以入座的情况。

3.notifyAll():唤醒同一对象监视器中调用wait()的所有线程,具有最高优先级的线程仙贝唤醒并执行。类似于某个不定期的培训班终于招生蛮满额后,通知所有学员都来上课的情况。

4.线程的等待和唤醒

 


注七:



class Q {
private String name = "unknown";
private String sex = "unknown";
private boolean bFull = false;
public synchronized void put(String name, String sex) {
if(bFull)
try {
wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
this.name = name;
try {
Thread.sleep(1);
} catch (Exception e) {
}
this.sex = sex;
bFull=true;
notify();
}
public synchronized void get() {
if(!bFull)
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(name + " : ");
System.out.println(sex);

bFull=false;
notify();
}
}
class Producer implements Runnable {
Q q;
public Producer(Q q) {
this.q = q;
}
public void run() {
int i = 0;
while (true) {
if (i == 0)
q.put("zhangsan", "male");
else
q.put("lisi", "female");
i = (i + 1) % 2;
}
}
}
class Consumer implements Runnable {
Q q;
public Consumer(Q q) {
this.q = q;
}
public void run() {
while (true)
q.get();
}
}
public class ProducerConsumer {
public static void main(String[] args) {
Q q = new Q();
new Thread(new Producer(q)).start();
new Thread(new Consumer(q)).start();
}
}


八.线程生命的控制

 


程序中如何控制线程的生命

stop(),suspend(),resume()容易引起死锁,不建议使用

主线程控制子线程执行的例子:

class ThreadTest implements Runnable {
private boolean bStop = false;
public void run() {
while (!bStop) {
System.out.println(Thread.currentThread() //
+ "is running.");
}
}
public void stopMe() {
bStop = true;
}
}
public class ThreadControlThread {
public static void main(String[] args) {
ThreadTest tt = new ThreadTest();
new Thread(tt).start();
for (int i = 1; i <= 100; i++) {
if (i > 30)
tt.stopMe();
System.out.println("main is running.." + i);
}
}
}

九.定时器

>Timer类

>TimerTask类

应用举例:

1.//10秒后执行run中的代码

public class TraditionalTimer {
public static void main(String[] args) throws Exception {
new  Timer().schedule(new TimerTask() {
@Override
public void run() {
System.out.println("bombind!");
}
}, 10000);

while(true){
System.out.println(new Date().getSeconds());
Thread.sleep(1000);
}
}
}


2.10秒后首次执行run中的代码,之后每隔3秒执行一次

public class TraditionalTimer {
public static void main(String[] args) throws Exception {
new  Timer().schedule(new TimerTask() {
@Override
public void run() {
System.out.println("bombind!");
}
}, 10000,3000);

while(true){
System.out.println(new Date().getSeconds());
Thread.sleep(1000);
}
}
}

另一种实现方式:

class MyTamerTask extends TimerTask {
@Override
public void run() {
System.out.println("bombind!");
new Timer().schedule(new MyTamerTask(), 3000);
}
}
public class TraditionalTimer {
public static void main(String[] args) throws Exception {
new Timer().schedule(new MyTamerTask(),10000);
while (true) {
System.out.println(new Date().getSeconds());
Thread.sleep(1000);
}
}
}

3.每2秒和4秒交替执行

public class TraditionalTimer2 {
public static void main(String[] args) throws Exception {
new Timer().schedule(new MyTimerTask2(), 2000);
while (true) {
System.out.println(new Date().getSeconds());
Thread.sleep(1000);
}
}
}
class MyTimerTask2 extends TimerTask{
static int count=0;
@Override
public void run() {
count=(count+1)%2;
System.out.println("bombind!");
new Timer().schedule(new MyTimerTask2(), 2000+2000*count);
}
}


4.与具体时间点有关的定时器应用使用:

schedule(TimerTask task, long delay, long period)

方法。

比如,每天的凌晨3:00定时执行某个任务。

5.更复杂的定时器应用可以使用开源工具quartz。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: