Java 中的多线程实现较为简单,这篇文章主要讲解多线程操作中状态操作相关的代码示例,希望可以帮助你提高对多线程的理解。
在开启一个软件后,操作系统会分配给软件一个进程,进程即该软件所在的内存区域,是软件运行时状态的一个抽象,是一个整体,进程中必须包含线程,不可独立存在。
线程的宿主是进程,一个进程代表一个软件,线程为一个进程中正在并行执行的一些功能,打个比方,QQ 有接收消息的功能还有上线提醒的功能,它们同时进行,互不干扰。
在多核 CPU 中,通过软件或者硬件来实现多个核心协同工作,达到充分使用 CPU 的目的。在单核CPU时,操作系统会进行伪多线程处理,即每个线程随机短暂运行并快速切换,实现多任务处理。
class DemoTask extends Thread {
public static List<String> list = new ArrayList<>();
@Override
public void run() {
System.out.println("hello Thread");
}
}
class DemoTask implements Runnable {
public static List<String> list = new ArrayList<>();
@Override
public void run() {
System.out.println("hello Runnable");
}
}
实际开发中,推荐实现 Runnable 接口,因为 Java 是支持多实现的但是仅仅支持单继承,如果继承了 Thread 对象,那就不能继承其他对象了。
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
public class TestRunnable {
public static void main(String[] args){
NewRunnable runnable = new NewRunnable();
//分别创建两个线程,并传入runnable参数
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
//运行线程
thread1.start();
thread2.start();
System.out.println("End....");
}
}
class NewRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在这里插入图片描述
该方法主要作用是当前线程主动放弃时间片,回到就绪状态,竞争下一次的时间片,比如说 A 和 B 两个进程 A 运行一段时间后 CPU 分配运行权利给了 B,但是 B如果调用该方法就会放弃运行的权利,于是 CPU 又去运行 A 或者其他线程了。
public class TestRunnable2 {
public static void main(String[] args) {
new Thread(new Task1(), "Task1").start();
new Thread(new Task2(), "Task2").start();
}
}
class Task1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("\r\nreading...:" + Thread.currentThread().getName());
System.out.println("running...:" + Thread.currentThread().getName());
}
}
}
class Task2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("\r\nreading...:" + Thread.currentThread().getName());
Thread.yield();
System.out.println("running...:" + Thread.currentThread().getName());
}
}
}
执行结果显示有三次 有可能放弃执行权发生,注意为什么是有可能,因为不排除在执行完 reading… Task2 后 Task2丢失执行权的可能。
在这里插入图片描述
这个方法可以理解为,我在执行代码的时候,我的好朋友线程有更要紧的事情要处理,所以我使用此方法,让它先运行,等朋友线程运行完毕后,我再运行,多么美好的基情,好基友就该这样。 注意,join有三个重载 void join() 等待这个线程终止。 void join(long millis) 等待这个线程终止最多millis毫秒。 void join(long millis, int nanos) 等待最多 millis毫秒加上 nanos纳秒这个线程终止。
package com.example.demo;
public class TestRunnable3 {
public static void main(String[] args) throws InterruptedException {
Thread task = new Thread(new Task(), "Task");
task.start();
for (int i = 0; i < 10; i++) {
System.out.println("running is " + Thread.currentThread().getName());
task.join();
}
}
}
class Task implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("\r\nreading...:" + Thread.currentThread().getName());
System.out.println("running...:" + Thread.currentThread().getName());
}
}
}
可以看到 在 main 线程刚开始运行时,就唤醒了 Task线程,并且直到 Task 线程执行完毕后,main 线程才继续执行
在这里插入图片描述
举个例子,线程 A 和线程 B,同时需要打印 S 数组的最后一个元素后并清空集合,线程 A 首先拿到时间片也就是所谓的执行权,进行了判空操作通过,这时时间片失效,执行权落到了线程 B那里,线程 B 执行判空操作后顺利打印了元素并替换了空集合,这时线程 A 继续执行访问元素,代码抛出了角标越界异常。
public class Test4 {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("OK");
Thread thread1 = new Thread(new DemoTask1(list));
Thread thread2 = new Thread(new DemoTask2(list));
thread1.start();
thread2.start();
}
}
class DemoTask1 implements Runnable {
List<String> list;
public DemoTask1(List<String> list) {
this.list = list;
}
@Override
public void run() {
try {
if (list.size() > 0) {
Thread.sleep(1000);
String s = list.get(0).toLowerCase();
list.clear();
System.out.println(s);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class DemoTask2 implements Runnable {
List<String> list;
public DemoTask2(List<String> list) {
this.list = list;
}
@Override
public void run() {
if (list.size() > 0) {
String s = list.get(0).toLowerCase();
list.clear();
System.out.println(s);
}
}
}
以上代码会出发线程安全问题,为什么会这样?这种情况出现的原因主要是因为这里访问了同一个线程共享的对象;因此就会出现这种类似“争抢”的情况发生;这就是所谓的线程不安全。
当线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
临界资源: 共享资源(同一对象),一次允许一个线程使用,才可以保证其正确性。 原子操作:: 不可分割的多步操作,被视为一个整体,其顺序和步骤不能打乱或缺省(比如此处,A在执行过程中因时间片到期,却被B“插了队”,这就破坏了原子操作)
在这里插入图片描述
每个对象都有一个互斥标记锁,用来分配给线程的。只有拥有对象互斥锁标记的线程才能进入该对象加锁的同步代码块。线程退出同步代码块时会释放相应的互斥锁标记。
synchronized (临界资源对象){ //对临界资源加锁
//代码(原子操作)
}
将 synchronized 添加到以上示例中发现,开再多的线程也不会出现线程安全问题了,就是这么简单的解决了线程安全问题。
class DemoTask1 implements Runnable {
List<String> list;
public DemoTask1(List<String> list) {
this.list = list;
}
@Override
public void run() {
try {
synchronized (list) {
if (list.size() > 0) {
Thread.sleep(1000);
String s = list.get(0).toLowerCase();
list.clear();
System.out.println(s);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class DemoTask2 implements Runnable {
List<String> list;
public DemoTask2(List<String> list) {
this.list = list;
}
@Override
public void run() {
synchronized (list) {
if (list.size() > 0) {
String s = list.get(0).toLowerCase();
list.clear();
System.out.println(s);
}
}
}
}
线程总共有以下几种状态
图片来自 Lukey Alvin 的博客
NEW (初始状态) 尚未启动的线程处于此状态。 RUNNABLE (运行状态) 在 Java 虚拟机中执行的线程处于此状态。 BLOCKED (阻塞状态) 被阻塞等待监视器锁定的线程处于此状态。 WAITING (无限期等待) 正在等待另一个线程执行特定动作的线程处于此状态。 TIMED_WAITING (有限期等待) 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。 TERMINATED (终止状态) 已退出的线程处于此状态。