一、多线程概述
1、要认识进程得从程序开始,程序是对数据描述与操作的代码的集合;
2、进程是程序的一次动态执行过程,他对应了代码加载、执行至执行完毕的一个完整过程;
3、进程的特点是:
(1)、进程是系统运行程序的基本单位;
(2)、每一个进程都有自己独立的一块内存空间,一组系统资源;
(3)、每一个进程的内部数据和状态都是完全独立的;
4、线程是进程中执行运算的最小单位,可完成一个独立的顺序控制流程;
5、每个进程中至少包含一个主线程来作为这个程序运行的入口点;
二、Java中实现多线程
1、Java在类和接口方面位多线程提供内置支持;
2、Java中通过Thread类将线程所必须的功能都封装了起来,常用方法如下:
(1)、Thread():分配新的Thread对象;
(2)、Thread( Runnable target ):分配新的Thread对象,target位run()方法被调用的对象;
(3)、Thread( Runnable target,String name ):分配新的Thread对象,target位run方法被调用的对象,name为新线程的名字;
(4)、void run():执行任务操作的方法;
(5)、void start():使该线程开始执行,java虚拟机调用该线程的run()方法;
(6)、void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行);
(7)、String getName():返回线程的名字;
void setName():指定线程名字;
(8)、int getPriority():返回线程的优先级;
(9)、void setPrority(int newPriority):更改线程的优先级;
(10)、static Thread currentThread():返回当前正在执行的线程对象的引用;
(11)、boolean isAlive():测试线程是否处于活动状态;
(12)、void join():等待线程终止;
(13)、void interrupt():中断线程;
boolean isInterrupted():判断当前线程是否是终止状态;
boolean interrupted():清除中断状态;
(14)、void yield():暂停当前正在执行的线程对象,并执行其他线程;
3、主线程
(1)、产生其他子线程的进程;
(2)、通常它必须最后完成执行,因为它执行各种关闭操作;
package 多线程;
public class Test {
public static void main(String[] args) {
Thread t = Thread.currentThread();
System.out.println("当前线程名是:"+t.getName());
t.setName("测试线程");
System.out.println("当前线程名是:"+t.getName());
/**
* 当前线程名是:main
* 当前线程名是:测试线程
*/
}
}
4、使用线程的过程
(1)、定义一个线程,同时指明这个线程所要执行的代码,即期望完成的功能;
(2)、创建线程对象;
(3)、启动线程;
(4)、终止线程;
三、在Java中使用线程
1、在Java中创建线程类有两种方法,一种是集成Thread类,另一种是实现Runnable接口;
2、继承Thread类创建线程
使用此方法创建线程一般要经过以下步骤:
(1)、继承Thread类;
(2)、重写run方法,将所有需要线程执行的代码全部写在run方法中;
(3)、创建线程对象;
(4)、调用start方法启动线程;
package 多线程;
public class MyThread extends Thread{
@Override
public void run() {
Thread.currentThread().setName("测试线程——");
for (int i = 1; i < 11; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
上述两个线程对象调用start方法之后,都会执行run方法中的代码,相当于每个线程都有自己独立的执行路径,并行执行,互不影响,但由于CPU在一个时间点只能执行一个线程,因此多个线程是交替执行的;
3、实现Runnable接口创建线程
1、Runnable接口定义在java.lang包下,其中声明了一个抽象方法run,一个类可以通过实现Runnable方法,并实现该方法来完成成所有线程活动,已实现的run方法称为该线程对象的线程体;
2、任何一个实现Runnable接口的对象都可以作为一个线程的目标对象;
package 多线程;
public class MyRunnable implements Runnable{
@Override
public void run() {
Thread.currentThread().setName("测试线程——");
for (int i = 1; i < 11; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t = new Thread(myRunnable);
t.start();
}
}
这种方式还可以实现多个线程之间的资源共享;
四、线程的状态
1、任何线程都具有五种状态:创建,就绪,运行,阻塞,死亡;
2、创建状态:在程序中用构造方法创建了一个对象之后,该对象就处于创建状态;
就绪状态:调用start方法启动线程,即进入就绪状态,此时线程将进入线程队列排队,等待CPU资源;
运行状态:当就绪状态的线程获得CPU资源时,随即开始进入运行状态,执行run方法;
阻塞状态:一个正在运行的线程因为某种原因不能继续运行时,进入阻塞状态,阻塞状态是一种"不可运行状态",处于这种状态的线程在得到一个特定的事件之后会转回运行状态,可是能线程进入阻塞状态的情形如下:
(1)、由于线程优先级较低,不能获得CPU资源;
(2)、sleep方法是线程休眠;
(3)、wait方法是线程等待;
(4)、yield方法是线程显式出让CPU控制权;
(5)、线程由于等待一个文件或用户输入,I/O时间被阻塞;
死亡状态:线程run方法执行完毕,则进入死亡状态,处于死亡状态的线程不具备继续运行的能力;
package 多线程;
public class MyRunnable implements Runnable{
@Override
public void run() {
try {
System.out.println("线程为运行状态!");
Thread.currentThread().setName("测试线程——");
//线程将在此处进入阻塞状态,休眠5秒后恢复运行
Thread.currentThread().sleep(5000);
for (int i = 1; i < 11; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t = new Thread(myRunnable);
System.out.println("线程为创建状态!");
t.start();
System.out.println("线程为就绪状态!");
}
}
五、线程的调度
1、在单CPU计算机中,一个时刻只有一个线程运行,所谓多线程的并发运行,其实是从宏观上看,各线程轮流获得CPU资源的使用权;
2、线程优先级高的线程获得CPU资源的概率较大;
3、线程优先级用1~10表示,1表示优先级最低,默认值是5,这些优先级对应一个Thread类的公用静态常量;
public static fianl int NORM_PRIORITY=5;
public static fianl int MAX_PRIORITY=10;
public static fianl int MIN_PRIORITY=1;
package 多线程;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i < 11; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable(),"线程1——");
Thread t2 = new Thread(new MyRunnable(),"线程2——");
//设置线程的优先级
t1.setPriority(1);
t2.setPriority(10);
//获取线程的优先级
System.out.println("线程1的优先级是:"+t1.getPriority());
System.out.println("线程2的优先级是:"+t2.getPriority());
//启动线程
t1.start();
t2.start();
}
}
六、线程的休眠
1、在程序中允许使用sleep方法是线程休眠,此时线程进入阻塞状态,休眠时间过后线程会再次进入就绪状态,进入线程队列排队,等待获取CPU资源,当获得CPU资源后,线程进入运行状态;
package 多线程;
public class MyRunnable implements Runnable {
@Override
public void run() {
try {
for (int i = 1; i < 11; i++) {
// 如果线程名位“线程1——”,则让其休眠1秒
if (Thread.currentThread().getName().equals("线程1——")) {
Thread.currentThread().sleep(1000);
}
System.out.println(Thread.currentThread().getName() + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable(), "线程1——");
Thread t2 = new Thread(new MyRunnable(), "线程2——");
// 设置线程的优先级
t1.setPriority(1);
t2.setPriority(10);
// 获取线程的优先级
System.out.println("线程1的优先级是:" + t1.getPriority());
System.out.println("线程2的优先级是:" + t2.getPriority());
// 启动线程
t1.start();
t2.start();
}
}
七、线程的强制运行
1、join方法可以使调用该方法的线程优先执行,等调用该方法的线程执行完毕后,再执行其他线程;
package 多线程;
public class MyThread implements Runnable {
@Override
public void run() {
try {
for(int i=0;i<10;i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
-----------------------------------------------------------------------------------
package 多线程;
public class MyThreadDemo {
public static void main(String[] args) {
Thread.currentThread().setName("主线程");
Thread t = new Thread(new MyThread(),"子线程");
t.start();
for(int i=0;i<10;i++) {
try {
if(i==5) {
//线程t调用了join方法,所以主线程会等线程t执行完毕之后再执行
t.join();
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
八、线程的礼让
1、yield方法可暂停当前线程执行,允许其他具有相同优先级的线程获得运行机会,该线程仍处于就绪状态,不转为阻塞状态;
2、礼让只是提供一种可能性,但不能保证一定可以实现礼让,因为礼让的线程仍然处于就绪状态,还有可能被线程调度再次选中运行;
package 多线程;
public class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"正在运行!");
if(i==3&&Thread.currentThread().getName().equals("线程A")) {
System.out.print("线程礼让:");
Thread.yield();
}
}
}
}
------------------------------------------------------------------------------------
package 多线程;
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my = new MyThread();
Thread t1 = new Thread(my,"线程A");
Thread t2 = new Thread(my,"线程B");
t1.start();
t2.start();
}
}
通过以上代码可得知,线程A在i=3的时候会礼让,但线程B不一定能获得运行机会;
九、多线程同步
1、多线程共享资源时引发的问题
小明,小王和小强,三个人都在网上抢火车票,假设现在只有10张火车票,用多线程模拟这一过程,用sleep方法让线程睡眠500毫秒,来模拟网络延时;
package 多线程;
public class Site implements Runnable {
/**
* count:总票数
* num:现在售出第几张票
*/
private int count = 10;
private int num = 0;
@Override
public void run() {
while (true) {
// 如果剩余总票数小于等于0,则跳出循环
if (count <= 0) {
break;
}
num++;
count--;
// 显示购票信息
System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
// 模拟网络延迟
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Site(), "小明");
Thread t2 = new Thread(new Site(), "小王");
Thread t3 = new Thread(new Site(), "小强");
t1.start();
t2.start();
t3.start();
/**
* console:部分输出结果
* ....
* 小明抢到了第4张票
* 小强抢到了第4张票
* 小王抢到了第4张票
* ...
*/
}
}
从上述例子我们可以看到,多线程中在共享数据的时候,存在数据不安全的问题;
2、多线程同步的实现
(1)、当多个线程对象共享同一数据资源时,需要以某种顺序来确保该资源某一时刻只能被一个线程使用,这就是线程同步;
package 多线程;
public class Site implements Runnable {
/**
* count:总票数 num:现在售出第几张票
*/
private static int count = 10;
private static int num = 0;
private static boolean flag = true;
@Override
public void run() {
while (flag) {
sale();
}
}
public static synchronized void sale() {
// 如果剩余总票数小于等于0,则跳出循环
if (count <= 0) {
flag = false;
return;
}
num++;
count--;
// 显示购票信息
System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
// 模拟网络延迟
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Site(), "小明");
Thread t2 = new Thread(new Site(), "小王");
Thread t3 = new Thread(new Site(), "小强");
t1.start();
t2.start();
t3.start();
}
}
-----------------------------------------------------------------------------------------
package 多线程;
public class Site implements Runnable {
/**
* count:总票数 num:现在售出第几张票
*/
private int count = 10;
private int num = 0;
private boolean flag = true;
@Override
public void run() {
while (flag) {
sale();
}
}
public synchronized void sale() {
// 如果剩余总票数小于等于0,则跳出循环
if (count <= 0) {
flag = false;
return;
}
num++;
count--;
// 显示购票信息
System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
// 模拟网络延迟
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Site site = new Site();
Thread t1 = new Thread(site, "小明");
Thread t2 = new Thread(site, "小王");
Thread t3 = new Thread(site, "小强");
t1.start();
t2.start();
t3.start();
}
}
注意以上两个例子的区别,被修饰符static修饰的变量和方法属于全局变量,可以控制该类所有的实例对象的线程;
没有static修饰的变量和方法,属于实力属性和方法,只能限制当前对象的所有线程;
3、使用同步代码块实现线程同步
将上面的例子稍微修改下:
package 多线程;
public class Site implements Runnable {
/**
* count:总票数 num:现在售出第几张票
*/
private int count = 10;
private int num = 0;
private boolean flag = true;
@Override
public void run() {
while (flag) {
synchronized (this) {
sale();
}
}
}
public void sale() {
// 如果剩余总票数小于等于0,则跳出循环
if (count <= 0) {
flag = false;
return;
}
num++;
count--;
// 显示购票信息
System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
// 模拟网络延迟
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Site site = new Site();
Thread t1 = new Thread(site, "小明");
Thread t2 = new Thread(site, "小王");
Thread t3 = new Thread(site, "小强");
t1.start();
t2.start();
t3.start();
}
}
使用同步代码块也可以实现对多线程的同步,不过同步代码块锁的是当前的对象;
得出结论:当synchonized锁的方法是实例方法或锁的是代码块时,那么它就是一个对象锁,只能对当前对象进行控制;
当synchonized锁的方法是静态方法时,那么他就是一个类锁,可以控制该类下所有对象;
package 多线程;
public class Run implements Runnable {
//赛道总长度
private static int meters = 400;
private boolean flag = true;
private static Run run = new Run();
@Override
public void run() {
while (flag) {
synchronized (run) {
//总长度跑完即结束
if (meters == 0) {
flag = false;
break;
}
begin();
}
}
}
public void begin() {
System.out.println(Thread.currentThread().getName() + "选手拿到了接力棒!");
//每人跑100米
for (int i = 0; i < 100; i += 10) {
System.out.println(Thread.currentThread().getName() + "选手跑了" + (i + 10) + "米");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
meters -= 100;
}
public static void main(String[] args) {
Thread t1 = new Thread(run, "1号");
Thread t2 = new Thread(run, "2号");
Thread t3 = new Thread(run, "3号");
t1.start();
t2.start();
t3.start();
}
}
-----------------------------------------------------------------------------------------
package 多线程;
public class Run2 implements Runnable {
// 赛道总长度
private static int meters = 400;
private static boolean flag = true;
@Override
public void run() {
while (flag) {
begin();
}
}
public static synchronized void begin() {
// 总长度跑完即结束
if (meters == 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "选手拿到了接力棒!");
// 每人跑100米
for (int i = 0; i < 100; i += 10) {
System.out.println(Thread.currentThread().getName() + "选手跑了" + (i + 10) + "米");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
meters -= 100;
}
public static void main(String[] args) {
Thread t1 = new Thread(new Run2(), "1号");
Thread t2 = new Thread(new Run2(), "2号");
Thread t3 = new Thread(new Run2(), "3号");
t1.start();
t2.start();
t3.start();
}
}
注意区分上述Run类和Run2类中的代码,一个是对象锁,一个是类锁,但实现的效果是相同的;