11. 多线程与锁
11.1 多线程
11.1.1 实现多线程
写法1:继承 Thread
类
class Worker extends Thread {
// IDEA里重写函数的快捷键:光标停在 Thread上按ctrl+o
@Override
public void run() {
for (int i = 0; i < 10; i ++ ) {
System.out.println("Hello! " + this.getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Main {
public static void main(String[] args) {
Worker worker1 = new Worker();
Worker worker2 = new Worker();
worker1.setName("thread-1");
worker2.setName("thread-2");
worker1.start();
// 主函数进程一开始只有一个线程,start之后会单开一个线程去执行worker里的run函数,先前的线程继续执行主函数的其他内容
worker2.start();
}
}
写法2:实现 Runnable
接口
class Worker implements Runnable {
@Override
public void run() {
for (int i = 0;i < 10;i ++) {
System.out.println("Hello" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Main {
public static void main(String[] args) {
Worker worker = new Worker();
new Thread(worker).start();
new Thread(worker).start();
}
}
与写法1相比,实现多线程可以只创建一个实例,实现同步更方便(静态变量,全局变量可能需要同步)
11.1.2 常用API
start()
:开启一个线程Thread.sleep()
: 休眠一个线程join()
:等待线程执行结束join(5000)
: 表示最多等5秒钟
interrupt()
:从休眠中中断线程,只有在线程代码能抛InterruptedException
时才有用,比如线程睡眠或等待。如果是其他因素导致线程阻塞,比如单纯的计算因素,就没有用setDaemon()
:将线程设置为守护线程。当只剩下守护线程时,即当所有其他线程都结束时,程序自动退出
package org.example.whj;
class Worker extends Thread {
@Override
public void run() {
for (int i = 0;i < 10;i ++) {
System.out.println("Hello " + i + " " + getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
break;
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Worker worker1 = new Worker();
worker1.setName("Thread-1");
Worker worker2 = new Worker();
worker2.setName("Thread-2");
worker1.setDaemon(true);
worker2.setDaemon(true);
// 设置worker1和worker2都为守护线程后,只有主线程为非守护线程
worker1.start();
worker2.start();
// worker1.join(); // 主线程执行到这里时会卡住,等待worker1的线程执行完后才继续执行
// worker1.join(5000); // 等线程1 5秒钟
// worker1.join(5000);
// worker1.interrupt(); // 调用这个api后,worker1线程会抛出一个InterruptedException异常并中断线程,
// 该异常会在执行sleep函数时被捕获,catch框住的语句就是当该异常被捕获后要执行的逻辑,这里是直接break
// 如果线程不会捕获或抛出InterruptedException异常,这个api就不会中断线程
Thread.sleep(5000);
System.out.println("Thread-1 不等啦");
System.out.println("Main Thread Over");
// 主线程在这里结束,非守护线程结束后守护线程也会自动结束
}
}
11.2 锁
多线程对同一变量进行操作可能会产生读写冲突问题,用锁来解决
lock
:获取锁,如果锁已经被其他线程获取,则阻塞unlock
:释放锁,并唤醒被该锁阻塞的其他线程
import java.util.concurrent.locks.ReentrantLock;
class Worker extends Thread {
public static int cnt = 0;
private static final ReentrantLock lock = new ReentrantLock(); // 静态变量,只有一把锁
@Override
public void run() {
for (int i = 0; i < 100000; i ++ ) {
lock.lock(); // 获取锁,如果锁被别的线程获取了,该线程就会阻塞在这里
try {
cnt ++ ;
} finally {
lock.unlock();
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Worker worker1 = new Worker();
Worker worker2 = new Worker();
worker1.start();
worker2.start();
worker1.join();
worker2.join();
System.out.println(Worker.cnt);
}
}
11.3 同步(Synchronized)(锁的语法糖)
写法1:将 Synchronized
加到代码块上
class Count {
public int cnt = 0;
}
class Worker extends Thread {
public final Count count;
public Worker(Count count) {
this.count = count;
}
@Override
public void run() {
synchronized (count) { // synchronized要加在一个常量上。只有在当前synchronized代码被执行完之后其他synchronized代码才会被执行
for (int i = 0; i < 100000; i ++ ) {
count.cnt ++ ;
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Count count = new Count();
Worker worker1 = new Worker(count);
Worker worker2 = new Worker(count);
worker1.start();
worker2.start();
worker1.join();
worker2.join();
System.out.println(count.cnt);
}
}
写法2:将 Synchronized
加到函数上(锁加到了 this
对象上)
这种写法需要配合上面提到的一个实例开多线程的写法,用多个实例开多线程没有效果
class Worker implements Runnable {
public static int cnt = 0;
private synchronized void work() {
for (int i = 0; i < 100000; i ++ ) {
cnt ++ ;
}
}
@Override
public void run() {
work();
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Worker worker = new Worker();
Thread worker1 = new Thread(worker);
Thread worker2 = new Thread(worker);
worker1.start();
worker2.start();
worker1.join();
worker2.join();
System.out.println(Worker.cnt);
}
}
11.3.1 wait
与 notify
只能用在 synchronized
的代码里
package org.yxc;
class Worker extends Thread {
private final Object object; // 如果这里用不传入object的写法,就需要定义为static,目的也是为了保证全局唯一,不然锁住后没法解锁
private final boolean needWait;
public Worker(Object object, boolean needWait) {
this.object = object;
this.needWait = needWait;
}
@Override
public void run() {
synchronized (object) { // 保证这个代码块的内容同一时间只有一个线程在执行
try {
if (needWait) {
object.wait();
System.out.println(this.getName() + ": 被唤醒啦!");
} else {
object.notifyAll(); // 唤醒所有线程
// object.notify(); // 唤醒随机一个线程
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
for (int i = 0; i < 5; i ++ ) {
Worker worker = new Worker(object, true);
worker.setName("thread-" + i);
worker.start();
}
Worker worker = new Worker(object, false);
worker.setName("thread-" + 5);
Thread.sleep(1000);
worker.start();
}
}
著作权信息
- 作者:yxc
- 链接:https://www.acwing.com/file_system/file/content/whole/index/content/7195051/
- 来源:AcWing
- 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。