线程的概念:
程序 进程与线程的区别:
程序是一个静态的代码或者说静态的存在.而进程是程序的一次运行过程.
多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响.
线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。
线程的实现方法:
继承Thread类
Thread 类是一个具体的类,即不是抽象类,该类封装了线程的行为。要创建一个线程,程序员必须创建一个
从 Thread 类导出的新类。程序员必须覆盖 Thread 的 run() 函数来完成有用的工作。用户并不直接调用此函数;而是必
须调用 Thread 的 start() 函数,该函数再调用 run()。
实现Runnable接口
有时要作为线程运行的类可能已经是某个类层次的一部分,所以就不能再按这种机制创建线程。对于这种情况,就
要 runnable 接口.实现Runnable接口, 提供run()方法的具体实现, 最后生成该类的实例, 并作为参数传入Thread类的构造
方法来创建线程。可以从其他类继承,简化类层次,并将Thread类与任务处理类分开。
实例代码分别如下:
public class myThread01 extends Thread { private int i = 0; public myThread02(String str) { super(str); } public void run() { while (i < 100) { System.out.println(getName() + " 完成了" + i + "% 的工作"); i++; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { new myThread01("worker01").start(); new myThread01("worker02").start(); new myThread01("worker03").start(); new myThread01("worker04").start(); } }
public class myThread02 implements Runnable { private int i = 0; public void run() { while (i < 100) { System.out.println(Thread.currentThread().getName() + " 完成了" + i + "% 的工作"); i++; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { myThread02 mt = new myThread02(); for (int i = 0; i < 4; i++) { new Thread(mt).start(); } } }
线程的生命周期:
新状态:线程已被创建但尚未执行(start() 尚未被调用)。
可执行状态:线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行。
死亡状态:正常情况下 run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果,但是不被推荐,前者会产生异
常,后者是强制终止,不会释放锁。
阻塞状态:线程没有被分配 CPU 时间,无法执行。
线程退出最好自己实现,在运行状态中一直检验一个状态,如果这个状态为真,就一直运行,如果外界更改了这个状态变量,那么线程就停止运行。
1.sleep()方法
在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。不推荐使用。sleep()使当前线程进入阻塞状态,
在指定时间内不会执行。
2.wait()方法
在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线
程有机会抢占该锁。一直等到有人通知我,我才会运行后面的代码。
当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出
IllegalMonitorStateException异常。(notify()方法表示,当前的线程已经放弃对资源的占有, 通知等待的线程来获得对资源的占有权,但是只有一个线程能够从wait状态中恢复, 然后继续运行wait()后面的语句; notifyAll()方法表示,当前的线程已经放弃对资源的占有, 通知所有的等待线程从wait()方法后的语句开始运行。 )
wait() 和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-
synchronized block中进行调用,虽然能编译通过,但在运行时会发生 IllegalMonitorStateException的异常。
3.yield方法
暂停当前正在执行的线程对象。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能
在进入到可执行状态后马上又被执行。yield()只能使同优先级或更高优先级的线程有执行的机会。
4.join方法
等待该线程终止。等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,
main则会执行完毕,导致结果不可预测。
线程的同步:
一个Java程序的多线程之间可以共享数据。当线程以异步方式访问共享数据时,有时候是不安全的或者不和逻辑的。
比如,同一时刻一个线程在读取数据,另外一个线程在处理数据,当处理数据的线程没有等到读取数据的线程读取完毕
就去处理数据,必然得到错误的处理结果。
Java 编程语言提供了一种简单的机制来防止发生这种覆盖。每个对象在运行时都有一个关联的锁。这个锁可通过为方
法添加关键字 synchronized 来获得。
同步有两种方式:
同步块:对易发生冲突的语句块加锁
同步方法:对易发生冲突的方法块加锁
分别实例如下:
class SellThread implements Runnable { int tickets = 100; Object obj = new Object(); public void run() { while (true) { synchronized (obj) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + " sell tickets:" + tickets); tickets--; } } } } }
class SellThread implements Runnable{ int tickets=100; public void run(){ sell(); } public synchronized void sell(){ while(true){ if(tickets>0){ System.out.println(Thread.currentThread().getName()+ " sell tickets:"+tickets tickets--; } } } }