线程与进程的主要差别体现在两个方面:
1、同样作为基本的执行单位,线程是划分地更小的执行单位。
2、每个进程都有一段专用的内存区域。与此相反,线程则共享存储单元(包括代码和数据),通过共享的内存单元来实现数据交换,实时通信与必要的同步操作。
在 Java 语言中, 线程也是一种对象, 但并不是任何对象都可以成为线程, 只有实现了 Runnable 接口或者继承了 Thread 类的对象才能成为线程。
线程的创建有两种方式: 一种是继承 Thread 类, 另一种是实现 Runnable 接口。
要启动线程必须调用Thread类之中的start()方法,而调用了start()方法,也就是调用了run()方法。
如果一个类继承了某一个类, 同时又想采用多线程技术, 就不能用 Thread 类产生线程, 因为 Java不允许多继承, 这时要用 Runnable 接口来创建线程。
在 Runnable 接口中并没有start()方法,所以一个类实现了Runnable 接口也必须用Thread类之中的start()方法来启动多线程。
线程创建后, 调用 start()方法进入就绪状态, 在就绪队列里等待执行;当线程执行 run()方法时, 线程进入运行状态。
当线程调用 Thread 类的 sleep()静态方法时, 线程进入睡眠状态。(注意:调用wait()方法,线程会释放资源;调用sleep()方法,线程不会释放资源)
当线程调用 Thread 类的 join()方法时, 合并某个线程。 即当前线程进入阻塞状态, 被调用的线程执行。
当线程调用 Thread 类的 yield()静态方法时, 线程让出 CPU 资源, 从运行状态进入阻塞状态。
package com.test; class JoinTest implements Runnable { public void run() { for (int i = 0; i < 10; i++) { System.out.println("我是:" + Thread.currentThread().getName()); try { Thread.sleep(1000); // 睡眠1s } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { JoinTest j = new JoinTest(); Thread t = new Thread(j); t.setName("子线程"); t.start(); for (int i = 0; i < 10; i++) { System.out.println("我是主线程"); if (i == 5) { try { t.join();// 合并子线程 } catch (InterruptedException e) { e.printStackTrace(); } } } //主线程睡眠结束后,如果子线程没有结束,则中断子线程 t.interrupt(); } }
输出:
我是主线程
我是:子线程
我是主线程
我是主线程
我是主线程
我是主线程
我是主线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是主线程
我是主线程
我是主线程
我是主线程
package com.test; class YieldTest implements Runnable { public void run() { for (int i = 1; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); if (i % 3 == 0) { Thread.yield(); // 让出CPU 资源 } } } public static void main(String[] args) { YieldTest y = new YieldTest(); Thread t1 = new Thread(y); Thread t2 = new Thread(y); t1.setName("thread1"); t2.setName("thread2"); t1.start(); t2.start(); } }
输出:
thread2:1
thread1:1
thread2:2
thread1:2
thread2:3
thread1:3
thread2:4
thread1:4
thread2:5
thread1:5
thread2:6
thread1:6
thread2:7
thread1:7
thread2:8
thread1:8
thread2:9
thread1:9
同步代码块:同步代码块是使用 synchronized 关键字定义的代码块, 但是在同步的时候需要设置一个对象锁, 一般都会给当前对象 this 上锁。
如果在一个方法上使用了 synchronized 定义, 那么此方法就称为同步方法。
所谓同步就是指一个线程等待另一个线程操作完再继续的情况。
package com.test; class MyThread implements Runnable { // 线程主体类 private int ticket = 6; //注释子类重写父类中的方法 @Override public void run() { // 理解为线程的主方法 for (int x = 0; x < 50; x++) { synchronized (this) { if (this.ticket > 0) { // 卖票的条件 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--); } } } } }
package com.test; public class Test { public static void main(String[] args) { MyThread mt = new MyThread(); new Thread(mt, "售票员B").start(); new Thread(mt, "售票员A").start(); new Thread(mt, "售票员C").start(); } }
输出:
售票员B卖票,ticket = 6
售票员B卖票,ticket = 5
售票员B卖票,ticket = 4
售票员B卖票,ticket = 3
售票员B卖票,ticket = 2
售票员B卖票,ticket = 1
如果有多个进程, 且它们都要争用对多个锁的独占访问, 那么就有可能发生死锁。最常见的死锁形式是当线程 1 持有对象 A 上的锁, 而且正在等待对象 B 上的锁; 而线程 2 持有对象 B上的锁, 却正在等待对象 A 上的锁。 这两个线程永远都不会获得第二个锁或释放第一个锁, 所以它们只会永远等待下去。
当线程调用 Object 类提供的 wait()方法时, 当前线程停止执行, 并释放其占有的资源, 线程从运行状态转换为等待状态。 当线程执行某个对象的 notify()方法时, 会唤醒在此对象等待池中的某个线程, 使该线程从等待状态转换为就绪状态;当线程执行某个对象的 notifyAll()方法时, 会唤醒对象等待池中的所有线程, 使这些线程从等待状态转换为就绪状态。
package com.test; public class ProducerConsumer { public static void main(String[] args) { Stack s = new Stack(); // 创建栈对象s Producer p = new Producer(s); // 创建生产者对象 Consumer c = new Consumer(s); // 创建消费者对象 new Thread(p).start(); // 创建生产者线程1 new Thread(p).start(); // 创建生产者线程2 new Thread(p).start(); // 创建生产者线程3 new Thread(c).start(); // 创建消费者线程 } } // Rabbit 类(产品:玩具兔) class Rabbit { int id; // 玩具兔的id Rabbit(int id) { this.id = id; } public String toString() { return "玩具 : " + id; // 重写toString()方法,打印玩具兔的id } } // 栈(存放玩具兔的仓库) class Stack { int index = 0; Rabbit[] rabbitArray = new Rabbit[6]; // 存放玩具兔的数组 public synchronized void push(Rabbit wt) { // 玩具免放入数组栈的方法 while (index == rabbitArray.length) { try { //释放同步资源,sleep不会,而且这个是等待状态而不是阻塞状态 this.wait(); // 栈满,等待消费者消费 } catch (InterruptedException e) { e.printStackTrace(); } } this.notifyAll(); // 唤醒所有生产者进程 rabbitArray[index] = wt; // 将玩具放入栈 index++; } public synchronized Rabbit pop() { // 将玩具兔取走(消费)的方法 while (index == 0) { try { this.wait(); // 等待生产玩具兔 } catch (InterruptedException e) { e.printStackTrace(); } } this.notifyAll(); // 栈不空,唤醒所有消费者线程 index--; // 消费 return rabbitArray[index]; } } // 生产者类 class Producer implements Runnable { Stack st = null; Producer(Stack st) { // 构造方法,为类的成员变量ss 赋值 this.st = st; } public void run() { // 线程体 for (int i = 0; i < 20; i++) { // 循环生产20 个玩具兔 try { Thread.sleep((int) (Math.random() * 200)); // 生产一个玩具兔后睡眠 } catch (InterruptedException e) { e.printStackTrace(); } Rabbit r = new Rabbit(i); // 创建玩具兔类 st.push(r); // 将生产的玩具兔放入栈 // 输出生产了玩具r,默认调用玩具兔类的toString() System.out.println("生产-" + r); } } } // 消费者类 class Consumer implements Runnable { Stack st = null; Consumer(Stack st) { // 构造方法,为类的成员变量ss 赋值 this.st = st; } public void run() { for (int i = 0; i < 60; i++) { // 循环消费,即取走20 个玩具兔 Rabbit r = st.pop(); // 从栈中取走一个玩具兔 System.out.println("消费-" + r); try { Thread.sleep((int) (Math.random() * 1000)); // 消费一个玩具兔后睡眠 } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出:
消费-玩具 : 0
生产-玩具 : 0
生产-玩具 : 0
生产-玩具 : 0
生产-玩具 : 1
生产-玩具 : 1…
注意:必须在同步环境中调用wait(),notify(),notifyAll()方法,线程拥有对象的锁才能调用对象等待或通知方法。
线程的优先级:就绪队列中优先级高的线程先获得执行。Java 线程有 10 个优先级, 用数字 1~10 表示, 线程默认的优先级是 5 级。对线程可通过 setPriority(int) 方法设置优先级, 通过 getPriority()方法获知一个线程的优先级。
在 Thread 类中有一个名为 sleep(long millis)的静态方法, 此方法用于线程的休眠。由于sleep()方法会抛出一个InterruptedException,所以在程序中需要用try…catch捕获异常。
线程让步是指暂停当前正在执行的线程对象, 转而执行其他线程。 如果当前运行的线程优先级大于或等于线程池中其他线程的优先级, 当前线程能得到更多的执行时间。 如果某线程想让和它具有相同优先级的其他线程获得运行机会, 使用让步方法 yield()即可。 yield()方法只是令当前线程从运行状态转到可运行状态。
线程联合:Thread 的方法 join()让一个线程 B 与另一个线程 A 联合, 即加到 A 的尾部。 在 A 执行完毕之前 B 不能执行。 A 执行完毕, B 才能重新转为可运行状态。
package com.test; public class Test { public static void main(String args[]) throws Exception { Thread sub = new Sub(); sub.setName("子线程"); System.out.println("主线程main 开始执行。"); sub.start(); System.out.println("主线程main 等待线程sub 执行……"); sub.join(); System.out.println("主线程main 结束执行。"); } } class Sub extends Thread { public void run() { System.out.println(this.getName() + "开始执行。"); System.out.println(this.getName() + "正在执行……"); try { sleep(3000); } catch (InterruptedException e) { System.out.println("interrupted!"); } System.out.println(this.getName() + "结束执行。"); } }
输出:
主线程main 开始执行。
主线程main 等待线程sub 执行……
子线程开始执行。
子线程正在执行……
子线程结束执行。
主线程main 结束执行。