第四章 多线程
一、多线程
进程与线程
一个程序有一个进程
一个进程有多个线程(必须有一个主线程)
定义:在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”
多个线程交替占用CPU资源,而非真正的并行执行
一般main()方法是主线程
查看线程
Thread t= Thread.currentThread();
// 输出线程的名称
System.out.println("当前线程名称: " + t.getName());
// 设置线程的名称
t.setName("MyMainThread");
// 输出线程的新名称
System.out.println("新线程名称: " + t.getName());
二、创建方式
1、继承Thread类,重写run方法,调用start方法启动
编写简单,可直接操作线程
适用于单继承
2、实现Runnable接口,重写run方法,创建Thread对象 new Thread(传对象),调用start方法启动
避免单继承局限性
便于共享资源
3.实现Callable<数据类型>接口,重写call方法 注意:Callable有返回值
都重写run方法,在线程启动时会自动调用run();
继承java.lang.Thread类
package test01;
public class ThreadOne extends Thread {
public ThreadOne() {
}
public void run(){
for(int i=1;i<=20;i++){
System.out.println(i+"."+Thread.currentThread().getName()+":"+i);
}
System.out.println(Thread.currentThread().getName()+"结束");
}
}
ThreadOne t1=new ThreadOne();//创建对象
ThreadOne t2=new ThreadOne();//创建对象
t1.start(); //线程2
t2.start(); //线程2
实现java.lang.Runnable接口
package test02;
public class ThreadExample implements Runnable{
@Override
public void run() {
for(int i=1;i<=10;i++){
System.out.println(i+"."+Thread.currentThread().getName()+":"+i);
}
}
}
ThreadExample threadExample=new ThreadExample();
Thread t1=new Thread(threadExample);
Thread t2=new Thread(threadExample);
t1.start();
t2.start();
实现Callable<数据类型>接口
public class MyCallable implements Callable<String> {//泛型接口
@Override
public String call() throws Exception {
for(int i=1;i<=10;i++){
System.out.println(i+"."+Thread.currentThread().getName()+":"+i);
}
return "线程结束啦";
}
}
public class Test {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask=new FutureTask<>(myCallable);//FutureTask 来传对象
FutureTask<String> futureTask1=new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);//创建线程
Thread thread1 = new Thread(futureTask1);//创建线程
thread.start();
thread1.start();
try {
System.out.println( futureTask.get());//接收返回值
System.out.println( futureTask1.get());//接收返回值
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、线程的状态
1.新建状态(new)
线程被创建但还没有调用 start() 方法。此时,线程处于 NEW 状态。
Thread myThread = new Thread(); // 线程被创建但还未启动
2.就绪状态(可运行状态)
当线程调用了 start() 方法后,线程进入 就绪 状态。
此时,线程可能在等待 CPU 时间片,也可能正在运行。
3.运行状态
start之后开始运行run(),此时为运行状态
4.死亡状态(终止状态)
线程run方法运行结束,线程终止
5.阻塞状态
1. sleep() 线程休眠,此时线程进入阻塞状态
2. join() 强制执行,谁调用就阻塞谁,然后调用join方法的先执行
阻塞状态恢复之后 状态变为 就绪->运行
四、线程调度
常用方法
setPriority 优先级// 默认是5,范围为1-10
package test05;
public class Test implements Runnable{
public static void main(String[] args) {
Thread t= Thread.currentThread();
Thread thread=new Thread();
System.out.println("当前线程名称: " + t.getName()+",默认优先级"+t.getPriority());
System.out.println("子线程名称"+thread.getName()+",默认优先级"+thread.getPriority());
t.setPriority(t.MAX_PRIORITY);
thread.setPriority(Thread.MIN_PRIORITY);
//需要先设置优先级,在开始运行线程
thread.start();
System.out.println("当前线程名称: " + t.getName()+",优先级"+t.getPriority());
System.out.println("子线程名称"+thread.getName()+",优先级"+thread.getPriority());
}
sleep() //线程休眠让线程暂时睡眠指定时长,线程进入阻塞状态睡眠时间过后线程会再进入可运行状态
public class SpecialPeople implements Runnable{
@Override
public void run() {
try {
for(int i=1;i<=10;i++) {
System.out.println("特需号:" + i + "号病人正在看病!!!!!!");
Thread.sleep(1000);//线程休眠1s
}
}catch (Exception e){
e.printStackTrace();
}
}
}
t1.join()
谁调用这个方法阻塞谁,t1.join()先执行,执行完之后在执行调用的这个线程
ClimbThread c1=new ClimbThread("张三",10,5);
ClimbThread c2=new ClimbThread("李四",8,6);
ClimbThread c3=new ClimbThread("王五",11,7);
Thread people=new Thread(c1);
Thread people1=new Thread(c2);
Thread people2=new Thread(c3);
people.start();
try {
people.join();//等待people.join() 执行完,在执行people1.start();
people1.start();
people1.join();
people2.start();
people2.join();
System.out.println("爬山结束");
} catch (Exception e) {
e.printStackTrace();
}
}
}
isAlive()//判断当前线程是否在执行中
join() 是一个阻塞方法。它使调用 join() 的线程等待目标线程完成执行。
作用:用于强制执行线程的顺序,即确保一个线程等待另一个线程完成后再继续执行。
阻塞行为:调用 join() 的线程会 完全阻塞,直到被 join() 的目标线程执行完毕。如果不等待另一个线程结束,当前线程将不会继续。
yield() 是一个非阻塞方法,它只是给线程调度器一个提示,让出当前线程的 CPU 执行权,允许其他线程(尤其是与当前线程优先级相同或更高优先级的线程)有机会获取 CPU。
五、synchronized
synchronized 关键字用于同步代码块或方法,以确保多个线程在同一时间只能访问特定资源或代码块中的一个线程。这有助于防止线程并发问题
,你需要确保所有线程共享同一个 RunThread 实例,这样所有线程都会竞争同一个锁。
RunThread runThread = new RunThread();
// 让所有线程使用同一个RunThread实例
Thread t1=new Thread(runThread);
Thread t2=new Thread(runThread);
Thread t3=new Thread(runThread);
1.同步方法
使用 `synchronized` 关键字来同步整个方法,确保该方法在同一时刻只能被一个线程访问。
public class Counter {
private int count = 0;
// 同步方法,只允许一个线程访问
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
increment() 方法被标记为 synchronized,因此,多个线程不能同时执行这个方法,它保证了对 count 的原子性操作。
2.同步代码块
public class Counter {
private int count = 0;
// 同步代码块
public void increment() {
synchronized (this) {
count++;
}
}
public int getCount() {
return count;
}
}
只是对 count++ 操作进行同步,而不影响其他操作,提高了性能和灵活性。
3.同步静态方法
也可以用于静态方法,锁定的是整个类的 Class 对象。
public class Counter {
private static int count = 0;
// 同步静态方法
public static synchronized void increment() {
count++;
}
public static int getCount() {
return count;
}
}
当多个线程访问同一个类的静态方法时,synchronized 确保静态方法在同一时刻只能被一个线程访问。
4.同步对象
可以在同步代码块中使用特定的对象作为锁
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
}
lock 对象被用作锁。每次进入同步代码块时,线程必须先获得 lock 的锁。这样可以避免锁定整个类或实例,提高性能。
synchronized 的工作原理
锁(Monitor Lock): 当一个线程进入 synchronized 方法或块时,它会获得对象的锁,其他线程必须等待该锁被释放后才能进入这个方法或块。
重入锁(Reentrant Lock): Java 的 synchronized 是可重入的,这意味着如果一个线程已经持有一个对象的锁,它可以再次进入该对象的 synchronized 块或方法,而不会被阻塞。
自动释放锁: 当线程退出 synchronized 方法或块时,锁会自动释放。
多个并发线程访问同一资源的同步代码块时
同一时刻只能有一个线程进入synchronized(this)同步代码块
当多个线程访问同一个对象的 synchronized(this) 代码块时,Java 会使用该对象的监视器锁(Monitor Lock)来确保在任意时刻只有一个线程可以进入同步块。
public class Example {
public void methodA() {
synchronized (this) {
// 同步代码块,只有一个线程可以进入
System.out.println(Thread.currentThread().getName() + " is in methodA");
try {
Thread.sleep(1000); // 模拟执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void methodB() {
synchronized (this) {
// 同步代码块,只有一个线程可以进入
System.out.println(Thread.currentThread().getName() + " is in methodB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
如果两个线程同时调用 methodA() 或 methodB(),它们将竞争相同的锁,因为它们都使用了 synchronized(this),从而确保同一时刻只有一个线程可以执行。
当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
synchronized(this) 锁定的是当前对象 (this),因此无论这个对象中有多少个 synchronized(this) 同步代码块,所有这些代码块都会被同一个锁保护。当一个线程进入某个 synchronized(this) 代码块时,其他线程无法进入该对象的任何其他 synchronized(this) 代码块,直到锁被释放。
上面的例子中,如果 Thread A 进入了 methodA(),Thread B 试图进入 methodB() 时也会被阻塞,尽管它们是不同的方法,因为它们都使用了 synchronized(this) 锁。
当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
不影响非同步代码块
六、线程安全
Hashtable && HashMap
Hashtable
继承关系
实现了Map接口,Hashtable继承Dictionary类
线程安全,效率较低
键和值都不允许为null
HashMap
继承关系
实现了Map接口,继承AbstractMap类
非线程安全,效率较高
键和值都允许为null
```java
StringBuffer && StringBuilder
前者线程安全,后者非线程安全
``