热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:并发编程基础

本文由编程笔记#小编为大家整理,主要介绍了并发编程基础相关的知识,希望对你有一定的参考价值。 并发编程基础一、创建新线程1、继承Thread类优点编写简单,如果需要访问当前线程,则无需使用Thread
本文由编程笔记#小编为大家整理,主要介绍了并发编程基础相关的知识,希望对你有一定的参考价值。



并发编程基础

一、创建新线程


1、继承Thread类


优点

编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。


缺点

线程类已经继承了Thread类,所以不能再继承其他父类。

public class Create extends Thread {
public Create(String name) {
this.setName(name);
}
@Override
public void run() {
for (int x = 0; x <100; ++x)
System.out.println("Thread:" + this.getName() + " : " + x);
}
public static void main(String[] args) throws InterruptedException {
Create t1 = new Create("t1");
Create t2 = new Create("t2");
t1.start();
t2.start();
}
}

2、Runnable接口


优点

线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。


缺点

编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

public class Create implements Runnable {
public Create(String name) {
Thread.currentThread().setName(name);
}
@Override
public void run() {
for (int x = 0; x <100; ++x)
System.out.println("Thread:" + Thread.currentThread().getName() + " : " + x);
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Create("t1"));
Thread t2 = new Thread(new Create("t2"));
t1.start();
t2.start();
}
}

3、通过Callable和Future创建线程


4、Runnable和Callable的区别

1. Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()
2. Callable的任务执行后可返回值,而Runnable的任务是不能返回值的
3. call方法可以抛出异常,run方法不可以
4. 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果

二、终止进程


1.stop()方法(废弃)

stop()方法在结束进程时,会直接终止进程,并立即释放这个线程所持有的锁,而这些锁是用来维持对象一致性的,若此时写进程刚写入一半被强行终止,那么对象就会被破坏。而此时锁已被释放,另外一个进程就读到了这个不一致的对象,悲剧就会发生


2.置中断标记位

public class Create extends Thread {
static boolean stop = false;
@Override
public void run() {
int x = 0;
while (true) {
if (stop)
break;
System.out.println(x++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Create();
t1.start();
sleep(1000);
stop = true;
}
}

3.interrupt()方法

interrupt()方法类似置中断标记位,但遇到类似wait()或sleep()方法这样的操作,则只能通过interrupt()来识别了

三个看起来很像的线程方法辨别

public void Thread.interrupt();//通知线程中断,设置中断标记位
public boolean Thread.isInterrupted();//判断是否被中断
public static boolean Thread.interrupted();//判断是否被中断,并清除当前中断状态

public class Create extends Thread {
@Override
public void run() {
int x = 0;
while (true) {
System.out.println(x++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Create();
t1.start();
t1.interrupt();
}
}
//虽然调用了interrupt()方法,但是程序并无处理中断的逻辑,所以虽然线程被置为中断状态,这个中断不会发生任何作用

//正确方法
public class Create extends Thread {
@Override
public void run() {
int x = 0;
while (true) {
if (this.isInterrupted())
break;
System.out.println(x++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Create();
t1.start();
sleep(1000);
t1.interrupt();
}
}

public class Create extends Thread {
@Override
public void run() {
int x = 0;
while (true) {
if (this.isInterrupted())
break;
try {
sleep(1000000);
} catch (InterruptedException e) {
this.interrupt();
}
System.out.println(x++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Create();
t1.start();
sleep(1000);
t1.interrupt();
}
}
//在catch语句中我们已经捕获了中断可以立即退出线程,但是我们并没有这么做,因为在代码中我们还必须进行后续的处理来保证数据的一致性和完整性,因此,执行了interrupt()再次置上中断标记位,
//Thread.sleep()方法由于中断而抛出异常,此时他会清除中断标记位,如果不加处理,下次循环开始时,无法捕捉到这个中断。故而需要重新置标记位

三、wait和notify

public final void wait() throws InterruptedException;
public final native void notify();

当一个对象实例调用wait方法后,当前线程就等待,一直到其他线程调用notify方法为止。

当我们有4个线程t1,t2,t3,t4依次执行了wait方法,他们就会依次进入等待队列,但当有线程执行notify方法时,并不是按照wait的次序唤醒,而是从这四个线程中随机唤醒一个。

此外wait方法不能随意调用,必须包含在对应的synchronized ()语句中,无论是wait或notify都必须首先获得目标对象的一个监视器。

public class Create {
final static Object object = new Object();
public static class t1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + "--t1 开始!");
try {
System.out.println(System.currentTimeMillis() + "-- t1 等待");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(System.currentTimeMillis() + "--t1 结束!");
}
}
public static class t2 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + "--t2 开始");
object.notify();
System.out.println(System.currentTimeMillis() + "--t2 通知!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(System.currentTimeMillis() + "--t2 放锁!");
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new t1();
Thread t2 = new t2();
t1.start();
t2.start();
}
}
}
//运行后我们可以发现,在t2线程notify后,t1并未运行,而是在t2等待2s后释放object锁,t1获得锁后才继续运行

wait和sleep方法

这两个方法都能让线程等待若干时间,除wait方法可被唤醒之外,另一个区别是wait方法会释放目标对象的锁,而sleep方法不会释放任何资源


四、suspend(挂起)和resume(继续执行)--废弃

suspend方法在导致线程暂停的同时,并不会释放任何锁资源,而对于被挂起的线程,其运行状态仍然是Runnable


五、join(等待线程结束)和yeild(谦让)

join方法的本质是让调用线程wait在当前线程对象实例上
while(isAlive())
wait(0);
他让调用线程在当前线程对象上等待,当线程结束后,被等待的线程会在退出前调用notifyAll()方法通知所有的等待线程继续执行

yield方法一旦执行,当前线程会让出cpu,然后重新参与cpu资源的争夺,当我们觉得一个线程不那么重要,害怕其占用过多资源,就可以在适当的时候调用Thread.yield()方法,给予其他重要线程更多的工作机会。

六、volatile和JAVA内存模型(JMM)

使用volatile修饰变量
1.当变量被修改时,虚拟机会保证所有线程都能看到这个改动
2.虚拟机会保证变量被多线程修改时不会出错

public class Create {
volatile static boolean ready;
public static class t1 extends Thread {
@Override
public void run() {
while(!ready);
System.out.println("end");;
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new t1();
t1.start();
ready=true;
}
}
//若未使用volatile修饰ready,当虚拟机在Client模式下时,JIT无优化,在主线程修改ready后,线程能发现改动。但是在Server模式下,由于系统优化,线程可能永远无法发现变量改动,导致死循环

七、线程组

public class Create {
public static class thread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getThreadGroup().getName() + "--" + Thread.currentThread().getName());
}
}
public static void main(String[] args) throws InterruptedException {
ThreadGroup tg = new ThreadGroup("PrintGroup");
Thread t1 = new Thread(tg, new thread(), "t1");
Thread t2 = new Thread(tg, new thread(), "t2");
t1.start();
t2.start();
System.out.println(tg.activeCount());
tg.list();
}
}

常用方法

activeAccount()//活跃线程数
list()//活跃线程信息
stop()//停止线程组所有线程,存在普通线程Stop方法的相同问题,不推荐使用

八、守护线程

volatile static int x = 0;
public static class ReadDaemon implements Runnable {
@Override
public void run() {
while (true)
System.out.println(x);
}
}
public static class Modify implements Runnable {
@Override
public void run() {
for (x = 0; x <100; ++x) {
try {
sleep(100);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread rd = new Thread(new ReadDaemon());
Thread modify = new Thread(new Modify());
rd.setDaemon(true);
rd.start();
modify.start();
modify.join();
}
//设置守护线程必须在start前,当其他线程结束后,守护线程会随之自动结束

九、线程优先级

Thread thread = new Thread(new Modify());
thread.setPriority(Thread.MAX_PRIORITY);
thread.setPriority(8);
//优先级从低到高0-10,高优先级线程在竞争资源时会更有优势,但这只是概率问题,很有可能高优先级线程一直抢占失败,因此在要求严格的场合,还是需要自己在应用层解决线程调度问题

十、synchronized

实现线程间同步,对同步代码进行加锁,使得每一次只有一个线程进入同步块,从而保证线程间的安全性

使用方法

- 对象加锁: 对给定对象加锁,进入同步代码前需要获得给定对象的锁
- 作用于实例方法: 对当前实例加锁,进入同步代码前需要获得当前实例的锁
- 作用于静态方法:对当前类加锁,进入同步代码前需要获得当前类的锁

对象加锁

public static class Test implements Runnable {
static final Test test = new Test();
static int x = 0;
@Override
public void run() {
for (int i = 0; i <100; ++i)
synchronized (test) {
++x;
}
}
}

实例方法加锁

public static class Test implements Runnable {
static int x = 0;
public synchronized void increase() {
++x;
}
@Override
public void run() {
for (int i = 0; i <100; ++i)
increase();
}
}

实例方法错误加锁

public static class Test implements Runnable {
static int x = 0;
public synchronized void increase() {
++x;
}
@Override
public void run() {
for (int i = 0; i <100; ++i)
increase();
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Test());
Thread t2 = new Thread(new Test());
t1.start();t2.start();
t1.join();t2.join();
System.out.println(x);
}
//虽然increase是一个同步方法,但是t1,t2指向不同的Test实例,所以这两个线程的实例并非同一个对象,因此结果会出错,修正方法就是下面的静态方法加锁

类加锁

public static class Test implements Runnable {
static int x = 0;
public static synchronized void increase() {
++x;
}
@Override
public void run() {
for (int i = 0; i <100; ++i)
increase();
}
}
//使用static,即使两个线程指向不同的对象,但方法块请求的事当前类的锁,而非当前实例,因此可正常执行

十一、synchronized和Integer

public class Create implements Runnable {
public static Integer i = 0;
static Create instance = new Create();
@Override
public void run() {
for (int j = 0; j <100000; ++j)
synchronized (i) {
++i;
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
//这个程序表面看起来没有问题,但实际运行会出错,因为Integer输入不变对象。一旦被创建就不能被修改,所以加锁的i对象每次都不是同一个Integer实例,加锁失败

附 Integer详细说明

Integer详细说明


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了logistic回归(线性和非线性)相关的知识,包括线性logistic回归的代码和数据集的分布情况。希望对你有一定的参考价值。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • OO第一单元自白:简单多项式导函数的设计与bug分析
    本文介绍了作者在学习OO的第一次作业中所遇到的问题及其解决方案。作者通过建立Multinomial和Monomial两个类来实现多项式和单项式,并通过append方法将单项式组合为多项式,并在此过程中合并同类项。作者还介绍了单项式和多项式的求导方法,并解释了如何利用正则表达式提取各个单项式并进行求导。同时,作者还对自己在输入合法性判断上的不足进行了bug分析,指出了自己在处理指数情况时出现的问题,并总结了被hack的原因。 ... [详细]
author-avatar
我是田小勇2702932553
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有