继承Thread类创建和执行多线程完成的四个步奏
1.定义一个类扩展Thread。
2.覆盖run方法,这个方法中实现线程要执行的操作
3.创建一个这个线程类的对象
4.调用start()方法启动线程对象
public class thread {
public static class countingThread extends Thread{
private String name;
public countingThread(String name){
this.name=name;
}
public void run(){
System.out.println("线程启动为:"+this.name);
for(int i=0;i<9;i++){
System.out.println(name+"运行:i="+(i+1)+"\t");
}
System.out.println("线程结束:"+this.name);
}
}
public static void main(String[]args){
countingThread thread1=new countingThread("线程A");
countingThread thread2=new countingThread("线程B");
thread1.start();thread2.start();
}
}
实现Runnable接口创建和执行多线程完成下列五个步奏
1.定义一个类实现Runnable接口:implements Runnable
2.覆写其中的run方法
3.创建Runnable接口实现类的对象
4.创建Thread类的对象,(以Runnbale子类对象为构造方法参数。)
5.start()方法启动线程
package runnbale;
public class runnable {
public static class RunnableDemo01 implements Runnable{
private String name;
public RunnableDemo01(String name){
this.name=name;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("线程启动: "+this.name);
for(int i=0;i<10;i++){
System.out.println(name+"运行:"+i);
}
System.out.println("线程结束:"+this.name);
}
}
public static void main(String []args){
RunnableDemo01 ct1=new RunnableDemo01("线程A");
RunnableDemo01 ct2=new RunnableDemo01("线程B");
Thread thread1=new Thread(ct1);
Thread thread2=new Thread(ct2);
thread1.start();
thread2.start();
}
}
两种方法实现方式的对比
1.根据功能要求,首先要显示时间,需要使用java.util包中Date类,实例化得到当前时间,每秒显示当前时间,则要多线程编程,每秒唤醒一次线程,并且在这一瞬间显示出时间;再者applet小程序已经继承了applet类,java不支持多继承,就不能继承Thread类,只能使用Runnable接口
import java.applet.Applet;
import java.awt.Graphics;
import java.sql.Date;
public class clock extends Applet implements Runnable{
Thread clockThread;
public void start(){
if(clockThread==null){
clockThread=new Thread(this);
clockThread.start();//线程启动
}
}
@Override
public void run() {
Graphics g = getGraphics();
// TODO Auto-generated method stub
while(clockThread!=null){
repaint();
try{
clockThread.sleep(1000);
paint(g);
}catch(InterruptedException e){}
}
}
public void paint(Graphics g){
Date now =new Date(9999999);
g.drawString("当前时间:"+now.getHours()+":"+now.getMinutes()+":"
+now.getSeconds(),25,30);
}
public void stop(){
clockThread.stop();
clockThread=null;
}
}
结论:使用Runnable接口,可以避免由于java的单继承带来的局限
用户经常碰到这样一种情况,即当要将已经继承了某个类的子类放入多线程中,由于java不支持多继承,所以不能再继承Thread类,这个类可采用Runnbale接口的方式。
public class SaleThread extends Thread{
private int ticket=5;
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++){
if(this.ticket>0){
System.out.println(Thread.currentThread().getName()+
"卖票:ticket ="+ticket--);
}
}
}
}
public class manyThread {
public static void main(String[] args){
SaleThread st1=new SaleThread();
SaleThread st2=new SaleThread();
SaleThread st3=new SaleThread();
st1.start();st2.start();st3.start();
}
}
要实现多个窗口共同卖5张票,只能创建一个资源对象,(该对象包含要发售的那5张票),但要创建多个线程去处理 这同一个资源对象,并且每个线程上所运行的是相同的程序代码。
package mainThread02;
public class RunnableDemo02 {
public static class SaleThread implements Runnable{
private int ticket=5;
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++){
if(this.ticket>0){
System.out.println(Thread.currentThread().getName()+
"卖票: ticket="+ticket--);
}
}
}
}
public static void main(String[] args){
SaleThread st=new SaleThread();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
}
}
结论:实现Runnable接口适合多个相同程序代码的线程去处理同一资源的情况,能实现资源共享,继承Thread不能资源共享。并且实现Runnable接口增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的,较好地体现了面向对象的设计思想。
所以,在开发中建议读者使用Runnable接口实现多线程
线程的调度
新创建的线程在他的生命周期中只能处于下列五中状态之中:
1.新建状态 2.就绪状态 3.运行状态 4.阻塞状态 5.死亡状态
线程的优先级
public static final int MIN_PRIORITY 最低优先级 1
public static final int NORM_RPIORITY 普通优先级,默认优先级 5
public static final int MAX_PRIORITY 最高优先级 10
public class CountingThread implements Runnable{
private String name;
public CountingThread(String name){
this.name=name;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
System.out.println(name+"运行: i="+i);
}
}
}
public class ThreadPriorityDemo {
public static void main(String []args){
Thread t1=new Thread(new CountingThread("线程A"));
Thread t2=new Thread(new CountingThread("线程B"));
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();t2.start();
}
}
因此那个线程的优先级高,那个线程就可能先被执行,让哪个先执行最终有CPU的调度决定的。即优先级高的线程会获得更多的运行机会。在java系统中,线程调度采用的优先级基础上的“先到先服务“的原则
线程睡眠
sleep();
这个就不用写了。
线程的状态测试
定义:public final boolean isAlice()
public class IsAliveDemo {
public static class CountingThread implements Runnable{
private String name;
public CountingThread(String name){
this.name=name;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
System.out.println(name+"运行:"+i);
}
}
}
public static void main(String[]args){
CountingThread ct=new CountingThread("线程A ");
Thread t=new Thread(ct);
System.out.println("线程A启动之前:"+t.isAlive());
t.start();
System.out.println("线程A启动之后:"+t.isAlive());
}
}
线程的加入
如果有一个A线程正在运行,希望插入一个B线程,并要求B线程先执行完毕,然后再继续A的执行,可使用Thread对象的join()方法拉力完成这个需求。
public final void join() throws InterruptedException
public class ThreadJoinDemo {
public static class countingThread implements Runnable{
private String name;
public countingThread(String name){
this.name=name;
}
public void run(){
System.out.println("线程启动为:"+this.name);
for(int i=0;i<9;i++){
System.out.println(name+"运行:i="+(i+1)+"\t");
}
System.out.println("线程结束:"+this.name);
}
}
public static void main(String []args){
Thread t1=new Thread(new countingThread("线程A"));
t1.start();
for(int k=0;k<5;k++){
if(k>=2){
try{
t1.join();
}catch(InterruptedException e){
}
}
System.out.println("主线程:k="+k);
}
}
}
线程的礼让
Thread.yield()与Thread.sleep(long)类似,只是不能由用户指定暂停多长时间。yield()先检测当前是否有相同优先级的线程处于可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程。所以yield()方法称为”礼让“,它把运行 机会让给了同等优先级的其他线程,并且yield()方法只能让同优先级的线程有执行的机会
public class ThreadYieldDemo {
public static class countingThread implements Runnable{
private String name;
public countingThread(String name){
this.name=name;
}
public void run(){
for(int i=0;i<5;i++){
try{
Thread.sleep(500);
}catch(Exception e){
}
System.out.println(name+"运行:i="+(i+1)+"\t");
if(i==2){
System.out.println(name+"线程礼让:");
Thread.currentThread().yield();
}
}
}
}
public static void main(String[]args){
Thread t1=new Thread(new countingThread("线程A"));
Thread t2=new Thread(new countingThread("线程B"));
t1.start();
t2.start();
}
}
yield()方法和sleep()方法的区别
1.sleep()方法使当前运行中的线程睡眠一段时间,进入不可运行状态,这段时间的长短是有程序设定的,yield()方法使当前线程让出CPU占有权,但让出的时间是不可设定的
2.yield()方法允许较低的线程获取运行机会,yield()只能使同优先级的线程有执行的机会。yield()方法执行的时候,当前线程仍然处于可运行状态,所以不可能让较低优先级的线程获取CPU占有权。
守护线程
守护线程是一类特殊的线程,他和普通线程的区别在于它并不是应用程序的核心部分,当一个应用程序的所有非守护线程终止运行时,即使仍然有守护线程在运行,应用程序也将终止,反之,只要有一个非守护线程在运行,应用程序就不会终止。守护线程一般被用于在后台为其他线程提供服务。调用方法isDaemon()来判断一个线程是否是守护线程,调用方法setDaemon()将一个线程设定为线程。
public final boolean isDaemon()
public class ThreadDaemonDemo {
public static class CountingThread implements Runnable{
private String name;
public CountingThread(String name){
this.name=name;
}
@Override
public void run() {
// TODO Auto-generated method stub
int i=0;
while(true){
i++;
System.out.println(name+"运行:i="+i);
}
}
}
public static void main(String[]args){
CountingThread ct=new CountingThread("线程A");
Thread t=new Thread(ct);
t.setDaemon(true);
t.start();
}
}
在线程CountingThread中,尽管run()方法是一个无限循环的方式,但是程序依然可以执行完,因为方法中无限循环的线程操作已经设置为守护线程而在后台运行了。
共享资源同步
线程间的共享资源不同步
public class SyncDemo1 {
public static class Account{
private double balance;
Account(double balance){
this.balance=balance;
}
void deposit(double i){
balance=balance+i;
}
double withdraw(double i){
if(balance>i)
balance=balance-i;
else {
i=balance;
balance=0;
}
return i;
}
double getBalance(){
return balance;
}
}
public static class WithdrawThread implements Runnable{
private Account myAccount;
private double amount;
public WithdrawThread(Account a,double amount){
this.myAccount=a;this.amount=amount;
}
@Override
public void run() {
// TODO Auto-generated method stub
double myBalance=myAccount.getBalance();
System.out.println(Thread.currentThread().getName()
+"开始取钱,当前账户余额:"+myBalance);
try{
Thread.sleep(1);
}catch(InterruptedException e){
System.out.println(e);
}
System.out.println("原有账户金额:"+myBalance+",取走"+
myAccount.withdraw(amount)+",余额"+myAccount.getBalance());
}
public static void main(String []args){
Account a=new Account(200);
WithdrawThread WT=new WithdrawThread(a,100);
Thread t1=new Thread(WT,"取钱线程A");
Thread t2=new Thread(WT,"取钱线程B");
Thread t3=new Thread(WT,"取钱线程C");
t1.start();
t2.start();
t3.start();
}
}
}
这种完整性称为共享数据操作的同步。在java语言中,引入了”对象互斥锁”的概念,来实现不同线程对共享数据操作的同步,“对象互斥锁”能阻止多个线程同时访问同一个条件变量。在java语言中,用关键字synchronized来声明一个操作共享数据的一段代码块或一个方法,可以实现”对象互斥锁”。根据关键字synchronized修饰的对象不同,有同步代码块和同步类量两种,以下分别说明。
1.同步代码块
所谓代码块就是用{}括起来的一段代码,在代码块上加上synchronized修饰,则此代码块就称为同步代码块。这段代码就称为一个临界区,表明任何时刻只能 有一个线程能获得访问权。
public class SyncDemo2 {
public static class Account{
private double balance;
Account (double balance){
this.balance=balance;
}
void depoit(double i){
balance=balance+i;
}
double withdraw(double i){
if(balance>i){
balance=balance-i;
}
else {
i=balance;
balance=0;
}
return i;
}
double getBalance(){
return balance;
}
}
public static class WithdrawThread implements Runnable{
private Account myAccount;
private double amount;
public WithdrawThread(Account a,double amount){
this.myAccount=a;
this.amount=amount;
}
@Override
public void run() {
// TODO Auto-generated method stub
this.process();
}
public synchronized void process(){
double myBalance =myAccount.getBalance();
System.out.println(Thread.currentThread().getName()
+"开始取钱,当前账户余额:"+myBalance);
try{
Thread.sleep(1);
}catch(InterruptedException e){
System.out.println(e);
}
System.out.println("现有"+myBalance+",取走"+myAccount.
withdraw(amount)+",余额"+myAccount.getBalance());
}
}
public static void main(String []args){
Account a=new Account(200);
WithdrawThread WT=new WithdrawThread(a,100);
Thread t1=new Thread(WT,"取钱线程A");
Thread t2=new Thread(WT,"取钱线程B");
Thread t3=new Thread(WT,"取钱线程C");
t1.start();t2.start();t3.start();
}
}
线程间交互同步
除了要处理多线程间共享数据操作的同步问题之外,在进行多线程程序设计时,还会遇到另一类问题,这就是如何控制相互交互的线程之间的运行进度,即线程间交互同步。典型的模型是”生产者–消费者“问题,”生产者“不断生产产品并将其放在共享的产品队列中,而”消费者“则不断从产品队列中去除产品,
public class ProducerConsumerDemo1 {
public static class ShareData{
private char c;
public synchronized void putShareChar(char c){
this.c=c;
}
public synchronized char getShareChar(){
return this.c;
}
}
public static class Producer implements Runnable{
private ShareData s;
Producer(ShareData s){
this.s=s;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(char ch='A';ch<='H';ch++){
s.putShareChar(ch);
System.out.println("生产者生产:"+ch);
try{
Thread.sleep((int)Math.random()*100);
}catch(InterruptedException e){}
}
}
}
public static class Consumer implements Runnable{
private ShareData s;
Consumer(ShareData s){
this.s=s;
}
@Override
public void run() {
// TODO Auto-generated method stub
char ch;
do{
ch=s.getShareChar();
System.out.println("消费者消费:"+ch);
try{
Thread.sleep((int)Math.random());
}catch(InterruptedException e){}
}while(ch!='H');
}
}
public static void main(String[]args){
ShareData s=new ShareData();
Producer producer=new Producer(s);
Consumer consumer=new Consumer(s);
Thread p=new Thread(producer);
Thread c=new Thread(consumer);
p.setPriority(Thread.MAX_PRIORITY);
c.setPriority(Thread.MAX_PRIORITY);
p.start();c.start();
}
}
等待通知机制,需要java语言中Object类的支持,可以用Object类的wait()和notify()/notify()/notityAll()方法用来协调线程间的交互关系,
public final void wait() throws InterruptedException
public final void notify()
public final void notifyAll()
public class ProducerConsumerDemo2 {
public static class ShareData{
private char c;
private boolean putFlag=false;
public synchronized void putShareChar(char c){
if(putFlag==true){
try{
wait();
}catch(InterruptedException e){}
}
this.c=c;
putFlag=true;
notify();
}
public synchronized char getShareChar(){
if(putFlag==false){
try{
wait();
}catch(InterruptedException e){}
}
putFlag=false;
notify();
return this.c;
}
}
public static class Producer extends Thread{
private ShareData s;
Producer(ShareData s){
this.s=s;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(char ch='A';ch<='H';ch++){
s.putShareChar(ch);
System.out.println("生产者生产:"+ch);
try{
Thread.sleep((int)Math.random()*100);
}catch(InterruptedException e){}
}
}
}
public static class Consumer extends Thread{
private ShareData s;
Consumer(ShareData s){
this.s=s;
}
@Override
public void run() {
// TODO Auto-generated method stub
char ch;
do{
ch=s.getShareChar();
System.out.println("消费者消费:"+ch);
try{
Thread.sleep((int)Math.random()*100);
}catch(InterruptedException e){}
}while(ch!='H');
}
}
public static void main(String []args){
ShareData s=new ShareData();
Producer producer=new Producer(s);
Consumer consumer=new Consumer(s);
Thread p=new Thread(producer);
Thread c=new Thread(consumer);
p.setPriority(Thread.MAX_PRIORITY);
c.setPriority(Thread.MAX_PRIORITY);
p.start();c.start();
}
}
方法wait()和nofity/notifyAll使用注意
Wait()和nofity/notifyAll必须在已经持有锁的情况下执行,所以他们只能出现在synronized作用的范围内,也就是出现在用
synchronized修饰的方法,类或代码块中。
多线程死锁
死锁:是指两个或两个以上的线程在执行过程中,引争夺资源而造成的一种互相等待的现象。此时称系统处于死锁状态,这些永远在互相等待的线程称为死锁线程。
public class DeadLock implements Runnable{
public static class Jia{
public void say(){
System.out.println("甲对已说:把pen给我,我才能给你note");
}
public void get(){
System.out.println("甲得到pen了.");
}
}
public static class Yi{
public void say(){
System.out.println("乙对甲说:把note给我,我才能给你pen");
}
public void get(){
System.out.println("乙得到pen了.");
}
}
private static Jia jia=new Jia();
private static Yi yi=new Yi();
private boolean flag=false;
@Override
public void run() {
// TODO Auto-generated method stub
if(flag){
synchronized(jia){
jia.say();
try{
Thread.sleep(500);
}catch(InterruptedException e){}
synchronized (yi) {
yi.get();
}
}
}
else{
synchronized (yi) {
yi.say();
try{
Thread.sleep(500);
}catch(InterruptedException e){}
synchronized (jia) {
jia.get();
}
}
}
}
public static void main(String []args){
DeadLock t1=new DeadLock();
DeadLock t2=new DeadLock();
t1.flag=true;
t2.flag=false;
Thread thA=new Thread(t1);
Thread thB=new Thread(t2);
thA.start();thB.start();
}
}
从死锁分析可知,产生死锁的四个必要条件
1.互斥条件:资源每次只能被一个线程使用
2.请求与保持条件:一个线程因请求资源而阻塞是,对已获得的资源保持不放。
3.不剥夺条件: 进程已获得的资源,在未使用完之前,无法强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。