前言:在当今的互联网行业中,多线程技术已经成为了一项非常重要的技能。随着计算机硬件的发展,越来越多的程序员开始关注多线程技术,希望通过多线程来提高程序的性能。Java作为一种广泛使用的编程语言,也提供了丰富的多线程支持。本文将详细介绍Java多线程的基本概念、原理、实现方法以及在生活中的应用,帮助读者更好地理解和掌握Java多线程技术。
1.线程属性
1.线程中断
中断线程的方法有以下三种
- 线程运行结束,自然结束
- 出现了方法中未捕获到异常,终止线程
- 使用stop方法(已经被废弃了)
- 使用interrupt方法设置线程的中断状态。 要想得出是否设置了中断状态,首先调用静态的Thread.currentThread 方法获得当前线程, 然后调用isInterrupted 方法。但 是 , 如 果 线 程 被 阻 塞 , 就 无 法 检 查 中 断 状 态。 这 里 就 要 引 人 Interrupt Exception异 常 。 当 在 一 个 被sleep 或 wait调 用 阻 塞 的 线 程 上 调 用 interrupt 方 法 时 , 那 个 阻 塞 调 用 (即 s l e e p 或 wait调用)将被一个InterruptedException异常中断。
class test implements Runnable {
public void run() {
try{
while(true){
System.out.println("在运行");
Thread.sleep(1000);
}catch(InterruptedException e){
Thread.currentThread().interrupt();
}
}
}
2.守护线程
将 一 个 线 程 转 换 为 守 护 线 程 (d a e m o n t h r e a d ) 。 这 样 — 个 线 程 并 没 有 什 么 魔 力 。 守 护 线 程 的 唯 一用途是为其他线程提供服务。计时器线程就是一个例子,它定时地发送 “计时器嘀嗒” 信 号给其他线程,另外清空过时缓存项的线程也是守护线程。当只剩下守护线程时,虚拟机就 会退出。因为如果只剩下守护线程,就没必要继续运行程序 了。看下面这个例子。
class Counter {
//线程个数
private int count;
public synchronized int getCount() {
return count;
}
//增加线程个数
public synchronized void incrementCount() {
count++;
}
//减少线程个数
public synchronized void decrementCount() {
count--;
}
}
//显示当前线程个数
class Display implements Runnable {
private Counter counter;
public Display(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
while (true) {
try {
System.out.println("Number of threads: " + counter.getCount());
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
final Counter counter = new Counter();
Thread displayThread = new Thread(new Display(counter));
displayThread.start();
for (int i = 0; i < 10; i++) {
Thread.sleep(400);
new Thread(new Runnable() {
@Override
public void run() {
//每创建一个线程就要更新counter
counter.incrementCount();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//每结速一个线程也要更新counter
counter.decrementCount();
}
}).start();
}
Thread.sleep(10000);
System.out.println("main线程已退出");
}
}
从这段代码可以看出,如果未将计数器线程设成守护线程,那么主线程结束后,只要计数器线程在运行,则程序也还在运行。当主线程结束后,我们就不需要计数器了,这个时候守护线程的就派上用场了。通过displayThread.setDaemon(true);将displayThread线程设置成守护线程。
2.线程同步于互斥
在大多数实际的多线程应用中,两个或两个以上的线程需要共享对同一数据的存取。如 果两个线程存取同一个对象,并且每个线程分别调用了一个修改该对象状态的方法,会发生 什么呢?可以想见,这两个线程会相互後盖。取决于线程访问数据的次序,可能会导致对象 被破坏 。这种情况通常称为竞态条件(racecondition)。
在多线程环境下,为了保证数据的一致性和完整性,需要对共享资源进行同步和互斥访问。Java提供了synchronized关键字来实现同步,通过synchronized关键字修饰的方法或者代码块,同一时刻只能有一个线程访问。此外,Java还提供了Lock接口及其实现类(如ReentrantLock)来实现互斥访问。
先看一个例子:
class test{
public static void main(String[] args) {
account account = new account(5000);
Thread a = new Thread(()->{
while (account.getCash()>=500){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
account.drawMoney(500);
System.out.println("余额:"+account.getCash());
}
});
Thread b = new Thread(()->{
while (account.getCash()>=1000){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
account.drawMoney(1000);
System.out.println("余额:"+account.getCash());
}
});
a.start();
b.start();
}
}
class account{
//账户余额
private int cash;
public account(int cash){
this.cash=cash;
}
//取钱
public void drawMoney(int money){
this.cash-=money;
}
public int getCash() {
return cash;
}
public void setCash(int cash) {
this.cash = cash;
}
}
运行结果:
在上面这个例子,a和b同时去取同一个账户的钱,因为二人可以同时操作账户对象,假如此时余额只有1000元,两个人都很自私,都想着干就完了,最后两人都取到了钱,但银行却亏了500元。所以这种并发地访问同一个对象尤其是要进行修改操作时,很不安全。
解决方案一:
添加synchronized注解
同步方法:将直接加到方法前面
public synchronized void drawMoney(int money){
this.cash-=money;
}
同步块
Thread a = new Thread(()->{
synchronized (account){
while (true){
if (account.getCash() >= 1000) {
try {
Thread.sleep(1000);
account.drawMoney(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("余额:" + account.getCash());
}
else break;
}
});
synchronized(要修改的对象){
要同步的代码
}
不管用哪种方法,都是将synchronized加在要修改的对象或方法上
Thread b = new Thread(() -> {
while (true) {
if (account.getCash() >= 500) {
try {
//获得锁
lock.lock();
Thread.sleep(1000);
account.drawMoney(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//释放锁
lock.unlock();
}
System.out.println("余额:" + account.getCash());
}
else break;
}
});
本期内容就到这里,如果觉得写的不错的话可以关注下一期。
下期预告:
线程间通信
线程池的使用
Java多线程在生活中的应用