线程学习
多线程:
题目一:火车站三个窗口各有100张票出售
{
private int ticket = 100;
public Test(String threadName) {
super(threadName);
}
public void run()
{
//开始卖票
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "还有剩余票" + ticket--);
}
}
/**
* @param args
*/
public static void main(String[] args)
{
//结果显示这三个线程并不是依次交替执行,而是在三个线程同时被执行的情况下,
//有的线程被分配时间片的机会多,票被提前卖完,而有的线程被分配时间片的机会比较少,票迟一些卖完。
//可见,利用扩展Thread类创建的多个线程,虽然执行的是相同的代码,但彼此相互独立,
//且各自拥有自己的资源,互不干扰。
Test t1 = new Test("窗口1");
Test t2 = new Test("窗口2");
Test t3 = new Test("窗口3");
t1.start();
t2.start();
t3.start();
}
}
利用多线程去执行相同的代码,结果显示这三个线程并不是依次交替执行,而是在三个线程同时被执行的情况下,有的线程被分配时间片的机会多,票被提前卖完,而有的线程被分配时间片的机会比较少,票迟一些卖完。可见,利用扩展Thread类创建的多个线程(还有一种方法是实现接口Runnable),虽然执行的是相同的代码,但彼此相互独立,且各自拥有自己的资源,互不干扰。
题目二:火车站三个窗口共有100张票出售
1、先将上面的改成实现接口Runnable
2、共用ticket=100这个字段,只要参数传递同一个对象就OK,main函数如下
{
Test target = new Test();
Thread t1 = new Thread(target, "窗口1");
Thread t2 = new Thread(target, "窗口2");
Thread t3 = new Thread(target, "窗口3");
t1.start();
t2.start();
t3.start();
}
线程的run()方法执行完毕时,该线程就被视为进入了消亡状态。一个处于消亡状态的线程不能再进入其他状态,即使对它调用 start()方法也无济于事。可以通过两种方法使线程进入消亡状态:自然撤消(线程执行完)或是强行中止,要终止一个线程,需要通过方法interrupt()来实现。
题目三:线程1执行完后再去执行线程2
(1)调用线程类的isAlive()方法和sleep()方法来等待某个或某些线程结束
t1.start();
while (t1.isAlive()) {
try {
Thread.sleep(3000);//主线程休息
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t2.start();
(2)调用线程类的join()方法来等待某个或某些线程结束
Thread类中的join()方法也可以用来等待一个线程的结束,而且这个方法更为常用,它的语法格式如下所示:
public final void join() throws InterruptedException |
代码如下:
t1.start();
try {
t1.join(); //当前线程等待线程t1 执行完后再继续往下执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
其他:
线程分为两类:用户线程和守护线程(又称为后台线程)。用户线程是那些完成有用工作的线程,也就是前面所说的一般线程。守护线程是那些仅提供辅助功能的线程。这类线程可以监视其他线程的运行情况,也可以处理一些相对不太紧急的任务。在一些特定的场合,经常会通过设置守护线程的方式来配合其他线程一起完成特定的功能。
线程终止
通过对结果的分析,可以发现,用户线程在调用了interrupt()方法之后并没有被中断,而是继续执行,直到人为地按下Ctrl+C或者Pause键为止。这个例子说明一个事实,直接使用interrput()方法并不能中断一个正在运行的线程。它可以使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态,退出堵塞代码。
具体步骤
在这种方式中,之所以引入共享变量,是因为该变量可以被多个执行相同任务的线程用来作为是否中断的信号,通知中断线程的执行。下面通过在程序中引入共享变量来改进代码如下所示:
{
boolean stop = false; // 引入一个布尔型的共享变量stop
public void run()
{
while (!stop) // 通过判断stop变量的值来确定是否继续执行线程体
{
System.out.println(getName() + " is running");
try {
sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
System.out.println("Thread is exiting...");
}
}
class InterruptThreadDemo2
{
public static void main(String[] args) throws InterruptedException
{
MyThread m = new MyThread();
System.out.println("Starting thread...");
m.start();
Thread.sleep(3000);
System.out.println("Interrupt thread...");
m.stop = true; // 修改共享变量
Thread.sleep(3000); // 主线程休眠以观察线程中断后的情况
System.out.println("Stopping application...");
}
}
在使用共享变量来中断一个线程的过程中,线程体通过循环来周期性的检查这一变量的状态。如果变量的状态改变,说明程序发出了立即中断该线程的请求,此时,循环体条件不再满足,结束循环,进而结束线程的任务。
为了更加安全起见,通常需要将共享变量定义为volatile类型或者将对该共享变量的一切访问封装到同步的代码或者同步方法中去。
在多线程的程序中,当出现有两个或多个线程共享同一实例变量的情况时,每一个线程可以保持这个实例变量自己的私有副本,变量的实际备份在不同时间被更新。而问题就是变量的主备份总是需要反映它的当前状态,此时反而使效率降低。为保证效率,只需要简单地指定变量为volatile类型即可,它可以告诉编译器必须总是使用volatile变量的主备份(或者至少总是保持任何私有的备份和最新的备份一样,反之亦然)。同样,对主变量的访问必须同任何私有备份一样,精确地顺序执行。
4.5 什么是同步,如何在多线程间保持同步
什么是同步呢?当两个或多个线程需要访问同一资源时,它们需要以某种顺序来确保该资源某一时刻只能被一个线程使用的方式称为同步。(火车站售票、抢购)
(1)使用同步代码块
为了防止多个线程无序地访问共享资源,只需将对共享资源操作的关键代码放入一个同步代码块中即可。同步代码块的语法形式如下所示:
synchronized (Object)
{
// 关键代码
}
其中,Object是需要同步的对象的引用。一般为this
(2)使用同步方法
同步方法和同步代码块的功能是一样的,都是利用互斥锁实现关键代码的同步访问。
只不过在这里通常关键代码就是一个方法的方法体,此时只需要调用synchronized关键字修饰该方法即可。一旦被synchronized关键字修饰的方法已被一个线程调用,那么所有其他试图调用同一实例中的该方法的线程都必须等待,直到该方法被调用结束后释放其锁给下一个等待的线程。
public synchronized void sale() // 同步方法中的代码为关键代码
{
}
也就是给方法加个标记
在处理线程同步时还需要注意一个问题,那就是死锁。死锁是多线程程序最常见的问题之一,那么什么是死锁,为什么会发生死锁呢?
死锁问题:即由于两个或多个线程都无法得到相应的锁而造成的两个线程都等待的现象。这种现象主要是因为相互嵌套的synchronized代码段而造成。
例如,在某一多线程的程序中有两个共享资源A和B,并且每一个线程都需要获得这两个资源后才可以执行。
线程1已经获取资源A的锁,由于某种原因被阻塞,此时线程2启动并获得资源B的锁,再去获得资源A的锁时发现线程1已经获取,因此等待线程1释放A锁。线程1从阻塞中恢复以后继续执行,欲获取资源B的锁,却发现B锁已被线程2获得,因此也陷入等待。在这种情况下,程序已无法向前推进,在没有外力的情况下,也不会自动退出,因而造成了严重的死锁问题。
4.6 多个线程之间如何进行通信
因为虽然synchronized关键字可以阻止并发更新同一个共享资源,实现了同步,但是它不能用来实现线程间的消息传递,也就是所谓的通信。其实,Java提供了3个非常重要的方法来巧妙地解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。
调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒。而调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。调用notifyAll()方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。