单线程
任何程序至少有一个线程,即使你没有主动地创建线程,程序从一开始执行就有一个默认的线程,被称为主线程,只有一个线程的程序称为单线程程序。如下面这一简单的代码,没有显示地创建一个线程,程序从main开始执行,main本身就是一个线程(主线程),单个线程从头执行到尾。
【Demo1】:单线程程序
public static void main(String args[]) {
System.out.println("输出从1到100的数:");
for (int i = 0; i < 100; i ++) {
System.out.println(i + 1);
}
}
创建线程
单线程程序简单明了,但有时无法满足特定的需求。如一个文字处理的程序,我在打印文章的同时也要能对文字进行编辑,如果是单线程的程序则要等打印机打印完成之后你才能对文字进行编辑,但打印的过程一般比较漫长,这是我们无法容忍的。如果采用多线程,打印的时候可以单独开一个线程去打印,主线程可以继续进行文字编辑。在程序需要同时执行多个任务时,可以采用多线程。
在程序需要同时执行多个任务时,可以采用多线程。Java给多线程编程提供了内置的支持,提供了两种创建线程方法:1.通过实现Runable接口;2.通过继承Thread类。
Thread是JDK实现的对线程支持的类,Thread类本身实现了Runnable接口,所以Runnable是显示创建线程必须实现的接口; Runnable只有一个run方法,所以不管通过哪种方式创建线程,都必须实现run方法。我们可以看一个例子。
【Demo2】:线程的创建和使用
/**
* 通过实现Runnable方法
*/
class ThreadA implements Runnable {
private Thread thread;
private String threadName;
public ThreadA(String threadName) {
thread = new Thread(this, threadName);
this.threadName = threadName;
}
//实现run方法
public void run() {
for (int i = 0; i < 100; i ++) {
System.out.println(threadName + ": " + i);
}
}
public void start() {
thread.start();
}
}
/**
* 继承Thread的方法
*/
class ThreadB extends Thread {
private String threadName;
public ThreadB(String threadName) {
super(threadName);
this.threadName = threadName;
}
//实现run方法
public void run() {
for (int i = 0; i < 100; i ++) {
System.out.println(threadName + ": " + i);
}
}
}
public class MultiThread{
public static void main(String args[]) {
ThreadA threadA = new ThreadA("ThreadA");
ThreadB threadB = new ThreadB("ThreadB");
threadA.start();
threadB.start();
}
}
说明:上面的例子中例举了两种实现线程的方式。大部分情况下选择实现Runnable接口的方式会优于继承Thread的方式,因为:
- 从 Thread 类继承会强加类层次;
- 有些类不能继承Thread类,如要作为线程运行的类已经是某一个类的子类了,但Java只支持单继承,所以不能再继承Thread类了。
线程同步
线程与线程之间的关系,有几种:
模型一:简单的线程,多个线程同时执行,但各个线程处理的任务毫不相干,没有数据和资源的共享,不会出现争抢资源的情况。这种情况下不管有多少个线程同时执行都是安全的,其执行模型如下:
模型二:复杂的线程,多个线程共享相同的数据或资源,就会出现多个线程争抢一个资源的情况。这时就容易造成数据的非预期(错误)处理,是线程不安全的,其模型如下:
在出现模型二的情况时就要考虑线程的同步,确保线程的安全。Java中对线程同步的支持,最常见的方式是添加synchronized同步锁。
我们通过一个例子来看一下线程同步的应用。
买火车票是大家春节回家最为关注的事情,我们就简单模拟一下火车票的售票系统(为使程序简单,我们就抽出最简单的模型进行模拟):有500张从北京到赣州的火车票,在8个窗口同时出售,保证系统的稳定性和数据的原子性。
【Demo3】:火车票售票系统模拟程序
/**
* 模拟服务器的类
*/
public class Service {
private String ticketName;// 票的名称
private int totalCount;// 票的总数
private int remaining;// 剩下票的数量
public Service(String ticketName, int totalCount) {// 构造方法(函数)
this.ticketName = ticketName;
this.totalCount = totalCount;
this.remaining = totalCount;
}
public int saleTicket(int ticketNum) {// 售票
if (remaining > 0) {// 有票时
remaining -= ticketNum;// 卖出去
try {
Thread.sleep(100);// 延迟0.1s,模拟实际
} catch (InterruptedException e) {
e.printStackTrace();
}
if (remaining >= 0) {// 还有票剩余
return remaining;
} else {
remaining += ticketNum;// 没票了,不能再买了
return -1;
}
}
return -1;
}
public String getTickerName() {// 获取票的名称
return this.ticketName;
}
public int getRemaining() {// 获取剩余的票数量
return this.remaining;
}
}
/**
* 售票程序
*/
public class TicketSaler implements Runnable {// 实现Runnable接口
private String nameString;// 名称,没用到
private Service service;// 新建服务对象
public TicketSaler(String windowName, Service service) {// 构造函数
this.nameString = windowName;
this.service = service;
}
public void run() {// 重写run方法
while (service.getRemaining() > 0) {// 当还有剩余的票时
synchronized (this) {// 同步锁
System.out.print(Thread.currentThread().getName() + "出售第 "// 买票信息
+ service.getRemaining() + " 张票");
int remaining = service.saleTicket(1);// 设置一次卖一张票
if (remaining >= 0) {
System.out.println("出票成功!剩余 " + remaining + " 张票");// 出票成功
} else {
System.out.println("出票失败!该票已售完");// 出票失败
// System.out.println("当前的线程信息 : "+Thread.currentThread());
// Thread.currentThread().interrupt();
}
}
}
}
}
测试程序:
/**
* 测试类
*/
public class TestDemo { // 测试类
public static void main(String args[]) {
Service service = new Service("长沙----》广州", 100);// 线程服务,火车票的行程
TicketSaler ticketSaler = new TicketSaler("售票程序", service);// 新建买票服务
Thread threads[] = new Thread[8];// 开启8个线程进程票务操作
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(ticketSaler, "窗口" + (i + 1));// 设置线程的名称
System.out.println("窗口" + (i + 1) + "开始出售 "// 打印出初始信息
+ service.getTickerName() + " 的票...");
// if (i == 2)//设置优先级
// threads[i].setPriority(Thread.MAX_PRIORITY);
threads[i].start();// 开启线程
}
}
}
结果如下:
窗口1开始出售 北京–>赣州 的票…
窗口2开始出售 北京–>赣州 的票…
窗口3开始出售 北京–>赣州 的票…
窗口4开始出售 北京–>赣州 的票…
窗口5开始出售 北京–>赣州 的票…
窗口6开始出售 北京–>赣州 的票…
窗口7开始出售 北京–>赣州 的票…
窗口8开始出售 北京–>赣州 的票…
窗口1出售第500张票,出票成功!剩余499张票.
窗口1出售第499张票,出票成功!剩余498张票.
窗口6出售第498张票,出票成功!剩余497张票.
窗口6出售第497张票,出票成功!剩余496张票.
窗口1出售第496张票,出票成功!剩余495张票.
窗口1出售第495张票,出票成功!剩余494张票.
窗口1出售第494张票,出票成功!剩余493张票.
窗口2出售第493张票,出票成功!剩余492张票.
窗口2出售第492张票,出票成功!剩余491张票.
窗口2出售第491张票,出票成功!剩余490张票.
窗口2出售第490张票,出票成功!剩余489张票.
窗口2出售第489张票,出票成功!剩余488张票.
窗口2出售第488张票,出票成功!剩余487张票.
窗口6出售第487张票,出票成功!剩余486张票.
窗口6出售第486张票,出票成功!剩余485张票.
窗口3出售第485张票,出票成功!剩余484张票.
……
在上面的例子中,涉及到数据的更改的Service类saleTicket方法和TicketSaler类run方法都用了synchronized同步锁进行同步处理,以保证数据的准确性和原子性。
关于synchronized更详细的用法请参见:《Java中Synchronized的用法》
优先级
线程优先级是指获得CPU资源的优先程序。优先级高的容易获得CPU资源,优先级底的较难获得CPU资源,表现出来的情况就是优先级越高执行的时间越多。
Java中通过getPriority和setPriority方法获取和设置线程的优先级。Thread类提供了三个表示优先级的常量:MIN_PRIORITY优先级最低,为1;NORM_PRIORITY是正常的优先级;为5,MAX_PRIORITY优先级最高,为10。我们创建线程对象后,如果不显示的设置优先级的话,默认为5。
【Demo】:线程优先级
/**
* 优先级
*/
public class PriorityThread implements Runnable {// 实现Runnable接口
@Override
public void run() {// 重写run方法
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread() + " : " + i);
}
}
}
调用代码:
public class Demo {
public static void main(String args[]) {
Thread t1 = new Thread(new PriorityThread(), "线程1");// 准备三个线程
Thread t2 = new Thread(new PriorityThread(), "线程2");
Thread t3 = new Thread(new PriorityThread(), "线程3");
t1.setPriority(Thread.MAX_PRIORITY);// 设置优先级
t2.setPriority(3);
// Thread.currentThread().setPriority(10);
// t1.setPriority(9);
// System.out.println(Thread.currentThread().getPriority());
t2.start();// 开启线程
t3.start();
t1.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread() + " : " + i);
}
}
}
从结果中我们可以看到线程thread1明显比线程thread1执行的快。
原博客链接:https://blog.csdn.net/luoweifu/article/details/46673975