Java基础知识点总结--从细节再看Java(十一)--多线程开发
再说如何进行多线程开发之前,我们先来看看什么是线程,什么又是进程,两者有怎样的关系呢?
- 程序(Program):
计算机指令的集合,以文件形式存储在磁盘上。即指一段静态代码,静态对象。
- 进程(Process):
程序的一次动态执行过程, 占用特定的地址空间。在某种程度上进程是相互隔离、独立运行的程序。多任务操作系统将CPU时间动态地划分给每个进程,一次可同时执行多个进程,每个进程独立运行。
程序是静态的,进程是动态的。
- 线程(Thread):
线程是进程中一个“单一的连续控制流程” 或一段执行路径。一个进程可拥有多个并行的(concurrent)线程。一个进程中的线程共享相同的内存单元或内存地址空间,一个进程中的线程可以访问相同的变量和对象,实现线程间的通信、数据交换、同步操作。
单线程:安全性高,效率低。
多线程:安全性低,效率高。
一、实现多线程
实现多线程,我们依赖一个类Thread,Thread类实现了Runnable接口,创建新执行线程有两种方法。
Thread中有定义了许多方法:
String getName() : 返回线程名;
void SetName(String name) : 改变线程名称;
Thread(Runnable target):Thread的构造方法,获取Runnable的参数。
static Thread currentThread() :返回当前正在执行的线程对象的引用。
int getPriority() :返回线程的优先级
void setPriority(int newPriority) :更改线程的优先级。 优先级取值范围:1--10
boolean isAlive() :测试线程是否处于活动状态。
static void sleep(long millis) :在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
static void yield() :暂停当前正在执行的线程对象,并执行其他线程。
- 方法一
将类声明为 Thread
的子类。该子类应重写 Thread
类的 run
方法。接下来可以分配并启动该子类的实例。
先看第一步,创建一个Thread的子类并重写run方法:
class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++)
System.out.println(getName() + ":" + i);
}
}
重写的run方法中描述的是要放在线程中执行的代码块。
接着我们再创建MyThread的对象并启动多个线程:
public class ThreadDemo {
public static void main(String[] args) {
//创建线程对象
MyThread th = new MyThread();
MyThread th2 = new MyThread();
//为线程对象改名
th.setName("A");
th2.setName("B");
//start方法启动线程
th.start();
th2.start();
}
}
- 方法二
创建线程的另一种方法是:声明实现 Runnable
接口的类,该类实现 run
方法。创建该类的实例,在创建 Thread
时将该实例作为一个参数来传递并启动。
首先我们先创建一个实现Runnable接口的类并实现run方法:
class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++)
//这里使用Thread的静态方法currentThread()获取当前线程,再调用线程的getName()方法获取线程的名字
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
然后创建这个类的实例,并将其作为参数传入新创建的Thread对象中去,最后启动线程:
public class ThreadDemo {
public static void main(String[] args) {
// 创建线程实例,共享一个Runnable对象,如果有成员变量,其中的成员变量相同
MyThread2 mt = new MyThread2();
//创建 Thread 时将MyThread2的实例作为一个参数来传递
Thread t = new Thread(mt);
t.setName("A");
Thread t2 = new Thread(mt);
t2.setName("B");
// 启动线程
t.start();
t2.start();
}
二、多线程之间的同步方法
多线程虽然提高了程序的运行效率,同时也使得程序容易出现错误,安全性降低。当一个程序中有多个共享同一数据的线程时,这些线程并发访问这些共享的数据容易造成数据异常。
例如,出售火车票时,有多个窗口,我们把每一个窗口当作一个线程。如果我们不作任何处理,当票还剩最后一张时,线程A进来了,但此时它并没有立刻把票卖出去,而是去做别的事情。就在这个时候,线程B过来了,它一看票还剩最后一张也进来了,然后接着去做别的事情。这时线程A回来并把票卖出去了,此时票全卖完了,也就是0张票,之后再有线程来时发现票数<1就进不来了。但是,线程B已经获取了票的资源,当它处理完其他事情回来卖票时,票数又少了一张,即火车票成了-1张。而这就是线程并发访问时出现的问题。
怎么解决呢,我们需要一把锁,当一个线程进来获取资源之后,我们让它把锁锁上,这样当别的线程再来获取资源时,发现上锁了也就没法进来了,只好等上一个线程把锁释放之后才能进去获取资源。
那么在Java中我们要怎么实现呢,这是我们需要用一个关键字:synchronized。
synchronized:同步并修饰代码块或方法,被修饰的部分一旦被线程访问则直接锁死,防止其他线程访问。
同步代码块的实现方式:
synchronized(mutex){
...
}
//mutex需要被所有线程共享
同步方法的实现方式:
public synchronized void method(){
...
}
//非静态同步方法*用的锁对象是this
public static synchronized void method2(){
...
}
//静态方法优先于对象先加载,所以没有this
//静态同步方法*用的锁对象是当前类的字节码对象
我们来看一个简单的火车票出售同步程序
class TicketThread implements Runnable {
//预出售的火车票总数
int tickets = 100;
//共享的锁对象
Object obj = new Object();
@Override
public void run() {
//窗口一直开放
while (true) {
//同步代码块
synchronized (obj) {
//当票数>0时操作
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",tickets=" + tickets--);
}
}
}
}
}
public class TicketThreadTest {
public static void main(String[] args) {
//创建自定义类的对象
TicketThread tt = new TicketThread();
//创建三个线程,代表三个窗口,传入自定义类的对象
Thread t1 = new Thread(tt);
t1.setName("窗口1");
Thread t2 = new Thread(tt);
t2.setName("窗口2");
Thread t3 = new Thread(tt);
t3.setName("窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
三、线程的生命周期
生命周期即一个对象的“生老病死”。线程的生命周期主要是一下几步:
上一篇: Android操作Html打开其他APP
下一篇: Java实现AC自动机全文检索示例