Java-多线程与线程安全基础

44次阅读

共计 5318 个字符,预计需要花费 14 分钟才能阅读完成。

进程与线程

进程: 指一个内存中运行的应用程序, 每个进程都有自己独立的一块内存空间, 一个进程中可以有多个线程.

线程: 指进程中的一个执行任务(控制单元), 是程序执行时的最小单位, 是 CPU 调度和分派的基本单位, 一个进程可以运行多个线程, 多个线程可共享资源.

并行与并发

并行: 多个进程同时运行, 发生在多 CPU 中, 一个进程占一个 CPU, 所有进程同步运行, 互相之间不抢夺资源

并发: 多个进程间隔运行, 发生在单 CPU 中, 每个进程切换运行, 切换时间很短, 所以看起来好像是同时在运行, 互相之间抢夺资源

同步与异步

同步: 多个线程有序执行, 前面的执行完了后面的补上, 在前面线程运行时其他线程都在等待

异步: 多个线程同时进入就绪状态, 等待 CPU 的统一调度, 当线程进入运行状态时其他线程可以访问其他资源, 把访问资源的空闲时间利用起来, 能提高效率, 也就是多线程机制

线程分类

应用线程: 前台线程, 执行各种具体任务的线程, 一个程序启动至少有一个应用线程 (main 线程) 和一个守护线程(GC)

守护线程: 后台线程, 随着前台线程的全部死亡, 守护线程也会自动死亡

线程的创建和启动

继承方式创建

自定义类继承 Thread

重写 run 方法, 把线程任务封装进 run 方法中

创建自定义对象

自定义对象调用 start()方法, 开启线程

// 继承方式创建线程

public class MyThreadDemo extends Thread {// 继承 Thread 类创建线程

// 重写 run 方法, 把线程任务封装进 run 方法

@Override

public void run() {

for (int i = 0; i < 10; i++) {

System.out.println(“ 吃了 ” + (i + 1) + “ 顿 ”);

}

}

}

// 开启线程

public class MyThreadTestDemo {

public static void main(String[] args) {

// 创建线程对象, 可以使用多态的方法创建, Thread m = new MyThreadDemo();

MyThreadDemo m = new MyThreadDemo();

// 开启线程

m.start();

// 线程开启后进入独立模块,CPU 调度随机, 也就是说两个线程随机运行

for (int i = 0; i < 10; i++) {

System.out.println(“ 睡了 ” + (i + 1) + “ 天 ”);

}

}

}

接口实现方式创建

自定义类实现 Runnable 接口

重写 run 方法, 把线程任务封装进 run 方法

创建自定义对象

创建 Thread 类对象, 将自定义对象作为参数传入 Thread 类创建对象的构造器

Thread 对象调用 start()方法, 开启线程

// 实现 Runnable 接口创建线程

public class MyRunnable implements Runnable{// 实现 Runnable 接口创建线程

// 重写 run 方法, 将线程任务封装进 run 方法

@Override

public void run() {

for (int i = 0; i < 10; i++) {

System.out.println(“ 吃了 ” + (i + 1) + “ 顿 ”);

}

}

}

// 启动线程

public class MyRunnableDemo {

public static void main(String[] args) {

// 创建线程对象

MyRunnable m = new MyRunnable();

// 创建 Thread 类对象, 将线程对象作为构造器参数传过去

Thread t = new Thread(m);

// 开启线程

t.start();

for (int i = 0; i < 10; i++) {

System.out.println(“ 睡了 ” + (i + 1) + “ 天 ”);

}

}

}

线程的生命周期

当程序创建一个线程以后, 线程处于新建状态, 无法被 CPU 调度, 需要调用 start()方法开启线程, 调用 start()方法之后线程进入就绪状态, 等待 CPU 调度

当就绪状态的线程被 CPU 调度时进入运行状态, 运行状态和就绪状态可以相互切换, 当多个线程切换速度很快时, 我们看起来就像多个线程在同步运行, 这就是并发

运行状态可以切换到等待状态, 它会等待另一个线程来执行一个任务, 一个等待状态的线程只有通过另一个线程通知它转到可运行状态, 才能继续执行

运行状态转到等待状态可以设置一个计时器, 等待特定的时间之后唤醒线程对象

运行状态遇到异常或者其他特殊状况导致不能运行时进入阻塞状态, 让出 CPU 调度, 停止自身运行

当线程执行完或者抛出未捕获的异常和错误时, 或者调用线程的 stop()方法, 线程终止, 生命周期结束

操作线程的方法
智汇代理申请 https://www.kaifx.cn/broker/t…

join 方法: 主要作用是同步, 它可以使得线程之间的并行执行变为串行执行. 比如在 A 线程中调用了 B 线程的 join()方法时, 表示只有当 B 线程执行完毕时,A 线程才能继续执行

sleep 方法: 让正在执行的线程暂停一段时间, 进入阻塞状态.

// sleep(long milllis) throws InterruptedException; 毫秒为单位

Thread.sleep(1000); // 需要处理异常, 使用 try-catch

1

2

线程的优先级: 每个线程都有优先级, 优先级的高低只和线程获得执行机会的次数多少有关, 并不是说优先级高的就一定先执行, 哪个线程的先运行取决于 CPU 的调度.Thread 对象的 setPriority(int x)和 getPriority()用来设置和获得优先级,x 的值不能大于线程执行的次数, 否则报 IllegalThreadStateException 异常

后台线程: 守护线程, 一般用于为其他线程提供服务, 若前台线程都死亡, 后台线程自动死亡.Thread 对象 setDaemon(true)用来设置后台线程,setDaemon(true)必须在 start()调用前, 否则抛出 IllegalThreadStateException 异常

线程安全性: 当多线程并发访问同一资源时会导致线程出现安全性的原因

需求: 现有 100 个苹果, 现在有 3 个人去吃.

继承方式

// 线程类, 继承 Thread 类

public class SafeThread extends Thread {

private int number = 100; // 苹果数量

public SafeThread(String name) {// 构造器, 调用父类构造器把值传给父类

super(name);

}

// 重写 run 方法, 把线程任务封装进去

public void run() {

for (int i = 0; i < 100; i++) {

if (number > 0) {

System.out.println(Thread.currentThread().getName()

+ “ 吃了第 ” + number– + “ 个苹果 ”);

}

}

}

}

// 测试类

public class SafeThreadDemo {

public static void main(String[] args) {

// 创建对象

Thread s1 = new SafeThread(“xx”);

Thread s2 = new SafeThread(“yy”);

Thread s3 = new SafeThread(“zz”);

// 开启线程

s1.start();

s2.start();

s3.start();

}

}

// 结果是 3 个人每个人都吃了 100 个苹果, 继承方式多个线程不能共享同一资源

实现接口方式

// 创建线程类实现 Runnable 接口

public class SafeRunnable implements Runnable {

private int number = 100; // 苹果数量

// 重写 run 方法, 把线程任务封装进去

public void run() {

for (int i = 0; i < 100; i++) {

if (number > 0) {

System.out.println(Thread.currentThread().getName()

+ “ 吃了第 ” + number– + “ 个苹果 ”);

}

}

}

}

// 测试类

public class SafeRunnableDemo {

public static void main(String[] args) {

// 创建线程对象

SafeRunnable s = new SafeRunnable();

// 创建 Thread 对象把线程对象当作参数参数传过去, 开启线程

new Thread(s,”xx”).start();

new Thread(s,”yy”).start();

new Thread(s,”zz”).start();

}

}

// 结果是 3 个人平分了 100 个苹果, 实现方式可以多个线程共享同一资源

继承方式与实现方式的区别

Java 中类是单继承, 如果继承了 Thread 类就不能再继承其他类了, 而实现方式不但可以再继承其他类, 还可以实现多个接口, 所以实现方法比较好用

继承方式多个线程没法共享同一资源, 实现方式可以做到多个线程共享同一资源

线程同步

当多线程访问同一资源对象的时候可能会出现线程不安全的问题, 实现 Runnable 接口创建线程的时候看起来可能没有问题, 但是出现网络延迟的时候就会出现, 这里用线程睡眠来模拟网络延迟

// 添加线程睡眠

public class SafeRunnable implements Runnable {

private int number = 100; // 苹果数量

// 重写 run 方法, 把线程任务封装进去

public void run() {

for (int i = 0; i < 100; i++) {

if (number > 0) {

try {

Thread.sleep(100); // 让线程休眠 0.1 秒再运行, 相当于网络出现 0.1 秒延迟

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()

+ “ 吃了第 ” + number– + “ 个苹果 ”);

}

}

}

}

// 测试类

public class SafeRunnableDemo {

public static void main(String[] args) {

// 创建线程对象

SafeRunnable s = new SafeRunnable();

// 创建 Thread 对象把线程对象当作参数参数传过去, 开启线程

new Thread(s,”xx”).start();

new Thread(s,”yy”).start();

new Thread(s,”zz”).start();

}

}

解决思路: 在一个线程执行该任务的时候其他的线程不能来打扰, 也就是设置一个同步锁, 比如说 A 线程进入同步锁进行操作的时候,B 和 C 以及其他的线程只能在外面等着,A 操作结束, 释放同步锁,A,B,C 以及其他线程才会有机会去抢同步锁(A 线程只要没有执行完所有任务, 同样进入争夺同步锁的行列, 谁获得同步锁, 谁才能执行代码)

同步代码块

同步锁:

对于非 static 方法, 同步锁就是 this

对于 static 方法, 同步锁就是当前方法所在类的字节码对象(.class 文件)

public class SafeRunnable implements Runnable {

private int number = 100; // 苹果数量

// 重写 run 方法, 把线程任务封装进去

public void run() {

for (int i = 0; i < 100; i++) {

/*

加同步锁

java 允许使用任何对象作为同步监听对象

一般我们把当前并发访问的共同资源作为同步监听对象

这里的 SafeRunnable 对象拥有三个线程共同的资源,

而且只有一份, 所以可以用来做同步锁

*/

synchronized (this) {// 加同步锁

if (number > 0) {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()

+ “ 吃了第 ” + number– + “ 个苹果 ”);

}

}

}

}

}

同步方法

public class SafeRunnable implements Runnable {

private int number = 100; // 苹果数量

// 重写 run 方法, 把线程任务封装进去

public void run() {

for (int i = 0; i < 100; i++) {

doWork();

}

}

// synchronized 修饰的方法是同步方法

synchronized public void doWork() {

if (number > 0) {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()

+ “ 吃了第 ” + number– + “ 个苹果 ”);

}

}

}

synchronized 的优劣势

好处: 保证了多线程并发访问时的同步操作, 避免线程的安全性问题

缺点: 使用 synchronized 的方法 / 代码块的性能要低一些

建议: 尽量减小 synchronized 的作用域

正文完
 0