Java语言中实现多线程的方法有两种:一种是继承java.lang包中的Thread类;另一个是用户在定义自己的类中实现Runnable接口。。但不管采用哪种方法,都要用到Java语言库中的Thread类以及相关方法。
一、利用Thread类的子类和Runnable接口来创建线程
Java语言的基本类型中已经定义了Thread这个基本类,内置了一组方法,是程序利用该类提供的方法产生一个新的线程、执行一个线程。终止一个线程,或是查看线程的执行状态。
继承Thread类是实现线程的一种方法。Thread类的构造方法如下所示:
Thread类的构造方法
构造方法 | 功能说明 |
---|---|
public Thread() | 创建一个线程对象,此线程对象的名称是“Thread-n”的形式,其中n是一个整数。使用这个构造方法,必须创建Thread类的一个子类并覆盖其run()方法 |
public Thread(String name) | 创建一个线程对象,参数name指定了线程的名称 |
public Thread(Runnable target) | 创建一个线程对象,此线程对象的名称是“Thread*n”的形式,其中n是一个整数。参数target的run()方法将被线程对象调用,作为其执行代码 |
public Thread(Runnable target,String name) | 功能同上,参数target的run()方法将被线程对象调用,作为其执行代码。参数name指定了新创建线程的名称 |
Thread类的常用方法
常用方法 | 功能说明 |
---|---|
public static Thread currentThread() | 返回当前正在执行的线程对象 |
public final String getName() | 返回线程的名称 |
public void start() | 是该线程由新建状态变为就绪状态。如果该线程已经是就绪状态,则产生IllegalStateException异常 |
public void run() | 线程应执行任务 |
public final boolean isAlive | 当线程处于就绪状态或执行状态时,给该线程设置中断标志;一个正在执行的线程让睡眠线程调用该方法,则可导致睡眠线程发生InterruptedException异常 |
public void interruption() | 判断该线程是否处于中断,若是返回true,否则返回false |
public final void join() | 暂停当前线程的执行,等待调用该方法的线程结束后再继续执行本程序 |
public final int getPriority() | 返回线程的优先级 |
public final void setPriority(int newPriority) | 设置线程优先级。如果当前线程不能修改这个线程,则产生SecurityException异常。如果参数不在所要求的优先级内,则产生IlleagalArgumentException异常 |
public static void yield() | 暂停当前线程的执行,但该线程仍处于就绪状态,不转为阻塞状态。该方法只给同优先级线程以执行的机会 |
public static void sleep(long millis) | 当前执行的线程指定睡眠时间。参数millis是线程睡眠的毫秒数。如果这个线程已经被别的线程中断,则产生InterruptedException异常 |
要在一个Thread的子类里激活线程,必须先做好下列两件事。
(1)此类必须是继承自Thread类
(2)线程所要执行的代码必须是写在run()方法内
package 创建Thread类的子类来创建线程
class MyThread extends Thread
{
private String who;
public MyThread(String name)
{
who=name;
}
public void run()
{
for(int i=0;i<5;i++)
{
try
{sleep((int)(1000-Math.random());}
catch(InterruptedException e){}
System.out.println(who+"正在运行!!!");
}
}
}
public class 示例
{
public static void main(String[] args)
{
MyThread t1=new MyThread("你");
MyThread t2=new MyThread("我");
t1.start();
t2.start();
System.out.println("主方法main()运行结束")
}
}
运行结果:
主方法main()运行已结束
你正在运行!!
她正在运行!!
你正在运行!!
她正在运行!!
你正在运行!!
你正在运行!!
她正在运行!!
她正在运行!!
你正在运行!!
她正在运行!!
package 利用Runnable接口创建线程;
class MyThread implements Runnable
{
private String who;
public MyThread(String str)
{
who = str;
}
public void run()
{
for(int i =0;i<5;i++)
{
try
{
Thread.sleep((int)(1000*Math.random()));
}
catch(InterruptedException e) {}
System.out.println(who+"正在运行!!");
}
}
}
public class jl1102 {
public static void main(String[] args)
{
MyThread you = new MyThread("你");
MyThread she = new MyThread("他");
Thread t1 = new Thread(you);
Thread t2 = new Thread(she);
// Thread t1 = new MyThread("你"); error!
t1.start();
t2.start();
System.out.println("主方法main()运行已结束");
}
}
运行结果:
主方法main()运行已结束
你正在运行!!
你正在运行!!
他正在运行!!
你正在运行!!
他正在运行!!
你正在运行!!
你正在运行!!
他正在运行!!
他正在运行!!
他正在运行!!
预期 System.out.println(“主方法main()运行已结束”); 是最后执行的,为什么第一行就输出了呢?事实上,main()方法本身也是一个线程,因此执行完 t1.start();t2.start(); 会继续向下执行,接着输出 主方法main()运行已结束 。 至于是先执行谁一般是看谁先抢到cpu资源而定。通常是 System.out.println(“主方法main()运行已结束”); 语句,因为它不用经过线程激活过程。
下面进行修改,让 System.out.println(“主方法main()运行已结束”); 最后执行;
package jl1103在多线程程序中join方法的使用;
class MyThread extends Thread
{
private String who;
public MyThread(String str)
{
who = str;
}
public void run()
{
for(int i=0;i<5;i++)
{
try
{
sleep((int)(1000*Math.random()));
}
catch(InterruptedException e) {}
System.out.println(who+"正在运行!!");
}
}
}
public class jl1103 {
public static void main(String[] args)
{
MyThread you = new MyThread("你");
MyThread she = new MyThread("他");
you.start();
try {
you.join();//限制you结束后才往下执行
}
catch(InterruptedException e) {}
she.start();
try {
she.join();
}
catch(InterruptedException e) {}
System.out.println("主方法main()运行结束!");
}
}
运行结果:
你正在运行!!
你正在运行!!
你正在运行!!
你正在运行!!
你正在运行!!
他正在运行!!
他正在运行!!
他正在运行!!
他正在运行!!
他正在运行!!
主方法main()运行结束!
二、线程间的数据共享
同一进程的多个线程间可以共享相同的内存单元,并利用这些共享单元来实现数据交换。实时通信和必要的同步操作。对于利用构造方法Thread(Runnable target)这种方式创建的线程,当轮到它来相用cpu资源时,可运行对象的成员变量自然就是这些线程共享的数据单元。
package 线程间的数据共享;
class ThreadSale extends Thread
{
private int tickets=10;
public void run() {
while(true)
{
if(tickets>0)
System.out.println(this.getName()+" 售机票第"+tickets--+"号");
else
System.exit(0);
}
}
}
public class jl1104 {
public static void main(String[] args)
{
ThreadSale t1 = new ThreadSale();
ThreadSale t2 = new ThreadSale();
ThreadSale t3 = new ThreadSale();
t1.start();
t2.start();
t3.start();
}
}
运行结果:
Thread-0 售机票第10号
Thread-0 售机票第9号
Thread-0 售机票第8号
Thread-1 售机票第10号
Thread-2 售机票第10号
Thread-1 售机票第9号
Thread-1 售机票第8号
Thread-1 售机票第7号
Thread-1 售机票第6号
Thread-0 售机票第7号
Thread-1 售机票第5号
Thread-1 售机票第4号
Thread-1 售机票第3号
Thread-2 售机票第9号
Thread-1 售机票第2号
Thread-0 售机票第6号
Thread-1 售机票第1号
Thread-2 售机票第8号
Thread-0 售机票第5号
Thread-0 售机票第4号
可看出Thread类不能实现数据间的共享,下面看Runnable接口:
package jl1105线程间的数据共享runneable;
class ThreadSale implements Runnable
{
private int tickets=10;
public void run() {
while(true)
{
if(tickets>0)
System.out.println(Thread.currentThread().getName()+" 售机票第"+tickets--+"号");
else
System.exit(0);
}
}
}
public class jl1105 {
public static void main(String[] args)
{
ThreadSale t=new ThreadSale();
Thread t1=new Thread(t,"第1售票窗口");
//用同一可运行对象t作为参数创建3个线程,第二个参数为线程名
Thread t2=new Thread(t,"第2售票窗口");
Thread t3=new Thread(t,"第2售票窗口");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
第1售票窗口 售机票第10号
第1售票窗口 售机票第9号
第1售票窗口 售机票第8号
第1售票窗口 售机票第7号
第1售票窗口 售机票第6号
第1售票窗口 售机票第5号
第1售票窗口 售机票第4号
第1售票窗口 售机票第3号
第1售票窗口 售机票第2号
第1售票窗口 售机票第1号
其实用Thread将 private int tickets=10; 设置为静态也可以实现共享内存,如下
Thread-2 售机票第10号
Thread-1 售机票第9号
Thread-0 售机票第8号
Thread-0 售机票第5号
Thread-0 售机票第4号
Thread-1 售机票第6号
Thread-1 售机票第2号
Thread-1 售机票第1号
Thread-2 售机票第7号
Thread-0 售机票第3号
Runnable接口用了t作为共享类
三、多线程的同步控制
同步跟共享数据是由去区别的,共享是指线程之间对内存数据共享,因为线程共同拥有对内存空间中数据的处理权力,这样会导致因为多个线程同时处理数据而是数据出现不一样,所以提出了同步解决此问题,即同步是在共享的基础之上,是针对多个多线程共享会导致数据不一致而提出来的。
package jl1106多线程的同步控制;
class Mbank
{
private static int sun=2000;
public static void take(int k)
{
int temp=sun;
temp-=k;
try {
Thread.sleep((int)(1000*Math.random()));
}
catch(InterruptedException e) {}
sun = temp;
System.out.println("sum="+sun);
}
}
class Customer extends Thread
{
public void run()
{
for(int i =1;i<=4;i++)
Mbank.take(100);
}
}
public class jl1106 {
public static void main(String[] args)
{
Customer c1=new Customer();
Customer c2=new Customer();
c1.start();
c2.start();
}
}
运行结果:
sum=1900
sum=1800
sum=1900
sum=1700
sum=1800
sum=1700
sum=1600
sum=1600
为什么会这样呢? 因为在线程相互抢夺时,数据还没有保存,所以会造成上面的情况,下面给出解决方案。
package jl1107多线程的同步控制1;
class Mbank
{
private static int sum=2000;
public synchronized static void take(int k)
{
int temp=sum;
temp-=k;
try {
Thread.sleep((int)(1000*Math.random()));
}
catch(InterruptedException e)
{
}
sum=temp;
System.out.println("sum="+sum);
}
}
class Customer extends Thread
{
public void run()
{
for(int i=1;i<5;i++)
Mbank.take(100);
}
}
public class jl1107 {
public static void main(String[] args)
{
Customer c1=new Customer();
Customer c2=new Customer();
c1.start();
c2.start();
}
}
运行结果:
sum=1900
sum=1800
sum=1700
sum=1600
sum=1500
sum=1400
sum=1300
sum=1200
synchronized的功能:首先判断对象或方法的互斥锁是否在,若在就获得互斥锁,然后就可以执行紧随其后的临界代码或方法体;如果对象或是方法的互斥锁不在(已被其他线程拿走),就进入等待状态,知道获得互斥锁。当被synchrnized限定的代码段执行完,就自动释放互斥锁。
四、线程之间的通讯
多线程的执行往往需要相互之间的配合。为了更加有效地协调不同线程的工作,需要在线程间建立沟通渠道,通过线程间的“对话”来解决线程间的同步问题,而不仅仅是依靠互斥机制。例如,当一个人在排队买面包时,若她给销售员的不是零钱,而售卖员又没有零钱找给她,那她就必须等待,并允许她后面的人先买,一遍售卖员获得零钱找给她。如果她后面的这个人仍没有零钱,那么她俩都必须等待,并允许后面的人先买。
java.lang,Object类的wait()、notify()和notifyAll()等方法为线程间的通讯提供了有效手段。下表给出了Object类中用于线程间通讯的常用方法
Object类中用于线程间通讯的常用方法
常用方法 | 功能说明 |
---|---|
public final void wait() | 如果一个正在执行同步代码(synchronized)的线程A执行了wait()调用(在对象x上),该线程暂停执行而进入对象x的等待队列,并释放已获得的对象x的互斥锁。线程A要一直等到其他线程在对象x上调用notify()或notifyAll()方法,才能够在重新获得对象x的互斥锁后继续执行(从wait()语句后继续执行) |
public void nofity() | 唤醒正在等待该对象互斥锁的第一个线程 |
public void notifyAll() | 唤醒正在等待该对象的所有线程,具有最高优先级的线程首先被唤醒并执行 |
下面通过一些例子说明wait()、notify()方法的应用
//用俩个线程模拟存票、售票过程,但要求没存入一张票,就售出一张票,在存入,直到售完为止。
class Tickets
{
protected int size;
int number=0;
boolean available=false;
public Tickets(int size)
{
this.size=size;
}
public synchronized void put()
{
if(available)
try {
wait();
}
catch(Exception e) {}
System.out.println("存入【"+(++number)+"】号票");
available=true;
notify();
}
public synchronized void sell() {
if(!available)
try{wait();}
catch(Exception e) {}
System.out.println("售出【"+number+"】号票");
available=false;
notify();
if(number==size)
number=size+1;
}
}
class Producer extends Thread
{
Tickets t =null;
public Producer(Tickets t)
{
this.t=t;
}
public void run() {
while(t.number<t.size)
t.put();
}
}
class Consumer extends Thread
{
Tickets t =null;
public Consumer(Tickets t)
{
this.t=t;
}
public void run() {
while(t.number<t.size)
t.sell();
}
}
public class jl1108 {
public static void main(String[] args)
{
{
Tickets t=new Tickets(10);
new Producer(t).start();
new Consumer(t).start();
}
}
}
运行结果:
存入【1】号票
售出【1】号票
存入【2】号票
售出【2】号票
存入【3】号票
售出【3】号票
存入【4】号票
售出【4】号票
存入【5】号票
售出【5】号票
存入【6】号票
售出【6】号票
存入【7】号票
售出【7】号票
存入【8】号票
售出【8】号票
存入【9】号票
售出【9】号票
存入【10】号票
售出【10】号票