想要了解多线程就需要知道什么是进程什么是线程,简单的来说进程是指一个程序运行时操作系统给这个程序分配的CPU、内存、网络、磁盘等一系列的操作系统的资源的总称。而一个进程又是由若干个线程组成的,一个进程中至少存在一个线程。
了解完什么 是线程后我们再来简单了解线程的几种基本状态。新建(New),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)相关关系如下图所示
新建(New):线程对象被创建。
就绪(Runnable):线程对象等待操作系统分配资源。
运行(Running):线程对象的到了CPU资源。
阻塞(Blocked):堵塞状态是线程由于某种原因放弃CPU使用权。临时停止执行。直到线程进入就绪状态,才有机会转到执行状态。
死亡(Dead):线程运行完了或者因异常退出了run()方法,该线程结束生命周期。
在Java中实现多线程有两种方式分别是继承Thread类和继承Runable接口。
通过继承Thread实现多线程,重写Thread类中的run()方法在其中完成业务代码的编写,继承了Thread类的类就是一个线程类(可以直接实例化后调用start()方法使线程进入就绪态等待操作系统调度)。
通过简单的例子展示相关操作:
/** 继承Thread类创建线程
* 须要重写run()方法
*/
public class Demo_01 {
public static void main(String[] args) {
System.out.println("Begin");
//创建线程
PrintFlagThread pft = new PrintFlagThread();
//线程进入就绪状态,等待操作系统调度
pft.start();
System.out.println("End");
}
}
class PrintFlagThread extends Thread{
//重写Thread的run方法
@Override
public void run() {
for(int i =1; i<101;i++) {
System.out.print("*");
if(i%10==0) {
System.out.println();
}
}
}
}
一个类通过实现Runable接口实现多线程,通过重写Runable接口中的run()方法完成业务逻辑的编写,实现了Runable接口的类不能算是一个真正的线程类需要配合Thread类完成线程对象的创建。
通过简单的例子展示相关操作:
/** 实现Runable接口创建线程
* 须要重写run()方法
*/
public class Demo_02 {
public static void main(String[] args) {
System.out.println("Begin");
PrintFlagThread printFlagThread = new PrintFlagThread();
Thread thread = new Thread(printFlagThread);
//线程进入就绪状态,等待操作系统调度
thread.start();
System.out.println("End");
}
}
class PrintFlagThread implements Runable{
//实现Runable接口中的run方法
@Override
public void run() {
for(int i =1; i<101;i++) {
System.out.print("*");
if(i%10==0) {
System.out.println();
}
}
}
}
- currentThread()setPriority(10)静态方法,返回代码段正在被哪个线程调用的信息
- setName():为当前线程命名。
- setPriority():设置当前线程优先级,线程的优先级分为1-10这10个等级,如果小于1 或大于10,则抛出异常throw new IllegalArgumentException(),默认是5。(线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。)
下面通过简单的例子来展示这个方法的作用。
public class Demo_05 {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyThread());
// 设置线程名称
thread1.setName("线程甲");
// 设置当前线程优先级
thread1.setPriority(10);
Thread thread2 = new Thread(new MyThread());
thread2.setName("线程乙");
thread2.setPriority(5);
Thread thread3 = new Thread(new MyThread());
thread3.setName("线程丙");
thread3.setPriority(1);
// 使线程进入就绪态
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
//打印当前线程名称10次
for(int i = 0; i<10 ;i++) {
//通过currentThread()方法得到当前线程
Thread currentThread = Thread.currentThread();
//通过线程对象获得线程对象名称并输出
System.out.println(currentThread.getName()+"的第"+i+"次打印");
}
}
}
从三次的运行结果不难看出,setPriority()对线程的优先级只起到影响作用并不是起到决定性的作用。
- yield():使当前正在执行的线程向另一个线程交出运行权
- join():将线程调用此方法的线程加入到主线程中,当当前线程执行完成后恢复主线程的执行。(如系统目前运行线程A,在线程A里面调用了线程B.join方法,则接下来线程B会抢先在线程A面前执行,等到线程B全部执行完后才继续执行线程A。)
下面通过例子展示这两个方法的作用:
public class Demo_08 {
public static void main(String[] args) {
PringCharThread pct = new PringCharThread(10, 'A');
Thread t = new Thread(pct);
PringCharThread pct1 = new PringCharThread(10, 'P');
Thread t1 = new Thread(pct1);
PringCharThread pct2 = new PringCharThread(10, 'W');
Thread t2 = new Thread(pct2);
Thread t3 = new Thread(new PrintNumberThread(10));
t.start();
t1.start();
t2.start();
t3.start();
}
}
class PringCharThread implements Runnable {
private int count;
private char ch;
public PringCharThread(int count, char ch) {
this.ch = ch;
this.count = count;
}
@Override
public void run() {
for (int i = 1; i <= count; i++) {
System.out.printf("%s%d\t", ch, i);
if (i % 10 == 0) {
System.out.println();
}
if (i == count >> 1) {
// 使当前正在执行的线程向另一个线程交出运行权,
Thread.yield();
}
}
}
}
class PrintNumberThread implements Runnable {
private int count;
public PrintNumberThread(int count) {
this.count = count;
}
@Override
public void run() {
for (int i = 1; i <= count; i++) {
System.out.printf("%d\t", i);
if (i % 10 == 0) {
System.out.println();
if (i == count >> 1) {
Thread thread = new Thread(new PringCharThread(20, '?'));
thread.start();
try {
thread.join();// 将线程therad加入到当前线程中,当thread执行完成后恢复当前线程的执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
运行结果
第一次:
W1 W2 1 2 3 4 5 6 7 8 9 10 A1 A2 A3 A4 A5 P1 P2 P3 P4 P5 A6 A7 A8 A9 A10
W3 W4 W5 P6 P7 P8 P9 P10
W6 W7 W8 W9 W10
第二次:
P1 P2 P3 P4 P5 1 2 3 4 5 6 7 8 9 10
A1 A2 A3 A4 A5 W1 A6 P6 P7 P8 P9 P10 A7 A8 A9 A10
W2 W3 W4 W5
W6 W7 W8 W9 W10
由于是多线程实现输出结果格式无法有效控制,顾做如上展示。由运行结果可以看出,字符打印线程让出执行权,待数字打印线程执行完成后继续完成字符的打印。
一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法,说明当前线程一定是获取了锁的。
- notify()/notifyall():唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。
- wait():会释放当前的锁,然后让出CPU,进入等待状态。
同样通过一个小例子展示它们的用法:
public class Demo_02 {
public static void main(String[] args) {
// 创建Object传入线程使他们共同持有同一个对象,进而完成同步锁的功能
Object lockobj = new Object();
// 创建打印数字线程类
Thread printNumberThread = new Thread(new PrintNumber(lockobj));
// 创建打印字母线程类
Thread printCharThread = new Thread(new PrintChar(lockobj));
printNumberThread.start();
printCharThread.start();
}
}
//打印数字的方法
class PrintNumber implements Runnable {
private Object obj;
public PrintNumber(Object obj) {
this.obj = obj;
}
@Override
public void run() {
synchronized (obj) {
for (int i = 1; i <= 52; i++) {
System.out.print(i);
if (i % 2 == 0) {
// 在输出偶数数字后,输出空格前使当前线程唤醒另一个线程并让当前线程进入等待
obj.notify();// 让当前线程通知
if (i < 52) {
try {
obj.wait();// 让当前线程进入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(" ");
}
}
}
}
}
class PrintChar implements Runnable {
private Object obj;
public PrintChar(Object obj) {
this.obj = obj;
}
@Override
public void run() {
synchronized (obj) {
for (int i = 'A'; i <= 'Z'; i++) {
System.out.print((char) i);
obj.notify();// 让当前线程通知
if (i < 'Z') {
try {
obj.wait();// 让当前线程进入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
两个线程一个打印数字一个打印字母,当打印到偶数数字时调用notify()通知字母打印线程,然后调用wait()方法等待字母打印线程执行,字母打印线程打印一个字母,然后通知数字打印线程,并停下来等,重复以上步骤。得到如下结果:
12A 34B 56C 78D 910E 1112F 1314G 1516H 1718I 1920J 2122K 2324L 2526M 2728N 2930O 3132P 3334Q 3536R 3738S 3940T 4142U 4344V 4546W 4748X 4950Y 5152 Z
当然还有很多方法如sleep(S):(让线程“睡眠”S毫秒)这里不一一列举,至此关于多线程一些基本操作就介绍完了。