java多线程基础
- 1. 线程是什么
- 2. 线程的创建和运行
- 方式1:继承Thread类
- 示例:
- 方式2:实现Runnable接口(推荐)
- 示例:
- 3. Thread类的常用方法
- 4. 线程插队
- (1)yield 当前线程把时间片让给其它线程,不一定成功
- 示例:
- (2)join 足够强势的插队,一定成功
- 示例:
- 5. 线程同步的两种方式
- (1) synchronized修饰方法
- 示例:
- (2) synchronized修饰代码块
- 示例1:使用this作为Object对象
- 示例2:使用唯一且共享的Object对象
- 示例3:使用当前类对象
1. 线程是什么
线程是进程中的一个执行流,如果将进程比作一个工厂的加工车间,那么线程就可以看成是其中的一条流水线。线程是程序运行的最小单位。
2. 线程的创建和运行
方式1:继承Thread类
一个类继承Thread类并重写run方法,这个类创建的对象就可以当作线程类使用,通过调用start方法启动一个线程,并执行重写的run方法。
注意不能直接调用run方法启动线程,run方法只是一个普通的成员方法,不会创建线程。
示例:
// 线程的第一种使用方式,继承Thread类并重写run方法
class Dragon extends Thread {
@Override
public void run() {
int times = 0;
while (true) {
System.out.println("龙开始第" + (++times) + "次喷火! " + "当前线程名: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times == 9){
System.out.println("龙烧死了对手");
break;
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
System.out.println("当前线程名: " + Thread.currentThread().getName());
Dragon dragon = new Dragon();
dragon.start();
}
}
运行结果:
方式2:实现Runnable接口(推荐)
一个类可以实现Runnable接口,并实现run方法,这个类就作为Runnable的子类,可以使用该类对象创建一个Thread类对象,进而通过Thread对象调用新实现的run方法,因为Thread有一个构造方法只有一个Runnable类型的参数,在调用Thead的start方法时,在创建线程后,它会调用Runnable的run方法。
注意不能直接调用run方法启动线程,run方法只是一个普通的成员方法,不会创建线程。
示例:
接口:
public interface FlyAction {
void fly();
}
public interface RunAction {
void runOnEarth();
}
线程类和测试代码:
class Animal{}
// 线程的第二种调用方式,实现Runnable接口,实现run接口,适合继承其它类的情况
class Bird extends Animal implements FlyAction, RunAction, Runnable{
@Override
public void run() {
int times = 0;
while(true){
System.out.println("鸟的年龄是第" + (++times) + "天~当前线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times == 3){
System.out.println("鸟可以跑了");
runOnEarth();
}else if(times == 5){
System.out.println("鸟可以飞了");
fly();
}else if(times == 10){
System.out.println("鸟长大了");
break;
}
}
}
@Override
public void fly() {
System.out.println("鸟在飞");
}
@Override
public void runOnEarth() {
System.out.println("鸟在地上跑");
}
}
public class ThreadTest2 {
public static void main(String[] args) {
Bird bird = new Bird();
//需要通过一个Thread对象来调用
Thread thread = new Thread(bird);
thread.start();
}
}
运行结果:
3. Thread类的常用方法
方法名 | 方法说明 | 参数 | 返回值 |
---|---|---|---|
sleep | 让当前线程休眠,静态方法 | 毫秒数 | void |
setName | 设置线程名,成员方法 | 线程名(String类型) | void |
getName | 获取线程名,成员方法 | 无 | 线程名(String类型) |
currentThread | 获取当前执行的线程的引用,静态方法 | 无 | 当前执行的线程 |
start | 创建线程并调用线程入口方法run,成员方法 | 无 | void |
setPriority | 修改线程优先级(10最高1最低),成员方法 | 新的优先级 | void |
getPriority | 获取当前线程的优先级,成员方法 | 无 | 当前线程优先级 |
4. 线程插队
当需要将特定的线程先运行,给该线程分配时间片时,就可以调用yield或join方法
(1)yield 当前线程把时间片让给其它线程,不一定成功
调用yield方法的线程不一定后运行,会受到CPU调度策略的影响
示例:
主线程打印5次hi之后把时间片交给子线程,子线程独占时间片并打印hi
class ThreadJoinDemo implements Runnable{
@Override
public void run() {
for(int cnt = 0; cnt < 10; ++cnt){
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadJoinTest {
public static void main(String[] args) {
ThreadJoinDemo threadJoinDemo = new ThreadJoinDemo();
Thread thread = new Thread(threadJoinDemo);
thread.start();
int cnt = 0;
while(cnt < 10) {
System.out.println("hi");
++cnt;
if(cnt == 5){
System.out.println("main线程开始让出时间片");
//当前线程让出时间片
Thread.currentThread().yield();
System.out.println("main线程让出时间片结束");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
结果分析:
子线程并没有独占时间片,因为此时CPU资源足够两个线程同事运行,CPU就不会让main线程停止运行,等待子线程运行结束
(2)join 足够强势的插队,一定成功
调用join方法的线程先分得时间片,运行完之后运行其它线程
示例:
main线程执行5次打印语句后,子线程执行10次打印语句,子线程执行完毕之后父线程再执行
class SonThread implements Runnable{
@Override
public void run() {
for(int cnt = 0; cnt < 10; ++cnt){
System.out.println("我是子线程, hello " + (cnt + 1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadJoinTest2 {
public static void main(String[] args) {
SonThread sonThread = new SonThread();
Thread thread = new Thread(sonThread);
int cnt = 0;
while (cnt < 10){
System.out.println("我是main线程, hi " + (cnt + 1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
++cnt;
if(cnt == 5){
thread.start();
try {
System.out.println("子线程开始运行");
thread.join();
System.out.println("子线程结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
5. 线程同步的两种方式
当多个线程对同一份资源(临界资源)进行访问时,可能会出现两个线程同时对临界资源进行修改,这样可能会造成临界资源状态的异常,因此需要让一些临界资源在同一时间只能由一个线程访问,这就是线程同步。
线程同步的两种实现方式如下:
(1) synchronized修饰方法
当在一个方法的返回值类型之前使用synchronized修饰,那么这个方法在同一时刻只能让一个线程调用,其它线程需要等待这个线程调用结束后才能调用。
示例:
class SellThread implements Runnable{
private static int tickets = 100;
private static boolean isNotSellOver = true;
@Override
public void run() {
while(isNotSellOver){
sellTask();
}
}
public synchronized void sellTask(){
if(tickets <= 0){
isNotSellOver = false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了一张票,还有" + (--tickets) + "张票");
}
}
public class ThreadSyn1 {
public static void main(String[] args) {
SellThread sellThread = new SellThread();
Thread thread1 = new Thread(sellThread);
Thread thread2 = new Thread(sellThread);
Thread thread3 = new Thread(sellThread);
thread1.start();
thread2.start();
thread3.start();
}
}
(2) synchronized修饰代码块
可以将synchronized(Object对象)放在代码块之前来修饰代码块,表示这个代码块同一时刻只能有一个线程访问。
重要的是,要想达到对临界资源进行保护的目的需要使用同一个Object对象
静态方法中的代码块可以使用当前类作为Object对象
非静态方法可以使用只有一份的Object对象例如this
示例1:使用this作为Object对象
class ThreadDemo2 implements Runnable{
private static int tickets = 100;
@Override
public void run() {
while(true){
synchronized (this) {
if (tickets <= 0) {
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了一张票,还有 " + (--tickets) + "张");
}
}
}
}
public class SynchronizedThreadTest2{
public static void main(String[] args) {
ThreadDemo2 threadDemo = new ThreadDemo2();
Thread thread = new Thread(threadDemo);
Thread thread2 = new Thread(threadDemo);
Thread thread3 = new Thread(threadDemo);
thread.start();
thread2.start();
thread3.start();
}
}
示例2:使用唯一且共享的Object对象
class ThreadDemo implements Runnable{
private static int tickets = 100;
private final static Object object = new Object();
@Override
public void run() {
while(true){
synchronized (object) {
if (tickets <= 0) {
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了一张票,还有 " + (--tickets) + "张");
}
}
}
}
public class SynchronizedThreadTest{
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread thread = new Thread(threadDemo);
Thread thread2 = new Thread(threadDemo);
Thread thread3 = new Thread(threadDemo);
thread.start();
thread2.start();
thread3.start();
}
}
示例3:使用当前类对象
class ThreadDemo3 implements Runnable{
private static int tickets = 100;
private static boolean isNotSelledOver = true;
@Override
public void run() {
while(isNotSelledOver){
doTask();
}
}
public static void doTask(){
synchronized (ThreadDemo3.class) {
if (tickets <= 0) {
isNotSelledOver = false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了一张票,还有 " + (--tickets) + "张");
}
}
}
public class StaticMethodSynchronizedThreadTest {
public static void main(String[] args) {
ThreadDemo3 threadDemo = new ThreadDemo3();
Thread thread = new Thread(threadDemo);
Thread thread2 = new Thread(threadDemo);
Thread thread3 = new Thread(threadDemo);
thread.start();
thread2.start();
thread3.start();
}
}