进程与线程
进程:是操作系统中正在运行中的程序,且一个进程中至少要有一个线程
线程:是进程中的正在运行的子程序流
线程消耗的资源比进程小。
多线程就是一个进程中存在多个子线程,开启多个线程是为了能够同时执行多个部分的代码,但其实是系统在快速的切换运行的线程,切换也是随机的,但是切换线程是需要消耗资源的,所以如果开启大量线程会导致效率降低,多线程的好处就是可以让几个部分的代码同时运行。
在Java中通过Runtime来调用进程。
Runtime.getRuntime.exec(程序名称);
其返回值类型就是Process类型
在一个进程中,可以分成主线程和子线程,只有在运行中的线程,才有机会执行代码,主线程的结束不会影响到其他正在运行的子线程,主线程停止了也就是main()方法运行结束,只有进程的所有线程都执行完毕,进程才会关闭,只要有一个线程正在运行,进程就不会退出,
线程的生命周期:
线程的生命周期可分为:创建---运行----等待-----终止
除了4种生命周期之外,线程还有7中状态分别是:创建状态-----可运行状态-----运行状态------阻塞状态-----等待状态------锁定状态-------终止状态
创建状态:及在代码中定义了一个子线程对象,但是并没有执行。
可运行状态:及调用了线程对象的start()方法,等待CPU调配资源。
运行状态:及获得了CPU的执行权,正在运行。
阻塞状态:及正在运行的线程调用sleep()方法,或者遇到了join()方法,进入了阻塞状态
等待状态:及正在运行的线程调用了wait()方法,使线程进入了等待队列中
锁定状态:及线程访问了带有锁的对象,并且改锁已经被其他线程持有,则线程进入锁定状态
终止状态:当线程中所以代码都被执行完毕时,或者人为控制条件使线程结束,线程被销毁,及线程终止
创建线程的两种方式:
在Java中创建线程有两种方式,及继承Thread类或者实现Runnable接口
方法一:继承Thread类
第一步:定义一个类继承Thread
第二步:重写Thread类中的run()方法
第三步:创建Thread子类的对象
第四步:调用线程对象的start()方法,开启线程(只有调用start()方法,才算是开启了子线程,如果直接调用run()方法,只是执行了方法,并不是开启线程)
示例代码:
public class Test {
public static void main(String[] args) {
//创建线程对象
ThreadTest thread=new ThreadTest();
//调用start()方法启动线程
thread.start();
}
}
//定义一个类,并且继承Thread类
class ThreadTest extends Thread{
//重写Thread类中的run()方法
public void run(){
System.out.println("我是子线程");
}
}
方法二:实现Runnable接口
第一步:定义一个类实现Runnable接口
第二步:实现Runnable接口中的run()方法
第三步:创建实现了Runnable接口的线程对象
第四步:因为Runnable接口本身并没有start()方法,所以得借助Thread对象来开启线程,创建一个Thread对象,将实现Runnable接口的线程对像传入到 Thread对象中
第五步:通过Thread对象的start();方法开启线程
示例代码:
public class Test {
public static void main(String[] args) {
//创建线程对象
ThreadTest thread=new ThreadTest();
//创建Thread对象,将thread以参数的形式,传入t中
Thread t=new Thread(thread);
//借助Thread对象的start();方法开启线程
t.start();
}
}
//定义一个类,并且实现Runnable接口
class ThreadTest implements Runnable{
//实现Runnable接口中的run()方法
public void run(){
System.out.println("我是子线程");
}
}
由上面的例子可见,由于使用Thread方式来创建线程比较局限,使得该类无法再继承其他类,而Runnable方式来创建线程可以避免单继承问题,也可以共享数据,一个Runnable对象可以被多个Thread对象调用,也就是可以在多线程共享数据。示例代码:
public class Test {
public static void main(String[] args) {
// 创建线程对象
ThreadTest thread = new ThreadTest();
// 一个Runnable对象可以被多个Thread对象调用
Thread t1 = new Thread(thread);
Thread t2 = new Thread(thread);
Thread t3 = new Thread(thread);
// 借助Thread对象的start();方法开启线程
t1.start();
t2.start();
t3.start();
}
}
// 定义一个类,并且实现Runnable接口
class ThreadTest implements Runnable {
// 实现Runnable接口中的run()方法
public void run() {
System.out.println("我是子线程");
}
}
Thread类的常用方法:
getName()
返回该线程的名称
join() 等待该线程终止
isAlive() 判断改程序是否处于活动状态
setName(String name) 设置该线程的名称
sleep(long time) 使该线程休眠指定的毫秒数
线程的安全问题
起因:当多个线程同时访问一个对象时,可能会出现线程并发问题。
解决方法:将可能产生并发问题的代码块,加上同步锁,使其在同一个时间只能由一个线程访问。
synchronized(加锁定的对象){
可能产生并发问题的代码块;
}
示例代码:
//模拟卖票的实例
public class Test {
public static void main(String[] args) {
// 创建线程对象
ThreadTest thread = new ThreadTest();
// 一个Runnable对象可以被多个Thread对象调用
Thread t1 = new Thread(thread);
Thread t2 = new Thread(thread);
Thread t3 = new Thread(thread);
// 借助Thread对象的start();方法开启线程
t1.start();
t2.start();
t3.start();
}
}
// 定义一个类,并且实现Runnable接口
class ThreadTest implements Runnable {
private int tick = 100;
public void run() {
while (true) {
if (tick > 0) {
try {
Thread.sleep(20);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + " " + tick--);
}
}
}
}
运行以上代码发现,会出现0张票和-1张票等结果,原因就是因为,当其中一个线程通过判断之后,还没来得及执行自减操作,另一个线程就抢夺到了CPU资源,因为这时候另一个线程还没自减,所以此时运行的线程也通过了判断,也就是说最后tick进行了多次自减操作,导致的票数出现0和-1张的情况,解决方案是,将判断语句中的代码加入到同步块中,示例代码:
//模拟卖票的实例
public class Test {
public static void main(String[] args) {
// 创建线程对象
ThreadTest thread = new ThreadTest();
// 一个Runnable对象可以被多个Thread对象调用
Thread t1 = new Thread(thread);
Thread t2 = new Thread(thread);
Thread t3 = new Thread(thread);
// 借助Thread对象的start();方法开启线程
t1.start();
t2.start();
t3.start();
}
}
// 定义一个类,并且实现Runnable接口
class ThreadTest implements Runnable {
private int tick = 100;
public void run() {
while (true) {
//加入同步代码块,锁定当前对象,及当线程访问到同步代码块时,
//如果当前对象的锁已经被其他线程持有,那该线程只能在同步代码块外等待。
synchronized (this) {
if (tick > 0) {
try {
Thread.sleep(20);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + " " + tick--);
}
}
}
}
}
线程的等待与唤醒
线程的等待及调用了wait()方法,唤醒及调用了notify()(唤醒最先等待的线程)或者notifyAll()(唤醒所有等待的线程)方法,两个方法必须在同步代码块中,示例代码:
//生产者与消费者
public class Test {
public static void main(String[] args) {
// 创建资源对象
Resource r = new Resource();
// 创建生产者对象
Input in = new Input(r);
// 创建消费对象
Output out = new Output(r);
// 创建线程,分别生产和消费资源
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
// 开启线程
t1.start();
t2.start();
}
}
// 生产者
class Input implements Runnable {
// 资源对象
Resource r;
public Input(Resource r) {
this.r = r;
}
// 生产资源
public void run() {
int x = 0;
while (true) {
if (x == 0) {
r.set("dmh", "男");
} else {
r.set("hyl", "女");
}
x = (x + 1) % 2;
}
}
}
// 消费者
class Output implements Runnable {
// 资源对象
Resource r;
public Output(Resource r) {
this.r = r;
}
// 消费资源
public void run() {
while (true) {
r.out();
}
}
}
// 资源
class Resource {
// 姓名
private String name;
// 年龄
private String sex;
// 线程结束的标识
private boolean flag = false;
// 同步方法,锁定的是当前对象
public synchronized void set(String name, String sex) {
if (flag)
try {
// wait()方法必须在同步代码块中
// 说明资源正在被消费,当前线程等待
this.wait();
} catch (InterruptedException e) {
}
this.name = name;
this.sex = sex;
flag = true;
// notify()方法必须在同步代码块中
// 唤醒等待队列的第一个线程
this.notify();
}
// 同步方法,锁定的是当前对象
public synchronized void out() {
if (!flag)
try {
// wait()方法必须在同步代码块中
// 说明资源正在生产,当前线程等待
this.wait();
} catch (InterruptedException e) {
}
flag = false;
// notify()方法必须在同步代码块中
// 唤醒等待队列的第一个线程
this.notify();
}
}