主线程与线程的生命周期
每个java程序都有一个默认的主线程,对于应用程序来说,主线程是main()方法执行的线程,对于小应用程序来说,主线程指挥浏览器加载并执行JavaApplet。要想实现多线程,必须在主线程中创建新的线程对象。java语言,使用Thread类及其子类的对象来实现线程,新建的线程都有一个完整的生命周期。
(1)创建状态。即通过线程子类的构造方法创建线程对象,或者通过实现Runnable接口类对象创建一个新的线程。
(2)就绪状态。线程被创建以后,将进入线程的队列排队,如果cpu资源空闲,他就可以脱离创建他的主线程独立,另外,被阻塞的线程解除阻塞以后也进入了就绪状态。
(3)运行状态。当就绪状态的线程被调度,并且有cpu资源可以利用时,该线程就进入了运行状态,(4)挂起状态。一个正在运行的线程A可能因为某种原因被阻塞,就需要挂起,把资源让给级别高的的线程,直到cpu资源有空闲时,线程A的阻塞被解除,才能继续运行。
(5)终止状态。当一个线程正常运行完成了他的全部工作,或者被强制结束,它的生命就结束了,这时的线程处于终止状态。
线程在各个状态之间的转化及线程生命周期的演进是由系统运行状况,同时存在的其他线程和线程本身的算法共同决定的,在创建和使用线程时应注意利用现成的方法宏观的控制这个过程。
线程状态控制方法
(1)挂起一个线程。如果要暂停一个线程可以使用supend()方法,在程序运行中可能需要挂起一个线程而不指定多长时间。用resume()方法来恢复线程的执行。
(2)停止线程。用stop()方法可以停止线程的执行,但并没有消灭这个线程,只是停止这个线程的执行。
(3)线程休眠。如果希望县城修炼一段时间,可以使用sleep(long)的方法。
(4)停暂线程。如果想暂停当前运行的线程可以使用yield()方法。
(5)中断线程。如果要中断一个运行中的线程可以用interrupt()方法,若要运行已中断的线程可以使用start()重新启动。
(6)连接线程。join()方法可以将一个线程加入当前线程中,当前线程要等待加入的线程完成之后才执行。
synchronized关键字及线程同步
有时运行一些县城需要共享数据,例如,两个线程,同时存取一个数据流,其中一个对数据进行了修改,而另一个线程使用的是原来的数据,这就带来了数据不一致的问题,如果多线程同时操作一个对象,则称该对象不是线程安全的,为了使多线程机制能够正常运转,需要采取一些措施来防止两个线程访问相同资源的冲突,特别是关键的时期,为了防止出现这样的冲突,需要在线程使用一个资源时为其加锁。访问资源的第一个线程加上锁以后,其他线程就不能再使用这个资源了,除非第一个线程被解锁。
对一种特殊资源,java提供了内建的机制来防止他们的冲突,这种机制就是对相关的方法使用关键字synchronized。
为了协调和同步线程对共享数据的操作,java.lang.Object类中提供了wait()方法和notify()方法实现线程的同步,这两个方法始终在循环中使用,不带参数的wait()方法一直等待,单位为毫秒,达到参数指定的时间后,线程又成为了就绪状态,如果没有达到指定的时间,则需要其他程序调用notify()方法将其唤醒。
使用wait()方法和notify()方法,同时配合条件检查可以有效控制多线程之间的运行顺序,实现线程之间的同步。
Time类通过实现接口Runnable创建线程。run()方法规定了线程要执行的任务,通过线程对象的题start()来启动线程。程序中还调用了sleep()方法,用于让程序休眠一定的时间,休眠结束后,系统发出一个Interrupted Exception异常,通知休眠结束。
代码及成果展示
demo01
package demo01;
public class Demo01Ticket {
public static void main(String[] args) {
RunnableImpl run =new RunnableImpl();
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
package demo01;
/*
* 解决线程安全问题的二种方案:使用同步代码块
* 使用步骤:
* 1.把访问了共享数据的代码块取出来,放到一个方法中
* 2.在方法上添加synchronized修饰符
* */
public class RunnableImpl implements Runnable {
private int ticket =100;
@Override
public void run() {
while(true) {
while(true) {
payTicket();}
}
}
public synchronized void payTicket() {
if(ticket>0) {
try {
Thread.sleep(10);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
demo02
package demo02;
public class Demo02Ticket {
public static void main(String[] args) {
RunnableImpl run =new RunnableImpl();
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
package demo02;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 解决线程安全问题的二种方案:使用lock锁
* 使用步骤:
* 1. void lock()获取锁
* 2. void unlock()释放锁
*
* */
public class RunnableImpl implements Runnable {
private int ticket =100;
//1.在成员位置创建一个ReentrantLock对象
Lock l=new ReentrantLock();
@Override
public void run() {
while(true) {
//2.在可能会出现线程安全的地方,调用Lock接口中的lock方法,获取锁
l.lock();
if(ticket>0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}catch(InterruptedException e) {
e.printStackTrace();
}finally {
//3.在可能会出现线程安全的地方,调用unLock 释放锁
l.unlock();
}
}
}
}
}
demo03
package demo03;
/*
* 等待唤醒案例:
* 创建一个顾客线程,告知老板要的包子种类,用wait()方法等待,放弃cpu执行
* 创建一个老板线程,花五秒做一个包子,做好之后,用notif通知顾客吃包子
*
* */
public class WaitAndNotify{
public static void main(String[] args) {
Object obj=new Object();
//创建第一个消费者
new Thread() {
@Override
public void run() {
while(true) {
synchronized(obj){
System.out.println("消费者1,告知老板包子要的种类");
try {
obj.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者1,包子已经做好了,开吃");
System.out.println("=========");
}
}
}
}.start();
new Thread() {
@Override
public void run() {
while(true) {
synchronized(obj){
System.out.println("消费者2,告知老板包子要的种类");
try {
Thread.sleep(2000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者2,包子已经做好了,开吃");
System.out.println("=========");
}
}
}
}.start();}
}