Java=多线程-高并发和线程安全,volatile,原子类
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java=多线程-高并发和线程安全,volatile,原子类,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含13158字,纯文字阅读大概需要19分钟。
内容图文
一,多线程
并行与并发
并行: 两个事件,在同一个时刻,都在发生
并发: 两个事件,在同一个时间段内,都在发生(交替执行)
进程与线程
进程: 正在内存中运行的程序,我们称为进程
线程: 进程中完成某个小功能的模块(进程中用执行某个功能的执行单元)
线程是属于某个进程的
每个进程都有独立的内存空间(独立的栈独立的堆等),并且至少有一个线程
每个线程都会跟进程申请一块独立栈,共享进程的堆
线程调用是指CPU在不同的进程不同的线程之间进行快速切换
线程调度的分类:
分时调度: 每个线程平均拥有CPU的执行权
抢占式调用: 每个线程随机分配CPU的执行权(具体的分配多少和线程优先级有关)
我们Java程序(Java进程)中所有线程采用抢占式调度
线程的状态
Thread对象共有6种状态:NEW(新建),RUNNABLE(运行),BLOCKED(阻塞),WAITING(等待),TIMED_WAITING(有时间的等待),TERMINATED(终止);状态转换如下:
1.Thread类
a.Thread类是什么?
Thread是Java定义好的,代表线程的类,只要创建该类的一个对象,其实就是创建了一个线程
b.Thread类的构造方法
public Thread(); // 无参构造,线程会有默认的名字,Thread-0,Thread-1等...
public Thread(String name); //带有线程名字的构造
public Thread(Runnable r);//带有线程任务的构造
public Thread(Runnable r,String name); //即带有线程名字,又带有线程任务的构造
c.Thread类的成员方法
public String getName(); //获取线程的名字
public void setName(String name);//修改线程的名字
public void run();//代表线程要执行的任务,任务有关的代码需要写在次方法中
public void start();//线程只创建并不会执行,必须调用start开启后才会执行任务
public static void sleep(long millis); //让当前线程"休眠/暂停"多少毫秒
这里的当前线程只指 Thread.sleep(1000)这句代码写哪个线程中,哪个线程就是当前线程
public static Thread currentThread();//获取当前线程对象
这里的当前线程是指 Thread.currentThread() 这句代码写哪个线程中,哪个线程就是当前线程
线程执行有优先级,优先级越高先执行机会越大(并不是一定先执行!!)。优先级用int的priority参数表示。
线程优先级最高为10,最低为1。默认为5
2.创建新的线程的方式-继承
a.描述:
将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
b.分析创建的步骤:
i.创建子类 继承 Thread
ii.子类中重写run方法(在run中编写线程要执行的任务代码)
iii.创建子类对象(实际上就是创建一个线程对象)
iv. 调用线程对象的start方法(启动该线程)
c.案例:
//i.创建子类 继承 Thread
public class MyThread extends Thread {
//ii.子类中重写run方法(在run中编写线程要执行的任务代码)
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("子线程..."+i);
}
}
}
public class ThreadDemo02 {
public static void main(String[] args) {
// iii.创建子类对象(实际上就是创建一个线程对象)
MyThread mt = new MyThread();
//iv. 调用线程对象的start方法(启动该线程)
mt.start();
//主线程 不会等待子线程任务结束
for (int i = 0; i < 50; i++) {
System.out.println("主线程..."+i);
}
}
}
注意:
a.我们可以给线程起名字,也可以使用默认的名字
b.我们获取线程的名字时:
建议使用通用方式: Thread.currentThread().getName();
如果是子线程内部也可以直接调用getName()获取子线程的名字
创建新的线程方式二_实现方式
a.描述
声明实现 Runnable 接口的类。该类然后实现 run 方法。
然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递 并启动.
b.分析步骤:
i.创建实现类 实现 Runnable接口(实际上接口中一个任务方法,run方法)
ii.实现类重写run方法(run中编写具体的任务代码)
iii.创建实现类对象(该实现类对象并不是线程对象,我们称为任务对象)
iv. 创建Thread对象,同时传入实现类对象
public Thread(Runnable r);//带有线程任务的构造
v. 启动该线程(调用线程对象的start方法)
c.代码实现
//i.创建实现类 实现 Runnable接口(实际上接口中一个任务方法,run方法)
public class MyRunnable implements Runnable {
//ii.实现类重写run方法(run中编写具体的任务代码)
@Override
public void run() {
//run中写任务代码
for (int i = 0; i < 50; i++) {
System.out.println("子线程..."+i);
}
}
}
public class TestThread {
public static void main(String[] args) {
//iii.创建实现类对象(该实现类对象并不是线程对象,我们称为任务对象)
MyRunnable mr = new MyRunnable();
//iv. 创建Thread对象,同时传入实现类对象
Thread tt = new Thread(mr);
//v. 启动该线程(调用线程对象的start方法)
tt.start();
//主线程不会等待子线程执行完毕
for (int i = 0; i < 50; i++) {
System.out.println("主线程..."+i);
}
}
}
两种方式的优劣比较
两种创建线程的方式,实现方式比较好
a.实现方式比较好,因为实现方式线程和任务是分开,是由程序员自己组合
b.实现方式避免了Java单继承不足
c.实现方式线程和任务是解耦的,继承方式线程和任务是耦合的
d.对于线程池来说,我们需要的是Runnable的实现类,而不需要Thread的子类
综上所述: 在开发中我们建议使用实现方式(并不是说继承方式不对)
匿名内部类简化创建线程方式
匿名内部类作用:
可以快速创建一个类的子类对象或者一个接口的实现类对象
public class TestDemo {
public static void main(String[] args) {
//1.继承方式创建线程
new Thread(){
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"..."+i);
}
}
}.start();
//2.实现方式创建线程
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"..."+i);
}
}
}).start();
//主线程不会等待子线程任务结束
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"..."+i);
}
}
}
二。高并发,和线程安全
高并发及线程安全的介绍
什么是高并发: 是指在某个时间点上,有大量的用户(线程)同时访问同一资源
线程安全: 是指在某个时间点上,发生高并后,访问的数据出现"不合符实际的数据",称为线程安全有问题
多线程的运行机制
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("i = " + i);
}
}
}
public class Demo {
public static void main(String[] args) {
//1.创建两个线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//2.启动两个线程
t1.start();
t2.start();
}
}
多线程的安全性问题--可见性
什么有可见性:
当一个共性变量,被多个线程使用时,其中某个线程对共性变量进行了修改,对于其他线程来说并不是立刻可见的
其他线程获取的值还是以前的副本(旧的值)
案例:
public class MyThread extends Thread {
//无论创建多个MyThread对象,他们共性一个静态变量a
public static int a = 0;
@Override
public void run() {
System.out.println("线程启动,休息2秒...");
try { Thread.sleep(1000 * 2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("将a的值改为1");
a = 1;
System.out.println("线程结束...");
}
}
public class TestSafaDemo01 {
public static void main(String[] args) {
//1.启动线程
MyThread t = new MyThread();
t.start();
//2.主线程继续
while (true) {
if (MyThread.a == 1) {
System.out.println("主线程读到了a = 1");
}
}
}
}
多线程的安全性问题-有序性
什么是有序性:
在不影响代码的结果的程度上对代码进行"重排"
如果在多线程的情况下,"重排"可能对一样的代码,执行后得出不一样的结果
我们要保证在多线程的情况下,不对代码进行"重排",保证代码是有序(不要使用重排!!)
多线程的安全性问题-原子性
什么原子性:
线程对一个共性变量,进行++时,这个++分成两步操作,先取出值加1 然后给共性变量赋值
如果取出值加1后,还没有来得及赋值,被其他线程抢走CPU,此时我们称为++操作不具有原子性
三。volatile关键字
volatile是一个关键字,用来修饰成员变量(静态变量),被他修饰的变量,具有可见性和有序性
volatile解决可见性
public class MyThread extends Thread {
//无论创建多个MyThread对象,他们共性一个静态变量a
public volatile static int a = 0;
@Override
public void run() {
System.out.println("线程启动...");
try { Thread.sleep(1000 * 2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("将a的值改为1");
a = 1;
System.out.println("线程结束...");
}
}
public class TestSafaDemo01 {
public static void main(String[] args) {
//1.启动线程
MyThread t = new MyThread();
t.start();
//2.主线程继续
while (true) {
if (MyThread.a == 1) {
System.out.println("主线程读到了a = 1");
}
}
}
}
volatile解决有序性
volatile不能解决原子性
volatile不能解决原子性问题
public class MyThread extends Thread {
public volatile static int a = 0;
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println("修改完毕!");
}
}
public class TestSafeDemo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start(); //线程1 对a加了10000次
t2.start(); // 线程2 对a加了 10000次
Thread.sleep(1000);
System.out.println("获取a最终值:" + MyThread.a);
//总是不准确的。原因:两个线程访问a 的步骤不具有:原子性
}
}
volatile的作用
a.解决变量的可见性,一旦变量发生改变,所有使用到该变量的线程都会取到最新值
b.解决变量的有序性,一旦变量加上volatile,那么编译器不会该变量的代码进行重排
c.无法解决变量操作过程中原子性,对变量的操作还是有可能被其他线程打断
四。原子类
a.什么是原子类?
在java.util.concurrent.atomic包下定义了一些对“变量”操作的“原子类”: 1).java.util.concurrent.atomic.AtomicInteger:对int变量操作的“原子类”; 2).java.util.concurrent.atomic.AtomicLong:对long变量操作的“原子类”; 3).java.util.concurrent.atomic.AtomicBoolean:对boolean变量操作的“原子类”;
是对普通类型(比如:int,Integer,double,Double)的原子类封装,使其的操作成员原子操作
b.原子类的作用?
对原子类的增加或者减少操作,保证是原子性,保证中间不会被其他线程"打断"
c.原子类有哪些?
比如:
AtomicInteger是对int变量进行操作的原子类
AtomicLong是对long变量进行操作的原子类
AtomicBoolean对boolean变量操作的“原子类”;
注意: 原子类,既可以解决原子性,也可以解决有序性和可见性
AtomicInteger类
a.AtomicInteger是什么?
是对int类型变量进行操作的原子类
b.AtomicInteger的构造方法
public AtomicInteger(int num);
c.AtomicInteger的成员方法
public int getAndIncrement();//就相当于 变量++
public int incrementAndGet();//就相当于 ++变量
d.使用AtomicInteger改写案例
public class MyThread extends Thread {
public static AtomicInteger a = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a.getAndIncrement();//相当于 a++ 先获取在自增1
}
System.out.println("修改完毕!");
}
}
public class TestSafeDemo {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start(); //线程1 对a加了10000次
t2.start(); // 线程2 对a加了 10000次
Thread.sleep(1000);
System.out.println("获取a最终值:" + MyThread.a);
//总是不准确的。原因:两个线程访问a 的步骤不具有:原子性
}
}
在Unsafe类中,调用了一个:compareAndSwapInt()方法,此方法的几个参数: var1:传入的AtomicInteger对象 var2:AtommicInteger内部变量的偏移地址 var5:之前取出的AtomicInteger中的值; var5 + var4:预期结果 此方法使用了一种"比较并交换(Compare And Swap)"的机制,它会用var1和var2先获取内存中 AtomicInteger中的值,然后和传入的,之前获取的值var5做一下比较,也就是比较当前内存的值和预期的值 是否一致,如果一致就修改为var5 + var4,否则就继续循环,再次获取AtomicInteger中的值,再进行比较并 交换,直至成功交换为止。 compareAndSwapInt()方法是"线程安全"的。 我们假设两个线程交替运行的情况,看看它是怎样工作的: 初始AtomicInteger的值为0 线程A执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:0 线程A被暂停 线程B执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:0 线程B执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4) 线程B成功将AtomicInteger中的值改为1 线程A恢复运行,执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4) 此时线程A使用var1和var2从AtomicInteger中获取的值为:1,而传入的var5为0,比较失败,返回 false,继续循环。 线程A执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:1 线程A执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4) 此时线程A使用var1和var2从AtomicInteger中获取的值为:1,而传入的var5为1,比较成功,将其修改 为var5 + var4,也就是2,将AtomicInteger中的值改为2,结束。 CAS机制也被称为:乐观锁。因为大部分比较的结果为true,就直接修改了。只有少部分多线程并发的情况会 导致CAS失败,而再次循环。
AtomicIntegerArray类
常用的数组操作的原子类: 1).java.util.concurrent.atomic.AtomicIntegetArray:对int数组操作的原子类。 2).java.util.concurrent.atomic.AtomicLongArray:对long数组操作的原子类。 3).java.utio.concurrent.atomic.AtomicReferenceArray:对引用类型数组操作的原子类。
非原子类数组在多线程并发时会有问题
public class MyThread extends Thread {
public static int[] intArray = new int[1000];//不直接使用数组
@Override
public void run() {
for (int i = 0; i < intArray.length; i++) {
intArray[i]++;
}
}
}
public class TestDemo01 {
public static void main(String[] args) throws InterruptedException {
//创建1000个线程,每个线程为数组的每个元素+1
for (int i = 0; i < 1000; i++) {
new MyThread().start();
}
Thread.sleep(1000 * 5);//让所有线程执行完毕
System.out.println("主线程休息5秒醒来");
for (int i = 0; i < MyThread.intArray.length; i++) {
System.out.println(MyThread.intArray[i]);
}
}
}
打印结果:
1000,1000,1000,999,1000,1000,1000,1000,....
有个别元素是小于1000的,因为int[]是非原子类数组,不能保存原子性!!!
使用原子类数组,保证原子性,解决问题
public class MyThread extends Thread {
public static int[] intArray = new int[1000];//不直接使用数组
public static AtomicIntegerArray arr = new AtomicIntegerArray(intArray);
@Override
public void run() {
// for (int i = 0; i < intArray.length; i++) {
// intArray[i]++;
// }
for (int i = 0; i < arr.length(); i++) {
arr.addAndGet(i, 1);//将i位置上的元素 + 1,相当于 ++数组[i]
}
}
}
public class TestDemo01 {
public static void main(String[] args) throws InterruptedException {
//创建1000个线程,每个线程为数组的每个元素+1
for (int i = 0; i < 1000; i++) {
new MyThread().start();
}
Thread.sleep(1000 * 5);//让所有线程执行完毕
System.out.println("主线程休息5秒醒来");
// for (int i = 0; i < MyThread.intArray.length; i++) {
// System.out.println(MyThread.intArray[i]);
// }
for (int i = 0; i < MyThread.arr.length(); i++) {
System.out.println(MyThread.arr.get(i));
}
}
}
内容总结
以上是互联网集市为您收集整理的Java=多线程-高并发和线程安全,volatile,原子类全部内容,希望文章能够帮你解决Java=多线程-高并发和线程安全,volatile,原子类所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。