Java中的 多线程编程

一、多线程的优缺点

多线程的优点:

1)资源利用率更好2)程序设计在某些情况下更简单3)程序响应更快

多线程的代价:

1)设计更复杂虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂。在多线程访问共享数据的时候,这部分代码需要特别的注意。线程之间的交互往往非常复杂。不正确的线程同步产生的错误非常难以被发现,并且重现以修复。

2)上下文切换的开销当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行。这种切换称为“上下文切换”(“context switch”)。CPU会在一个上下文中执行一个线程,然后切换到另外一个上下文中执行另外一个线程。上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生。

二、创建java多线程

1、创建Thread的子类

创建Thread子类的一个实例并重写run方法,run方法会在调用start()方法之后被执行。例子如下:

复制代码

复制代码

public class MyThread extends Thread {
   public void run(){
     System.out.println("MyThread running");
   }
}

MyThread myThread = new MyThread();
myTread.start();


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">

也可以如下创建一个Thread的匿名子类:

复制代码

Thread thread = new Thread(){
   public void run(){
     System.out.println("Thread Running");
   }
};
thread.start();
复制代码

2、实现Runnable接口

第二种编写线程执行代码的方式是新建一个实现了java.lang.Runnable接口的类的实例,实例中的方法可以被线程调用。下面给出例子:

复制代码

复制代码

public class MyRunnable implements Runnable {
   public void run(){
    System.out.println("MyRunnable running");
   }
}

Thread thread = new Thread(new MyRunnable());
thread.start();


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">

同样,也可以创建一个实现了Runnable接口的匿名类,如下所示:

复制代码

复制代码

Runnable myRunnable = new Runnable(){
   public void run(){
     System.out.println("Runnable running");
   }
}
Thread thread = new Thread(myRunnable);
thread.start();
复制代码

复制代码

三、线程安全

在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。如同一内存区(变量,数组,或对象)、系统(数据库,web services等)或文件。实际上,这些问题只有在一或多个线程向这些资源做了写操作时才有可能发生,只要资源没有发生变化,多个线程读取相同的资源就是安全的。

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。

如果一个资源的创建,使用,销毁都在同一个线程内完成,且永远不会脱离该线程的控制,则该资源的使用就是线程安全的。

四、java同步块

  1. 实例方法
  2. 静态方法
  3. 实例方法中的同步块
  4. 静态方法中的同步块

实例方法同步:

public synchronized void add(int value){ this.count += value; }

Java实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。只有一个线程能够在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。一个实例一个线程。

静态方法同步:

public static synchronized void add(int value){ count += value; }

静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。

实例方法中的同步块:

public void add(int value){ synchronized(this){ this.count += value; } }

复制代码

复制代码

public class MyClass {

public synchronized void log1(String msg1,String msg2){
log.writeln(msg1);
log.writeln(msg2);
}

public void log2(String msg1,String msg2){
synchronized(this){
log.writeln(msg1);
log.writeln(msg2);
}
}
}


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">

静态方法中的同步块:

复制代码

复制代码

public class MyClass {
    public static synchronized void log1(String msg1,String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }
public static void log2(String msg1,String msg2){
   synchronized(MyClass.class){
      log.writeln(msg1);
      log.writeln(msg2);
   }
}

}


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">

五、java线程通信

线程通信的目标是使线程间能够互相发送信号。另一方面,线程通信使线程能够等待其他线程的信号。

Java有一个内建的等待机制来允许线程在等待信号的时候变为非运行状态。java.lang.Object 类定义了三个方法,wait()、notify()和notifyAll()来实现这个等待机制。

一个线程一旦调用了任意对象的wait()方法,就会变为非运行状态,直到另一个线程调用了同一个对象的notify()方法。为了调用wait()或者notify(),线程必须先获得那个对象的锁。也就是说,线程必须在同步块里调用wait()或者notify()。

以下为一个使用了wait()和notify()实现的线程间通信的共享对象:

复制代码

复制代码

public class MyWaitNotify{

MonitorObject myMonitorObject = new MonitorObject();
boolean wasSignalled = false;

public void doWait(){
synchronized(myMonitorObject){
while(!wasSignalled){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
//clear signal and continue running.
wasSignalled = false;
}
}

public void doNotify(){
synchronized(myMonitorObject){
wasSignalled = true;
myMonitorObject.notify();
}
}
}


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">

注意以下几点:

1、不管是等待线程还是唤醒线程都在同步块里调用wait()和notify()。这是强制性的!一个线程如果没有持有对象锁,将不能调用wait(),notify()或者notifyAll()。否则,会抛出IllegalMonitorStateException异常。

2、一旦线程调用了wait()方法,它就释放了所持有的监视器对象上的锁。这将允许其他线程也可以调用wait()或者notify()。

3、为了避免丢失信号,必须把它们保存在信号类里。如上面的wasSignalled变量。

4、假唤醒:由于莫名其妙的原因,线程有可能在没有调用过notify()和notifyAll()的情况下醒来。这就是所谓的假唤醒(spurious wakeups)。为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。这样的一个while循环叫做自旋锁。

5、不要在字符串常量或全局对象中调用wait()。即上面MonitorObject不能是字符串常量或是全局对象。每一个MyWaitNotify的实例都拥有一个属于自己的监视器对象,而不是在空字符串上调用wait()/notify()。

六、java中的锁

自Java 5开始,java.util.concurrent.locks包中包含了一些锁的实现,因此你不用去实现自己的锁了。

常用的一些锁:

java.util.concurrent.locks.Lock;java.util.concurrent.locks.ReentrantLock;java.util.concurrent.locks.ReadWriteLock;java.util.concurrent.locks.ReentrantReadWriteLock;

一个可重入锁(reentrant lock)的简单实现:

复制代码

复制代码

public class Lock {
    boolean isLocked = false;
    Thread  lockedBy = null;
    int lockedCount = 0;
public synchronized void lock() throws InterruptedException{
    Thread callingThread = Thread.currentThread();
    while(isLocked &amp;&amp; lockedBy != callingThread){
        wait();
    }
    isLocked = true;
    lockedCount++;
    lockedBy = callingThread;
}

public synchronized void unlock(){
    if(Thread.currentThread() == this.lockedBy){
        lockedCount--;
        if(lockedCount == 0){
            isLocked = false;
            notify();
        }
    }
}

}


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">

注意的一点:在finally语句中调用unlock()

复制代码

lock.lock();
try{
    //do critical section code,which may throw exception
} finally {
    lock.unlock();
}
复制代码

七、java中其他同步方法

信号量(Semaphore):java.util.concurrent.Semaphore

阻塞队列(Blocking Queue):java.util.concurrent.BlockingQueue

复制代码

复制代码

public class BlockingQueue {
    private List queue = new LinkedList();
    private int limit = 10;
public BlockingQueue(int limit) {
    this.limit = limit;
}

public synchronized void enqueue(Object item) throws InterruptedException {
    while (this.queue.size() == this.limit) {
        wait();
    }
    if (this.queue.size() == 0) {
        notifyAll();
    }
    this.queue.add(item);
}

public synchronized Object dequeue() throws InterruptedException {
    while (this.queue.size() == 0) {
        wait();
    }
    if (this.queue.size() == this.limit) {
        notifyAll();
    }
    return this.queue.remove(0);
}

}


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">

八、java中的线程池

Java通过Executors提供四种线程池,分别为:

newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

newFixedThreadPool 

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newScheduledThreadPool 

创建一个大小无限制的线程池。此线程池支持定时以及周期性执行任务。

newSingleThreadExecutor

创建一个单线程的线程池。此线程池支持定时以及周期性执行任务。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

线程池简单用法:

复制代码

复制代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(index);
}
});
}
}
}


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">


<div class="cnblogs_code_toolbar"><span class="cnblogs_code_copy"><a title="复制代码">

<img src="https://www.jb51.cc/res/2019/02-10/23/51e409b11aa51c150090697429a953ed.gif" alt="复制代码">

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 连接 连接池产生原因 连接池实现原理 小结 TEMPERANCE:Eat not to dullness;drink not to elevation.节制
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 一个优秀的工程师和一个普通的工程师的区别,不是满天飞的架构图,他的功底体现在所写的每一行代码上。-- 毕玄 1. 命名风格 【书摘】类名用 UpperCamelC
今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。一、接口和抽象类类,即一个对象。先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 —包含抽象方法的类叫做抽象类。接口
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket一、引子文件,作为常见的数据源。关于操作文件的字节流就是 —FileInputStream&amp;FileOutputStream。
作者:泥沙砖瓦浆木匠网站:http://blog.csdn.net/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_Aonqz
本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程与多线程 线程是什么? 线程(Thread)是一个对象(Object)。用来干什么?Java 线程(也称 JVM 线程)是 Java 进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。 Ja
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket在面向对象编程中,编程人员应该在意“资源”。比如?1String hello = &quot;hello&quot;; 在代码中,我们
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 《程序兵法:Java String 源码的排序算法(一)》 文章工程:* JDK 1.8* 工程名:algorithm-core-le
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 一、父子类变量名相同会咋样? 有个小故事,今天群里面有个人问下面如图输出什么? 我回答:60。但这是错的,答案结果是 40 。我知错能改,然后说了下父子类变
作者:泥瓦匠 出处:https://www.bysocket.com/2021-10-26/mac-create-files-from-the-root-directory.html Mac 操作系统挺适合开发者进行写代码,最近碰到了一个问题,问题是如何在 macOS 根目录创建文件夹。不同的 ma
作者:李强强上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算。这一讲,泥瓦匠带你走进Java中的进制详解。一、引子在Java世界里,99%的工作都是处理这高层。那么二进制,字节码这些会在哪里用到呢?自问自答:在跨平台的时候,就凸显神功了。比如说文件读写,数据通信,还
1 线程中断 1.1 什么是线程中断? 线程中断是线程的标志位属性。而不是真正终止线程,和线程的状态无关。线程中断过程表示一个运行中的线程,通过其他线程调用了该线程的 方法,使得该线程中断标志位属性改变。 深入思考下,线程中断不是去中断了线程,恰恰是用来通知该线程应该被中断了。具体是一个标志位属性,
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want需求 项目在设计表的时候,要处理并发多的一些数据,类似订单号不能重复,要保持唯一。原本以为来个时间戳,精确到毫秒应该不错了。后来觉得是错了,测试环境下很多一
纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 加微信:bysocket01
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want.文章Points:1、介绍RESTful架构风格2、Spring配置CXF3、三层初设计,实现WebService接口层4、撰写HTTPClient 客户
Writer :BYSocket(泥沙砖瓦浆木匠)什么是回调?今天傻傻地截了张图问了下,然后被陈大牛回答道“就一个回调…”。此时千万个草泥马飞奔而过(逃哈哈,看着源码,享受着这种回调在代码上的作用,真是美哉。不妨总结总结。一、什么是回调回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百
Writer :BYSocket(泥沙砖瓦浆木匠)一、什么大小端?大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加
What is a programming language? Before introducing compilation and decompilation, let&#39;s briefly introduce the Programming Language. Programming la
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket泥瓦匠喜欢Java,文章总是扯扯Java。 I/O 基础,就是二进制,也就是Bit。一、Bit与二进制什么是Bit(位)呢?位是CPU
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocket一、前言 泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标:专注很重要。专注Java 基础 + H5(学习) 其他操作系统,算法,数据结构当成课外书博览。有时候,就是那样你越是专注方面越