线程
1、进程、线程的基本概念
进程:是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间)。比如用户点击ie浏览器,就启动了一个进程,操作系统就会为该进程分配独立的地址空间,当用户再次点击ie浏览器图标,就又启动了一个进程,操作系统将为新的进程分配新的独立的地址空间。
线程:是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程可以并发执行,线程有就绪、阻塞和运行三种基本状态。
总结:1. 线程是轻量级的进程
2. 线程没有独立的地址空间(内存空间)
3. 线程是由继承创建的(寄生在进程)
4. 一个进程可以拥有多个线程
2、线程的5种状态
线程从创建、运行到结束总是处于下面五个状态之一:
-
新建状态:当用new操作符创建一个线程时, 例如new Thread®,线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
-
就绪状态:一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。 -
运行状态:当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
-
阻塞状态:线程运行过程中,可能由于各种原因进入阻塞状态:
1>线程通过调用sleep方法进入睡眠状态;
2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3>线程试图得到一个锁,而该锁正被其他线程持有;
4>线程在等待某个触发条件;
……
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
-
死亡状态:有两个原因会导致线程死亡:
- run方法正常退出而自然死亡,
- 一个未捕获的异常终止了run方法而使线程猝死。
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.
新建状态 new
就绪状态 Runnable(可运行状态)
运行状态 Running
阻塞状态 Blocked
死亡状态 Dead
3、线程的使用
在java中一个类要当做线程来使用有两种方法。(开发线程的两种方式)
-
继承Thread类,并重写run函数。
-
实现Runnable接口,并重写run函数。
为什么要提供第二种方法呢?因为,比如你写的某个类已经继承了别的类,由于java中类只能单继承,所以这个类不就不能实现线程了吗。所以有了这种方法。
需求:请编写一个程序,该程序每隔一秒,在控制台输出: “hello world”。【拓展需求:当输出10次后,自动退出】
public class Demo1 {
/**
* 1. 继承Thread类,并重写run函数
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建一只猫
Cat cat=new Cat();
//启动线程,会导致run函数运行
cat.start();
}
}
//猫类
class Cat extends Thread{
//重写run函数
@Override
public void run() {
// TODO Auto-generated method stub
int times=0;//统计次数
while(true){
try {
//休眠1秒(1000毫秒=1秒)
//sleep就让该线程进入阻塞状态(Blocked),并释放资源
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
times++;
System.out.println("hello world"+times);
//打印10次,跳出线程
if(times==10){
break;//退出while循环
}
}
}
}
方法二:
public class Demo2 {
/**
* 2、实现Runnable接口,并重写run函数
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//实例化狗类
Dog dog=new Dog();
//创建一个Thread对象
Thread t = new Thread(dog);
t.start();
}
}
// 狗类
class Dog implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
int times = 0;// 统计次数
while (true) {
try {
// 休眠1秒(1000毫秒=1秒)
// sleep就让该线程进入阻塞状态(Blocked),并释放资源
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
times++;
System.out.println("hello world" + times);
// 打印10次,跳出线程
if (times == 10) {
break;// 退出while循环
}
}
}
}
4、多线程
需求:请编写一个程序,该程序可以接收一个整数n,创建一个线程,一个线程计算1+2+…+n,另一个线程每隔一秒在控制台输出,”我是线程,正在输出第xx个hello world”,xx要用具体的数字显示,这两个工作要求同时进行。
public class Demo3 {
/**
* 多线程:
* 1、一个计算1+2+...+n
* 2、我是线程,正在输出第xx个hello world
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//实例化Pig
Pig pig=new Pig(10);
//实例化Bird
Bird bird=new Bird(10);
//创建猪线程
Thread t1=new Thread(pig);
//创建鸟线程
Thread t2=new Thread(bird);
//启动两个线程
t1.start();
t2.start();
}
}
//鸟说hello world
class Bird implements Runnable{
int n=0;
int times = 0;// 统计次数
public Bird(int n){
this.n=n;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
// 休眠1秒(1000毫秒=1秒)
// sleep就让该线程进入阻塞状态(Blocked),并释放资源
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
times++;
System.out.println("我是线程,正在输出第"+times+"个 hello world");
// 打印n次,跳出线程
if (times == n) {
break;// 退出while循环
}
}
}
}
//猪算算术1+2+...+n
class Pig implements Runnable{
int n=0;
int result=0;//和
int times = 0;// 统计次数
public Pig(int n){
this.n=n;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
// 休眠1秒(1000毫秒=1秒)
// sleep就让该线程进入阻塞状态(Blocked),并释放资源
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
result+=(++times);
System.out.println("当前的结果:" + result);
// 打印n次,跳出线程
if (times == n) {
break;// 退出while循环
}
}
}
}
线程对象只能启动一个线程(不管是通过继承Thread,还是通过实现Runnable接口创建线程,它们的对象只能启动一次。否则就会抛出异常。)
public class Demo4 {
/**
* 功能:演示线程的注意事项
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
/*A a1=new A();
Thread t1=new Thread(a1);
t1.start();
t1.start();
*/
B b1=new B();
b1.start();
b1.start();
}
}
class A implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("我是线程1.。。");
}
}
class B extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("我是线程2.。。");
}
}
多线程例子:(请编写一个程序,模拟一个机票售票系统:有三个售票点,在一天卖出2000张票。(注意是一共卖出2000张票,不是每个售票点都卖2000张))
public class Demo5 {
/**
* 功能:模拟售票
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//定义售票窗口
TicketWindow tw1=new TicketWindow();
//创建三个线程
Thread t1=new Thread(tw1);
Thread t2=new Thread(tw1);
Thread t3=new Thread(tw1);
//启动三个线程
t1.start();
t2.start();
t3.start();
}
}
//窗口售票类
class TicketWindow implements Runnable {
//一共有2000张票
private int nums=2000;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//代码要保证原子性(同步代码块)
synchronized(this){
//出票速度是一秒出一张
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
//先判断是否还有票
if(nums>0){
//显示售票信息
System.out.println("在售出第 "+nums+" 张票"+"--"
+Thread.currentThread().getName());//得到当前线程名称
nums--;
}else{
//售票结束
break;
}
}
}
}