前言:并发执行与并行执行
多线程是指在同一个进程中同时存在几个执行体,按几条不同的执行路径同时工作的情况。
新建状态:当一个Thread类或者其子类的对象被声明并且创建,但是还没有执行,处于一种新建状态。此时,线程对象已经被分配了内存空间和其他资源,并且已经初始化,但是该线程尚未被调度,此时的线程可以被调度,变成就绪状态。
就绪状态:就绪状态就是可运行的状态。新建状态的线程被启动之后,将进入线程队列排队等待CPU资源,此时已经具备了运行的条件。一旦轮到它来享用CPU资源的时候,就可以脱离创建它的主线程独立开始自己的生命周期了。另外原来处于阻塞状态的线程被解除阻塞之后也会进入就绪状态。
执行状态:线程正在执行,已经拥有了对CPU的控制权。每一个Thread类及其子类的对象都有一个重要的run()方法。当线程对象被调度执行的时候,它将自动调用本对象的 run()方法,从该方法的第一条语句开始执行,一直到执行完毕,除非该线程主动让出CPU的控制权或者CPU的控制权被优先级更高的线程抢占。处于执行状态的线程在下列情况下将让出CPU的控制权。
阻塞状态:一个正在执行的线程在某些特殊的情况下,将会让出CPU并暂停中止自己的执行。
消亡状态:处于消亡状态的线程不具有继续执行的能力。
优先级决定了线程被CPU执行的优先顺序。如果线程的优先级完全相等,就按照先来先执行的原则进行调度。
Java中,线程的优先级从低到高以整数1~10表示,共分为10级。
Thread类有三个关于线程优先级的静态常量:
对于新创建的线程,系统会遵循下面的原则为其指定优先级:
Java实现多线程的方法有两种:继承java.lang包下面的Thread类,另一种就是用户在定义自己的类中实现Runnable接口。但是不管采用什么方法,都要用到Java语言类库中的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) | 功能同上,name指定了新创建线程的名称。 |
常用方法 | 功能说明 |
---|---|
public static Thread currentThread() | 返回当前正在执行的线程对象 |
public final String getName() | 返回线程的名称 |
public void start() | 使该线程由新建状态变为就绪状态,如果该线程已经是就绪状态,则产生IllegalStateException异常 |
public void run() | 线程应该执行的任务 |
public final boolean isAlive() | 如果线程处于就绪、阻塞或者运行状态,返回true,否则返回false |
public void interrupt() | 当线程处于就绪或者执行状态时候,给该线程设置中断标志。一个正在执行的线程让睡眠线程调用这个方法,则可导致睡眠线程发生InterruptedException异常而唤醒自己,从而进入就绪状态。 |
public static boolean isInterrupted() | 判断该线程是否被中断,若是返回true,否则返回false |
public final void join() | 暂停当前线程的执行,等待调用该方法的线程结束后再继续执行本线程 |
public final int getPriority() | 返回线程的优先级 |
public final void setPriority(int newPriority) | 设置线程的优先级。如果当前线程不能修改这个线程,则产生SecurityException异常。 |
public static void sleep(long milis) | 指定当前线程的睡眠时间。参数millis是线程睡眠的毫秒数,如果这个线程已经被别的线程中断,则产生InterruptedException异常 |
public static void yield() | 暂停当前线程的执行,但该线程仍处于就绪状态,不转为阻塞状态。该方法只给同优先级线程以执行的机会 |
要在一个Thread的子类里激活线程,必须做好下面两件事情:
package thr01; public class MyThread extends Thread{ private String who; public MyThread(String str){ who = str; } @Override public void run() { for (int i = 0; i < 5; i++) { try { sleep((int)(1000 * Math.random())); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(who + "正在运行!"); } } } class App11_1{ public static void main(String[] args) { MyThread you = new MyThread("你"); MyThread she = new MyThread("她"); you.start(); she.start(); System.out.println("主线程main()运行结束"); } }
需要注意的点就是上面sleep()函数,Math.random()返回的时0~1的浮点数。
需求:
package runable01; public class MyThread implements Runnable{ private String who; public MyThread(String str){ who = str; } @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep((int)(1000 * Math.random())); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(who + "正在运行!"); } } } class App11_2{ public static void main(String[] args) { MyThread you = new MyThread("你"); MyThread she = new MyThread("她"); Thread t1 = new Thread(you); Thread t2 = new Thread(she); t1.start(); t2.start(); } }
从前面的例子中,我们看到,程序中被同时激活的多个线程将会同时执行,但是有时候需要有序地执行,这个时候可以使用Thread类中的join()方法。当某一个线程调用join()方法的时候,则其他线程会等到该线程结束后才开始执行。也就是说t.join()将使得t线程“加塞”到当前线程之前获得CPU,当前线程则进入阻塞状态,指导线程t结束为止,当前线程恢复为就绪状态,等待调度。
package join01; class MyThread extends Thread{ private String who; public MyThread(String str){ who = str; } @Override public void run() { for (int i = 0; i < 5; i++) { try { sleep((int)(1000 * Math.random())); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(who + "正在运行!"); } } } public class App11_3 { public static void main(String[] args) { thr01.MyThread you = new thr01.MyThread("你"); thr01.MyThread she = new thr01.MyThread("她"); you.start(); try { you.join(); } catch (InterruptedException e){} she.start(); try { she.join(); } catch (InterruptedException e){} System.out.println("主方法main()运行结束"); } }
激活线程you之后,继续往下执行,但是下面有you.join()语句,所以它会使得程序的流程先停留在此处,直到you线程结束之后,才会执行到第14行的she线程。同理,由于she线程也调用了join()方法,所以要等到she线程结束之后,才会输入主方法结束。
获取当前进程的名字:
同一进程的多个线程之间可以共享相同的内存单元,并可以利用这些共享单元来实现数据交换、实时通信和必要的同步操作。
对于利用构造方法Thread(Runnable target)这种方式创建的线程,当轮到它来享用CPU资源的时候,可运行对象target就会自动调用接口中的run()方法,因此,对于同一个可运行对象的多个线程,可运行对象target就会自动调用接口中的run()方法,因此,对于同一个可运行对象的多个线程,可运行对象的成员变量自然就是这些线程共享的数据单元。
package ticket; class ThreadSale extends Thread{ private int tickets = 10; @Override public void run() { while (true){ if (tickets > 0) System.out.println(this.getName() + " 售机票第"+tickets--+"号"); else System.exit(0); } } } public class App11_4 { 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-0 售机票第7号 Thread-0 售机票第6号 Thread-0 售机票第5号 Thread-0 售机票第4号 Thread-0 售机票第3号 Thread-0 售机票第2号 Thread-2 售机票第10号 Thread-2 售机票第9号 Thread-2 售机票第8号 Thread-2 售机票第7号 Thread-1 售机票第10号 Thread-2 售机票第6号 Thread-0 售机票第1号 Thread-2 售机票第5号 Thread-2 售机票第4号 Thread-2 售机票第3号 Thread-2 售机票第2号 Thread-2 售机票第1号 Thread-1 售机票第9号 Thread-1 售机票第8号 Thread-1 售机票第7号 Thread-1 售机票第6号 Thread-1 售机票第5号 Thread-1 售机票第4号 Thread-1 售机票第3号 Thread-1 售机票第2号 Thread-1 售机票第1号
分析:
package ticket; class ThreadSale2 implements Runnable{ private int tickets = 10; @Override public void run() { while (true){ if (tickets >0) System.out.println(Thread.currentThread().getName()+"售机票第"+tickets--+"号"); else System.exit(0); } } } public class App11_5 { public static void main(String[] args) { ThreadSale2 t = new ThreadSale2(); Thread t1 = new Thread(t, "第1售票窗口"); Thread t2 = new Thread(t, "第2售票窗口"); Thread t3 = new Thread(t, "第3售票窗口"); t1.start(); t2.start(); t3.start(); } }
结果:
第1售票窗口售机票第10号 第1售票窗口售机票第7号 第1售票窗口售机票第6号 第1售票窗口售机票第5号 第1售票窗口售机票第4号 第1售票窗口售机票第3号 第1售票窗口售机票第2号 第1售票窗口售机票第1号 第3售票窗口售机票第8号 第2售票窗口售机票第9号
结论:
package bank01; class Mbank{ private static int sum = 2000; public static void take(int k){ int temp = sum; temp -= k; try{ Thread.sleep((int)(1000 * Math.random())); } catch (InterruptedException e) { e.printStackTrace(); } sum = temp; System.out.println("sum=" + sum); } } class Customer extends Thread{ public void run(){ for (int i = 1;i<=4;i++) Mbank.take(100); } } public class App11_6 { public static void main(String[] args) { Customer c1 = new Customer(); Customer c2 = new Customer(); c1.start(); c2.start(); } }
结果:
sum=1900 sum=1900 sum=1800 sum=1800 sum=1700 sum=1700 sum=1600 sum=1600
错误原因:
synchronized的用法:
//格式一:同步语句 Synchronized(对象){ //临界代码段 } //格式二: public synchronized 返回类型 方法名(){ //方法体 } //或者 public 返回类型 方法名(){ synchronized(this){ //方法体 } }
package bank02; 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) { e.printStackTrace(); } sum = temp; System.out.println("sum=" + sum); } } class Customer extends Thread{ public void run(){ for (int i = 1;i<=4;i++) Mbank.take(100); } } public class App11_7 { 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
要求每存入一张票,就售出一张票,售出后,再存入,直到售完为止。
package ticket02; public class App11_8 { public static void main(String[] args) { Tickets t = new Tickets(10); new Producer(t).start(); new Consumer(t).start(); } } 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){ e.printStackTrace(); } } System.out.println("存入第【"+(++number)+"】号票"); available = true; notify();//存入票后唤醒售票线程开始售票 } public synchronized void sell(){//同步方法,实现售票功能 if (!available){//如果没有存票,则售票线程等待 try { wait(); }catch (Exception e){ e.printStackTrace(); } } System.out.println("售出第【"+(number)+"】号票"); available = false; notify();//售票后唤醒存票线程开始存票 if (number == size) number = size + 1;//在售完最后一张票后,设置一个结束标志 //number > size表示售票结束 } } 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(); } }