程序
是一段可执行的代码,由线程来执行的
一个程序要实现多个代码同时交替运行就需要产生多个线程
进程
执行中的程序(程序是静态的概念,进程是动态的概念)
线程
定义
在单个进程中独立执行的线索
主要功能
最大限度利用CPU资源,使程序同一时间执行不同任务
何时使用
一个对象要做出多个动作,并且多个动作需要穿插在一起时,就需要线程来编写程序
举个例子,一个厨师既要炒菜又要切菜,两个动作是交替进行的
线程与线程通信
共享一个变量,并对变量的访问进行同步
多个线程一起执行,线程的执行顺序没有保障,但每个线程都有独立的执行路径
线程的调度有的平台分时间片段轮流CPU资源,有的平台是抢占式
CPU每一个核同一时间可以处理一个线程,一个线程同一时间只能被一个核处理
当线程对象创建完成后,还只是一个普通对象,要想成为独立执行的线程必须调用start()
启动线程是先调用start()为线程的执行准备系统资源,然后再执行run()
进程与线程的关系
一个进程可以包含一个或者多个线程
线程本身不能运行,栖身于进程中,由进程启动而执行,完成任务后,自动终止,也可由进程强制终止
多进程
就是我们可以在计算机中同时运行两个或者两个以上的程序
我们可以打开一个记事本,再打开一个Excel
多线程
线程是最小的执行单位,这意味着一个程序可以同时执行多个任务的功能
Excel打印的同时,我们可以输入内容
多个进程的内存空间和系统资源是完全独立的;多线程是共享一块内存空间和一组系统资源,有可能相互影响
线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以进程之间的转换负担大,线程之间的转换负担小
实现多线程机制的两种方法
package com.itlwc;
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Thread() {
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("继承Thread类重写run()实现多线程运行");
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("实现Runnable接口中run()实现多线程运行");
}
}
});
t1.start();
t2.start();
}
}
/*
打印结果:
继承Thread类重写run()实现多线程运行
继承Thread类重写run()实现多线程运行
继承Thread类重写run()实现多线程运行
实现Runnable接口中run()实现多线程运行
实现Runnable接口中run()实现多线程运行
实现Runnable接口中run()实现多线程运行
*/
Thread常用方法
package com.itlwc;
public class Test {
public static void main(String[] args) throws InterruptedException {
// 获取返回对当前正在运行的线程对象的引用
Thread thread = Thread.currentThread();
// 返回当前线程的线程组中活动线程的数目
int ii = Thread.activeCount();
// 获取当前正在执行main()的线程id
long l = thread.getId();
// 获取当前正在执行main()的线程名称
String s = thread.getName();
// 获取当前正在执行main()的线程优先级
int i = thread.getPriority();
//返回该线程的状态
Thread.State state =thread.getState();
//中断线程
thread.interrupt();
//测试当前线程是否已经中断
Thread.interrupted();
//测试线程是否处于活动状态
thread.isAlive();
//测试该线程是否为守护线程
thread.isDaemon();
//等待该线程终止
thread.join();
//改变线程名称,使之与参数 name 相同
thread.setName("普通线程");
//将该线程标记为守护线程或用户线程
thread.setDaemon(true);
//更改线程的优先级
thread.setPriority(1);
//在指定的毫秒数内让当前正在执行的线程休眠
Thread.sleep(1000);
//暂停当前正在执行的线程对象,并执行其他线程s
Thread.yield();
}
}
线程的5种状态
新建状态(new)
当一个线程对象创建后,处于新建状态,在这种状态下,线程对象还只是一个对象,
没有成为一条独立执行的线索,也不可能被线程调度程序调度
准备状态(runnable)
处于新建状态下的线程被start()调用后进入准备状态,这个状态随时可能被线程调度程序调度,
获取CPU执行时间,线程一旦进入准备状态不能回到新建状态
运行状态(running)
一旦处于准备状态的线程获取CPU时间,就进入了执行状态,
线程随时可能被调度程序调度到准备状态,
于某种必要的条件或者由于运行特定的方法,可能会进入等待/阻塞状态
等待阻塞状态(blocked)
因为某种原因放弃了CPU使用权,暂时停止运行
直到线程进入就绪状态,才有机会转入运行状态
阻塞分为三种
等待阻塞
运行的线程执行wait()方法,JVM会把该线程放入等待池中
同步阻塞
运行线程在获取对象的同步锁时,若该同步锁被别的线程占用,JVM会把该线程放入锁池中
其他阻塞
运行线程执行了sheep()或者join()或者发生了I/O请求时JVM会将线程置为阻塞状态
当sheep()超时,join()等待线程终止或者超时,I/O处理完毕,线程重新进入就绪状态
死亡状态(dead):
当线程的run方法正确执行完毕或者由于发生了异常而终止了执行时,
线程就进入了死亡状态,进入死亡状态下的线程不能再被启动
线程优先级
package com.itlwc;
/*
设置优先级两种方法:
1~10 数值越大优先级越高
Thread.MIN_PRIORITY,Thread.MAX_PRIORITY,Thread.NORM_PRIORITY
优先级会继承的,父线程的优先级是多少,子线程的优先级就是多少
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Thread(){
//重写了父类Thread中的run()
public void run() {
System.out.println("优先级低后执行");
}
});
Thread t2 = new Thread(new Runnable(){
//实现Runnable接口中的run()
public void run() {
System.out.println("优先级高先执行");
}
});
// 设置优先级
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.NORM_PRIORITY);
t1.start();
t2.start();
}
}
/*
打印结果:
优先级低后执行
优先级高先执行
*/
sheep()与wait()
sleep()
方法签名
public static void sheep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠
public static void sheep(long millis,int nanos)
在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠
功能
让线程等待而停止执行,不释放对象的锁
wait()
方法签名
public final void wait()
导致当前的线程等待,直到其他线程调用此对象的notify()或notifyAll()
功能
让线程等待而停止执行,释放对象的锁
线程的让步
实际运行中有时需要当前运行线程让出CPU,让其他线程运行,这时就需要线程的让步
线程的让步包括两种方式
线程只是让出当前的CPU资源,具体让给谁不确定
线程将给指定的线程让步,指定的线程没有完成,其绝不恢复运行
使用yield()让步
yield()可以让当前线程让出CPU,回到准备状态,yield()让步不一定成功,
有可能线程回到准备状态又立刻被调度再次进入运行状态
public static void yield()
使用join()让步
当一个线程必须等待另一个线程执行完成之后才恢复执行时,才使用join()让步
public final void join()
另一个执行完成之后这个线程才恢复运行,相当于合并一个线程
public final void join(long millis)
另一个执行执行millis毫秒之后这个线程才恢复运行
public final void join(long millis,int nanos)
另一个执行执行millis毫秒nanos纳秒之后这个线程才恢复运行
三个方法都是final的,继承Thread类时不能重写join(),都会抛出InterruptedException
守护线程
Java根本没有单线程程序,即使只开发一个主线程,后台还有很多辅助线程,比如线程调度,内存管理等
这些在后台运行的线程称为守护线程
开发守护线程和普通线程区别不大,只要调用线程对象的setDaemon()进行设置即可
public final void setDaemon(boolean on)
最终方法,继承Thread类时不能被重写
参数on == true,守护线程
参数on == false,前台普通线程
如果所有前台线程执行完毕,守护线程即使没有执行完毕,程序也退出
案例
package com.itlwc;
class MyRunnable1 implements Runnable {
public void run() {
for (int i = 0; i < 30; i++) {
System.out.print("[" + i + "]");
}
System.out.print("前台用户线程执行完毕");
}
}
class MyDaemon implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.print("<" + i + ">");
}
System.out.print("后台守护线程执行完毕");
}
}
public class Test {
public static void main(String[] args) {
Runnable r1 = new MyRunnable1();
Runnable r2 = new MyDaemon();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t2.setDaemon(true);
t1.start();
t2.start();
}
}
线程的同步
多线程程序中,由于同时有多个线程并发运行,有时候带来严重问题,甚至引发错误
比如:一个银行帐号在同一时刻只能被一个用户操作,如果两个用户同时操作可能会产生错误
为了解决这些问题,在多线程开发中就需要使用同步技术
同步方法简介
同步方法是指用 synchronized 关键字修饰的方法
同步方法与普通方法不同的是,同步方法执行线程将获得同步方法所属对象的锁,
一旦对象被锁, 其他线程就不能执行,不能获得对象锁的线程进入对象的锁的等待池中
直到别的线程释放锁,其获得锁才能执行
synchronized 只能修饰方法,不能修饰变量
若线程获得锁后进入睡眠或让步,则将锁也带着睡眠或让步,这种做法将严重影响等待锁的线程执行
同步降低多线程的并发性,在一定程度上影响性能.因此,在不需要同步的时候,一定不能同步
线程同步调度的方法
public final void wait()
使得某一个线程进入该资源的等待池,直到别的线程调用该资源的notify()或notifyAll()
public final void wait(long timeout)
timeout表示毫秒
public final void wait(long timeout,int nanos)
timeout表示毫秒,nanos表示纳秒
public final void notify()
唤醒等待池中某一个线程,具体唤醒哪个没有保障
public final void notifyAll()
如果多个线程访问同一个资源,使用notifyAll()来唤醒全部等待线程,避免不必要的永远等待
线程的死锁
线程之间互相等待对方释放资源对象的锁,而每个线程又持有其他线程需要的锁,造成永久等待
案例
package com.itlwc;
class MyThread extends Thread {
// 需要访问的两个资源对象引用
private Object resource1;
private Object resource2;
public MyThread() {
}
public MyThread(Object resource1, Object resource2, String name) {
// 传递资源对象的引用
this.resource1 = resource1;
this.resource2 = resource2;
// 设置线程的名称
this.setName(name);
}
public void run() {
// 获得资源o1的锁,对资源o1进行操作
synchronized (resource1) {
System.out.println(this.getName()
+ "线程拿到" + resource1 + "对象的锁");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(this.getName()
+ "等待" + resource1 + "对象的锁释放");
// 在拿着资源o1锁的同时,需要拿到
// 资源o2的缩对两个资源同时进行操作
synchronized (resource2) {
System.out.println(this.getName()
+ "线程拿到了"+ resource2.toString() + "的锁");
System.out.println(this.getName()
+ "可以对两个资源同时操作了");
}
}
}
}
// 主类
public class Test {
public static void main(String args[]) {
// 创建3个资源对象
String s1 = "tom";
String s2 = "jerry";
String s3 = "lucy";
// 创建3个线程对象
MyThread mt1 = new MyThread(s1, s2, "MT1");
MyThread mt2 = new MyThread(s2, s3, "MT2");
MyThread mt3 = new MyThread(s3, s1, "MT3");
// 启动上述3个线程
mt1.start();
mt2.start();
mt3.start();
}
}
/*
打印结果:
MT1线程拿到tom对象的锁
MT3线程拿到lucy对象的锁
MT2线程拿到jerry对象的锁
MT1等待tom对象的锁释放
MT2等待jerry对象的锁释放
MT3等待lucy对象的锁释放
*/
同步语句块
同步方法在同一时刻只能锁住一个对象,但在并发执行中有时需要锁定的对象不止一个
案例
package com.itlwc;
//自定义的线程类
class MyThread extends Thread {
// 该引用为资源对象
private Object resource;
public MyThread() {
}
public MyThread(Object resource, String name) {
this.resource = resource;
this.setName(name);
}
public void run() {
synchronized (resource) {
System.out.println(this.getName() + "线程访问了资源");
try {
Thread.sleep(1000);
System.out.println(this.getName() + "线程带着锁睡觉去了");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(this.getName() + "线程带着锁睡醒后释放了锁");
}
}
}
// 主类
public class Test {
public static void main(String[] args) {
// 创建资源对象
Object resource = new Object();
// 创建2个线程
MyThread mt1 = new MyThread(resource, "MT1");
MyThread mt2 = new MyThread(resource, "MT2");
// 启动这2个线程
mt1.start();
mt2.start();
}
}
/*
打印结果:
MT1线程访问了资源
MT1线程带着锁睡觉去了
MT1线程带着锁睡醒后释放了锁
MT2线程访问了资源
MT2线程带着锁睡觉去了
MT2线程带着锁睡醒后释放了锁
*/