Java- 多线程学习笔记
一、进程与线程的区别
进程:进程是相对操作系统进行资源和调度的基本单位,一个操作系统可以同时执行多个进程(每一个应用程序就是一个进程)。
线程:线程是相对一个应用程序进行资源分配和调度的基本单位(一个应用程序可以有多个子线程),但每一个应用程序至少有一个主线程。
二、如何使用java创建多线程的应用程序
方法一:
创建步骤:
1、定义类继承Thread类;
2、重写run()方法
3、通过start()方法进行启动多线程
代码如下:
public class ThreadTestDemo {
public static void main(String[] args) {
// 如何启动一个新的线程
MyThread myThread = new MyThread();
// 调用start()方法
myThread.start(); // 自动调用run()方法
for(int i = 0;i < 100;i++) {
System.out.println("主线程中输出:i=" + i);
}
}
}
// 创建子线程的类
class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i < 100;i++) {
System.out.println("MyThread线程中输出:i=" + i );
}
}
}
方法二:通过实现Runnable接口,代码如下:
// 方法二:
class MyThread2 extends TestDemo1 implements Runnable{
@Override
public void run() {
for(int i = 0;i < 100;i++) {
System.out.println(Thread.currentThread().getName() +"线程中输出:i=" + i );
}
}
}
使用此方法实现的多线程程序,启动需要使用Thread类实例化,调用start()方法启动子线程,代码如下:
MyThread2 thread2 = new MyThread2();
Thread thread = new Thread(thread2, "子线程2");
thread.start();
三、线程的生命周期
线程的生命周期分为五个阶段,分部是:新建状态、就绪状态、运行状态、阻塞状态、死亡状态,如图:
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
四、线程调度
4.1 多线程执行随机性
多线程的程序在多线程同时在执行的过程中,由于每个线程执行run()方法是根据系统的调度来决定的,所以多线程在执行的先后次序上有一定的随机性,其代码代码如下:
public class ThreadRandomTest {
public static void main(String[] args) {
Thread[] threads = new Thread[10];
for(int i = 0;i < 10;i++) {
threads[i] = new RandomThread("RandomThread" + i);
}
for(Thread thread:threads) {
thread.start();
}
}
}
class RandomThread extends Thread{
public RandomThread(String name) {
super(name);
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.2 线程中断
使用sleep()方法让当前的线程阻塞,代码如下:
public class SleepThreadDem {
public static void main(String[] args) {
SleepThread sleepThread = new SleepThread();
SleepThread sleepThread2 = new SleepThread();
System.out.println("是否激活(new):" + sleepThread.isAlive());
sleepThread.start();
sleepThread2.start();
System.out.println("是否激活(start):" + sleepThread.isAlive());
for(int i = 0;i < 10;i++) {
System.out.println(Thread.currentThread().getName() + "输出i=" + i);
if(i == 2) {
try {
// 使得当前线程阻塞,进入睡眠状态
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class SleepThread extends Thread{
@Override
public void run() {
System.out.println("是否激活(run):" + this.isAlive());
for(int i = 0;i < 10;i++) {
System.out.println(Thread.currentThread().getName() + "输出i=" + i);
if(i == 3) {
try {
// 使得当前线程阻塞,进入睡眠状态
System.out.println("是否激活(sleep):" + this.isAlive());
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
4.3 线程让步
yield()方法与sleep()方法相似,都可让当前正在运行中的线程暂停,不过yield()方法不会阻塞当前线程
public class YieldThreadDemo {
public static void main(String[] args) {
YieldThread yi1 = new YieldThread("呃呃呃");
YieldThread yi2 = new YieldThread("鹅鹅鹅");
yi1.start();
yi2.start();
}
}
class YieldThread extends Thread {
public YieldThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出数字:" + i);
if (i == 3) {
System.out.println("当i=3时,线程"+Thread.currentThread().getName() +"让步,执行相同或者更高级优先级线程:");
Thread.yield();
}
}
}
}
4.4 线程插队
使用join进行插队,其代码如下:
public class JoinThreadDemo {
public static void main(String[] args) {
JoinThread joinThread1 = new JoinThread("子线程-1");
JoinThread joinThread2 = new JoinThread("子线程-2");
joinThread1.start();
joinThread2.start();
try {
joinThread1.join(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
joinThread2.join(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 主线程
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出i=" + i);
}
}
}
class JoinThread extends Thread {
public JoinThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程运行开始!");
for (int i = 0; i < 5; i++) {
System.out.println("线程:" + Thread.currentThread().getName() + "输出" + i);
}
System.out.println(Thread.currentThread().getName() + "线程运行结束!");
}
}
join()方法-线程插队,即当前子线程调用join()方法后,线程进入阻塞状态直到子线程执行完成,才会继续执行。
五、线程同步
当多个线程同时访问一个共有资源,可能发生线程同步,使用场景 如:多个窗口买同趟车票,有可能重买或多买,代码如下:
public class TicketsThreadDemo {
public static void main(String[] args) {
TicketsThread t = new TicketsThread();
new Thread(t, "A窗口").start();
new Thread(t, "B窗口").start();
new Thread(t, "C窗口").start();
new Thread(t, "D窗口").start();
}
}
class TicketsThread implements Runnable{
private int tickets = 10;
@Override
public void run() {
while(tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出票 " + tickets--);
}
}
}
执行结果:
解决此次问题可以在run()方法中添加synchronized关键字使得run()方法可以同步,多线程的程序执行此方法时,此方法进行锁定直到执行完成,其他线程调用可以执行,这样可以保证程序逻辑正常,修改代码如下:
class TicketsThread implements Runnable{
private int tickets = 10;
@Override
public synchronized void run() {
while(tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出票 " + tickets--);
}
}
}
执行效果如下: