java之多线程
目录
创建多线程的三种创建方式
常用的成员方法
守护线程
多线程的声明周期
编辑 同步代码块编辑
同步方法
死锁
等待唤醒机制(线程协调)
线程池
创建多线程的三种创建方式
继承 Thread
类
通过继承 Thread
类并重写 run()
方法创建线程。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running: " + getName());
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
实现 Runnable
接口
实现 Runnable
接口并将实例传递给 Thread
对象。推荐此方式,因为可以避免 Java 单继承的限制。
class MyRunnable implements Runnable {
@Override
public void run() {
// 注意这里不能直接getName,因为getName是Thread里面的方法,我们必须先获取当前的线程在调用此方法
System.out.println("Runnable running: " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
// 创建MyRun的对象 表示多线程要执行的任务
MyRun mr = new MyRun();
// 创建线程对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
通过 Callable
和 FutureTask
Callable
可以返回结果或抛出异常,结合 FutureTask
执行。
import java.util.concurrent.Callable;
// 第三种开启线程的方法;特点:可以得到多线程运行的结果
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 开启线程 求1~100之间的和
int sum = 0;
for (int i = 1; i <100 ; i++) {
sum+=i;
}
return sum;
}
}
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException{
// 第三种方式:
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft);
// 给线程设置名字
t1.setName("线程1");
// 开启线程
t1.start();
Integer result = ft.get();
System.out.println(result);
}
}
常用的成员方法
start()
: 启动线程并调用run()
方法。run()
: 线程执行的具体逻辑。join()
: 等待线程执行结束。sleep(long millis)
: 暂停当前线程执行一段时间。yield()
: 暂时释放 CPU 资源,让其他线程有机会执行。getName()
/setName()
: 获取或设置线程名称。isAlive()
: 检查线程是否仍然存活。setPriority(int priority)
: 设置线程优先级。
守护线程
守护线程在后台运行,用于服务用户线程(非守护线程)。当所有用户线程结束时,守护线程自动退出。
通过 setDaemon(true)
将线程设置为守护线程。当主线程结束之后,守护线程不会立刻结束而是陆续结束。
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("Daemon thread running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
});
thread.setDaemon(true); // 设置为守护线程
thread.start();
System.out.println("Main thread ends.");
}
}
多线程的声明周期
同步代码块
使用 synchronized
确保同一时间只有一个线程访问共享资源。
注意在synchronized小括号里面的一般会是该类的字节码文件(类.class)必须保证其唯一性。
package Thread;
// 三个窗口售卖票 总共有50张票
public class practice {
public static void main(String[] args) {
Myr myr = new Myr();
Thread t1 = new Thread(myr);
Thread t2 = new Thread(myr);
Thread t3 = new Thread(myr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Myr implements Runnable {
private int cnt = 1; // 初始票号设为 1
// 同步代码块
@Override
public void run() {
while (true) {
synchronized (Myr.class) {
if (cnt > 50) break; // 先检查是否有票可卖
System.out.println(Thread.currentThread().getName() + "正在售卖第 " + cnt + " 张票!");
cnt++; // 售出当前票后再自增
}
try {
Thread.sleep(100); // 模拟售票时间
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
同步方法
同步方法是锁住方法里面所有的代码,其实就是把同步代码块里面的代码提取成同步方法。
注意:同步方法锁的对象是不可以指定的。在java中,非静态的锁对象是this。静态的锁对象是本类的字节码文件对象(类.class)
package Thread;
// 三个窗口售卖票 总共有50张票
public class practice {
public static void main(String[] args) {
Myr myr = new Myr();
Thread t1 = new Thread(myr);
Thread t2 = new Thread(myr);
Thread t3 = new Thread(myr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Myr implements Runnable {
private int cnt = 1; // 初始票号设为 1
@Override
public void run() {
while (true) {
if (!method()) break;;
}
}
// 同步方法
private synchronized boolean method() {
if (cnt > 50) return false; // 先检查是否有票可卖
System.out.println(Thread.currentThread().getName() + "正在售卖第 " + cnt + " 张票!");
cnt++; // 售出当前票后再自增
try {
Thread.sleep(100); // 模拟售票时间
} catch (
InterruptedException e) {
throw new RuntimeException(e);
}
return true;
}
}
死锁
死锁就是两个锁嵌套,在以后写代码的时候需要注意不要写嵌套的锁
等待唤醒机制(线程协调)
在等待唤醒机制中,一般会将锁对象(通常是一个共享资源或
Object
)和线程操作关联起来。这是因为 wait()
、notify()
和 notifyAll()
方法必须在同步代码块或同步方法中调用,而这些同步需要一个明确的锁对象来协调线程间的通信。
代码中通过 Desk.lock.wait()
和 Desk.lock.notify()
有效地将线程等待与特定的锁对象关联起来。这样线程可以知道锁住和这个锁对象关联起来的线程,也可以唤醒与这个锁对象关联起来的线程
package waitandnotify;
public class Desk {
public static final Object lock = new Object(); // 锁对象
public static int cnt = 5; // 剩余次数
public static int foodFlag = 0; // 食物标志:0 - 无食物,1 - 有食物
}
class Cook extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.cnt == 0) {
break; // 没有剩余任务,结束线程
}
if (Desk.foodFlag == 1) {
try {
Desk.lock.wait(); // 有食物,等待消费者
} catch (InterruptedException e) {
e.printStackTrace();
}
} else if (Desk.foodFlag == 0) {
// 生产食物
System.out.println("厨师做了一碗面条");
Desk.foodFlag = 1;
Desk.lock.notify(); // 唤醒消费者
}
}
}
}
}
class Consumer extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.cnt == 0) {
break; // 没有剩余任务,结束线程
}
if (Desk.foodFlag == 0) {
try {
Desk.lock.wait(); // 没有食物,等待生产者
} catch (InterruptedException e) {
e.printStackTrace();
}
} else if (Desk.foodFlag == 1) {
// 消费食物
System.out.println("消费者吃了一碗面条");
Desk.foodFlag = 0;
Desk.cnt--; // 减少任务次数
Desk.lock.notify(); // 唤醒生产者
}
}
}
}
}
public class Main {
public static void main(String[] args) {
Cook cook = new Cook();
Consumer consumer = new Consumer();
cook.start();
consumer.start();
}
}
线程池
package Thread.Threadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // 核心线程数量不能少于0
6, // 最大线程数量,不能小于0 最大线程数量大于等于核心线程
60, // 临时线程60s就会销毁
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 任务队列
Executors.defaultThreadFactory(), // 创建线程工厂
new ThreadPoolExecutor.AbortPolicy() //任务的拒绝策略
);
// 为什么任务的拒绝策略需要定义在类的内部类当中?
//划重点:什么时候创建内部类,当一个类依赖另一个类存在时,而他单独存在没有意义时,
// 就可以创建某个类的内部类
pool.submit(new MyRunnable());
}
}