为完成特定的功能、使用计算机语言编写的一组指令的集合,即指一段静态的代码。
进程是资源(CPU、内存等)分配的最小单位,指正在运行的程序,程序运行时就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
一个正在运行的软件(360安全卫士)就是一个进程,一个进程可以执行多个任务(360安全卫士可以同时进行木马查杀、电脑清理、系统修复等任务,每一个任务就是一个线程),可以简单的认为进程是线程的集合。
进程可进一步细化为线程,线程是程序执行时的最小单元(任务),是操作系统进行任务调度的最小单位,隶属于进程。
一个程序就是一个进程,而一个程序中的多个任务则被称为线程。进程是表示资源分配的基本单位,又是调度运行的基本单位。
一个进程可以包含多个线程,一个线程只能属于一个进程,线程不能脱离进程而独立运行;
每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序, java程序的入口main()方法就是在主线程中被执行的。
在主线程中可以创建并启动其它的线程;
一个进程内的所有线程共享该进程的内存资源。
例如:假如把上课的过程比作进程,把老师比作CPU,那么每个学生就是一个线程,所有学生共享这个教室(也就是所有线程共享进程的资源),上课时学习A向老师提问,老师对A进行解答,此时可能会有学生B对来世的解答不懂再次提出疑问,(注意:此时老师可能还没有解答完A同学的问题),此时老师又向学生B解惑,解释完之后又继续回答学生A的问题,同一时刻老师只能向一个学生回答问题(即,多个线程在运行时,同一个CPU在某一时刻只能服务于一个线程,可能给一个线程分配的时间还不够这个线程执行完就轮到其他线程执行了,这样多个线程在来回的切换执行)。
在Java中要实现线程,最简单的方式就是扩展Thread类,重写其中的run方法,方法原型如下:
Thread类中的run方法本身并不执行任何操作,如果我们重写了run方法,当线程启动时,它将执行run方法。
定义:
public class MyThread extends Thread {
public void run(){
}
}
调用:
MyThread thread = new MyThread();
thread.start();
案例:
package com.ffyc.javathread.demo2;
/*
自定义线程 继承Thread类
Thread类是java提供的对线程进行管理的类
*/
public class MyThread extends Thread{
/*
在线程中要执行的任务都写在run方法中
*/
@Override
public void run() {
//要执行的任务
for (int i = 0; i <1000 ; i++) {
System.out.println("MyThread:"+i);
}
}
//测试
public static void main(String[] args) {
//创建线程对象
MyThread myThread = new MyThread();
//myThread.run(); 调用run(),不能启动线程,就是普通的方法调用
myThread.start();//启动线程
}
}
java.lang.Runnable接口中仅仅只有一个抽象方法:
public void run()
也可以通过实现Runnable接口的方式来实现线程,只需要实现其中的run方法即可;
Runnable接口的存在主要是为了解决Java中不允许多继承的问题。
实现Runnable接口的方式:
定义:
public class MyThread implements Runnable{
@Override public void run() {
……
}
}
线程执行任务:
MyThread r = new MyThread(); //创建一个线程作为外壳,将r包起来
Thread thread = new Thread(r);
thread.start();
案例:
package com.ffyc.javathread.demo3;
/*
实现Runnable接口 线程任务类
以后常用的方式, 避免直接继承Thread类,导致我们自己的类,不能再继承其他类
*/
public class PrintNum implements Runnable{
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
//测试
public static void main(String[] args) {
//创建一个线程要执行的任务
PrintNum p = new PrintNum();
//创建线程
Thread thread1 = new Thread(p);
thread1.start();
Thread thread2 = new Thread(p);
thread2.start();
}
}
区别:
继承Thread: 把run方法写在Thread子类中
实现Runnable:把run()方法写到接口中然后再用Thread类来包装
最终都是调用Thread类的start()方法来启动线程的。
实现Runnable的好处:
1)避免了单继承的局限性
因java是单继承,继承Thread类创建线程,就没办法继承其他类了,不够灵活,实现Runnable接口来创建线程的方式就避免了单继承的局限性,所以平常常用第二种方式。
2)多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
方 法 | 说 明 |
---|---|
public Thread(); | 创建线程 |
public Thread(String name); | 创建线程,并指定名称 |
public Thread(Runnable target); | 创建线程,并指定任务 |
public Thread(Runnable target, String name); | 创建线程,并指定任务,定义线程名称 |
方 法 | 功 能 |
---|---|
public void run() | 线程任务执行的方法,和启动线程没有关系 |
public synchronized void start() | 启动线程 |
public static native Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
final String getName() | 返回线程的名称 |
setName() | 设置线程的名称 |
public final void setPriority(int newPriority) | 设置线程的优先级,优先级默认为5,最大是10,最小是1,设置优先级后,不一定优先级高的就先执行,需要操作系统调用。 |
final int getPriority() | 返回线程优先级 |
public static void sleep(long millis) throws InterruptedException | 让当前正在执行的线程休眠指定的时间(毫秒),由活跃状态改为挂起状态 |
public static native void yield() | 线程让步,主动让出CPU执行权,可以直接进入到就绪队列中,等待CPU再次调度 |
public final void join() throws InterruptedException | 等待线程终止 |
案例:
package com.ffyc.javathread.demo4;
public class PrintNum implements Runnable{
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
if(i%10==0){
Thread.yield();//线程让步, 主动让出cpu执行权,可以直接进入到就绪队列中
}
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);//让线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
//创建一个线程要执行的任务
PrintNum printNum = new PrintNum();
//创建线程
Thread thread = new Thread(printNum,"线程1");
//thread.setName("线程A");//设置线程名称
thread.start();//启动线程
Thread thread1 = new Thread(printNum,"线程2");
//thread.join();//等待该线程结束
thread1.start();
//thread.setPriority(10);//设置线程的优先级 线程优先级默认是5 最大是10, 最小是1
//thread1.setPriority(1);//设置优先级后,不一定优先级高的,每次都是优先执行,需要操作系统调用
System.out.println(Thread.currentThread().getPriority());//获取优先级
}
}
注意:start()启动一个线程,线程之间是没有顺序的,是按CPU分配的时间片来回切换的。
● 事实上,计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务;
● 优先级较高的线程有更多获得CPU的机会,反之亦然;
● 优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或返回优先级;
调度策略
时间片
抢占式:高优先级的线程抢占CPU
Java的调度方法
同优先级线程组成先进先出队列,使用时间片策略
对高优先级,使用优先调度的抢占式策略
Thread类有如下3个静态常量来表示优先级
① MAX_PRIORITY:取值为10,表示最高优先级。
②MIN_PRIORITY:取值为1,表示最底优先级。
③NORM_PRIORITY:取值为5,表示默认的优先级。
public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;
新建(new):当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态 ,创建完成后就需要为线程分配内存。
就绪(runnable):处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 。
运行(running):当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能。
阻塞(blocked):在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态。
死亡(terminated):线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
Java中的线程分为两类:用户线程和守护线程
任何一个守护线程都是整个JVM中所有非守护线程的保姆,只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
守护线程的作用:
为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC(垃圾回收器),它就是一个很称职的守护者。
用户线程和守护线程两者几乎没有区别
唯一的不同之处就在于虚拟机的离开:
如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了,因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
设置守护线程: setDaemon(boolean on)
注意:设置线程为守护线程必须在启动线程之前,否则会抛出一个IllegalThreadStateException异常
案例:
①创建一个用户线程:
public class UserThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
②创建一个守护线程:
public class DaemonThread extends Thread{
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是守护线程,默默守护其他线程");
}
}
}
③测试:
public class Test {
public static void main(String[] args) {
DaemonThread daemonThread = new DaemonThread();
daemonThread.setDaemon(true);//设置线程为守护线程,当其他用户线程结束后,守护线程自动结束,必须在启动之前设置
daemonThread.start();
UserThread userThread = new UserThread();
userThread.start();
}
}
运行结果:由图可以看出,当用户守护线程结束时,守护线程随着JVM一同结束工作。
多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务(一个程序内部,可以同时执行多个任务)。
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
优点:
①提高了程序的处理能力和响应速度;
②提高了CPU的利用率,压榨硬件价值;
③改善了程序结构,将复杂的任务分为多个线程,独立运行。
缺点:
①线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;多线程需要协调和管理,所以需要CPU时刻跟踪线程;因此,对内存、CPU的要求也就提高了;
②多个线程对同一个共享资源访问的时候会出现问题。
例如:卖票、龟兔赛跑问题
/*
龟兔赛跑
*/
public class Game implements Runnable {
boolean flag = true;//记录比赛是否结束
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
//设置一个标记,只有第一个跑完的人获胜,不能说第二个跑完的人也胜利,但是现在的CPU都是多核的,乌龟和兔子有可能同时满足条件,就会出现乌龟和兔子都胜利的情况
if (i==1000){
//synchronized (this){ 此处加锁处理
while (flag) {
System.out.println(Thread.currentThread().getName() + "胜利");
flag = false;
}
}
}
}
}
//}
测试:
public class Test{
public static void main(String[] args) {
//创建一个线程要执行的任务
Game game = new Game();
//创建兔子线程
Thread thread1 = new Thread(game,"兔子");
thread1.start();
//创建乌龟线程
Thread thread2 = new Thread(game,"乌龟");
thread2.start();
}
}
运行结果可能出现下图所示情况:
这个时候我们就需要对共享资源的访问加"锁"来进行处理。
并行:多个CPU同时执行多个任务(即,在同一个时间节点上,多个事情依次进行);
比如:多个人同时做不同的事
并发:在一个时间段内一次执行操作(即,在一个时间段内,多个事情依次执行);
例如:卖票,抢购,秒杀看似同时进行,实际是一个一个执行
多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有先来后到;
同步就是排队+锁:
几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作;
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制。
确保一个时间点只有一个线程访问共享资源,给共享资源加一把锁,哪个线程获取了这把锁,才有权利访问该共享资源。
在Java代码中实现同步:使用synchronized(同步锁)关键字同步方法或代码块。
①同步代码块:
synchronized (同步锁){ //需要被同步的代码; }
②同步方法:放在方法声明中,表示整个方法为同步方法。
public synchronized void show (String name){ // 需要被同步的代码; }
同步锁
同步锁可以是任何对象,必须唯一,保证多个线程获得是同一个对象(用来充当锁标记)
同步执行过程:
第一个线程访问,锁定同步对象,执行其中代码;
第二个线程访问,发现同步对象被锁定,无法访问;
第一个线程访问完毕,解锁同步对象;
第二个线程访问,发现同步对象没有锁,然后锁定并访问。
注意:
一个线程持有锁会导致其他所有需要此锁的线程挂起,在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
public class TicketThread extends Thread{
//保证两个线程拿到的是同一份资源
static int num = 10;
@Override
public void run() {
while (true){
if (num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}else {
break;
}
}
}
}
测试:
public class Test {
public static void main(String[] args) {
//创建线程
TicketThread t1 = new TicketThread();
//为线程设置名称
t1.setName("窗口1");
TicketThread t2 = new TicketThread();
t2.setName("窗口2");
//启动线程
t1.start();
t2.start();
}
}
有可能出现下图的问题:
①同步代码块的方式处理
public class TicketThread extends Thread{
//保证两个线程拿到的是同一份资源
static int num = 10;
//保证同步锁里的是同一个对象
static String s = new String();
public void run() {
while (true){
/*
synchronized 修饰的代码称为同步代码块
synchronized(唯一的一个对象,可以是任意对象,此对象是用来记录有没有线程进入进入同步代码块)
*/
synchronized (s){
if (num>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}else {
break;
}
}
}
}
}
②同步方法的方式处理
public class TicketThread extends Thread{
//保证两个线程拿到的是同一份资源
static int num = 10;
@Override
public void run() {
while (true) {
if (num <= 0) {
break;
}
this.print();
}
}
/*
synchronized修饰非静态方法时,锁对象时this
synchronized修饰静态方法时,锁对象是当前类的Class对象
*/
public static synchronized void print(){
if (num>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}
}
}
测试:
public class Test {
public static void main(String[] args) {
//创建线程
TicketThread t1 = new TicketThread();
//为线程设置名称
t1.setName("窗口1");
TicketThread t2 = new TicketThread();
t2.setName("窗口2");
//启动线程
t1.start();
t2.start();
}
}
运行结果:
public class PrintTicket implements Runnable {
//打印票的对象就只有一份,所以不用static修饰
int num = 10;
@Override
public void run() {
while (true) {
synchronized (this) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num--;
} else {
break;
}
}
}
}
}
public class PrintTicket implements Runnable {
//打印票的对象就只有一份,所以不用static修饰
int num = 10;
@Override
public void run() {
while (true) {
if (num <= 0) {
break;
}
this.print();
}
}
public synchronized void print(){
if (num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}
}
}
测试:
public class Test {
public static void main(String[] args) {
PrintTicket p = new PrintTicket();
Thread thread1 = new Thread(p,"窗口1");
thread1.start();
Thread thread2 = new Thread(p,"窗口1");
thread2.start();
}
}
运行结果:
从JDK 5.0开始,Java提供了更强大的线程同步机制-通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
案例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PrintTicket implements Runnable {
int num = 10;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();//获取锁
if (num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}else {
break;
}
}finally {
lock.unlock();//释放锁
}
}
}
}
测试:
public class Test {
public static void main(String[] args) {
PrintTicket p = new PrintTicket();
Thread thread1 = new Thread(p,"窗口1");
thread1.start();
Thread thread2 = new Thread(p,"窗口1");
thread2.start();
}
}
测试结果:
ReentrantLock 和 synchronized 区别:
①synchronized是关键字,实现是依靠底层编译后的指令来控制的,而ReentrantLock是java.util.concurrent.locks包下的一个类,是依靠java代码实现控制。 ②synchronized可以修饰代码块和方法,而ReentrantLock只能修饰代码块 ③synchronized是隐式锁,自动添加锁,同步代码块执行完毕或者出现异常,锁会自动释放,而ReentrantLock是手动添加,手动释放。
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁,出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
案例:中国人和美国人吃饭
正常情况:中国人:两个筷子,美国人:刀和叉
特殊情况:中国人:一个筷子,一把刀;美国人:一个筷子,一把叉
设计时考虑清楚锁的顺序,尽量减少嵌套的加锁交互数量。
案例:
public class DieLockThread extends Thread{
boolean flag;
Object objA = new Object();
Object objB= new Object();
public DieLockThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag){
synchronized (objA){
System.out.println("if objA");
synchronized (objB){
System.out.println("if objB");
}
}
}else {
synchronized (objB){
System.out.println("else objB");
synchronized (objA){
System.out.println("else objA");
}
}
}
}
}
测试:
public class Test {
/*
多个线程分别占用对方资源,等待对方释放资源
设计程序时需要考虑清除锁的顺序,尽量减少锁的嵌套使用
*/
public static void main(String[] args) {
new DieLockThread(true).start();
new DieLockThread(false).start();
}
}
有可能出现如图所示情况:程序不报错,一直死锁着
线程通讯指的是多个线程通过相互牵制,相互调度,即线程间的相互作用。
涉及三个方法:
方 法 | 说 明 |
---|---|
wait() | 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。 |
notify() | 一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个。 |
notifyAll() | 一旦执行此方法,就会唤醒所有被wait的线程。 |
注意:
wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
案例:两个线程交替打印1-10之间的数字
public class PrintNum implements Runnable{
int i = 0;
@Override
public void run() {
while (true){
synchronized (this){
this.notify();//唤醒等待的线程
System.out.println(Thread.currentThread().getName()+":"+i++);
if (i>=10){
break;
}
try {
this.wait();//必须使用该对象来电泳wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
测试:
public class Test {
public static void main(String[] args) {
PrintNum printNum = new PrintNum();
Thread t1 = new Thread(printNum);
t1.start();
Thread t2 = new Thread(printNum);
t2.start();
}
}
运行结果:
经典例题:生产者/消费者问题
生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台处取走产品,生产者一次只能生产固定数量的产品(比如:1),这时柜台中不能再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,费者拿走产品后,唤醒生产者,消费者开始等待。
柜台:
public class Count {
int num = 1;
public synchronized void add() {
if (num==0){
this.num=1;
System.out.println("生产者生产了一件商品");
this.notify();
}else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void sub() {
if (num>0){
this.num = 0;
System.out.println("消费者购买了一件商品");
this.notify();
}else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生产者:
public class Producter extends Thread{
private Count count;
public Producter(Count count){
this.count = count;
}
@Override
public void run() {
while (true){
count.add();
}
}
}
消费者:
public class Customer extends Thread{
private Count count;
public Customer(Count count){
this.count = count;
}
@Override
public void run() {
while (true){
count.sub();
}
}
}
测试:
public class Tset {
public static void main(String[] args) {
Count count = new Count();
new Producter(count).start();
new Customer(count).start();
}
}
运行结果:
实现Callable接口与使用Runnable相比,Callable功能更强大些.
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,获取返回结果:
接收任务
FutureTask futureTask = new FutureTask(任务);
创建线程
Thread t = new Thread(futureTask);
t.start();
Integer val = futureTask.get();//获得线程call方法的返回值
案例:
import java.util.concurrent.Callable;
public class Sum implements Callable {
@Override
public Integer call() throws Exception {
int i = 0;
for (int j = 0; j < 5; j++) {
i+=j;
}
return i;
}
}
测试:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) {
Sum sum = new Sum();
FutureTask futureTask = new FutureTask(sum);
Thread t = new Thread(futureTask);
t.start();
Integer integer = null;
try {
integer = futureTask.get();
System.out.println(integer);
}catch (InterruptedException e){
e.printStackTrace();
}catch (ExecutionException e) {
e.printStackTrace();
}
}
}
运行结果: