文章目录

  • 一.多线程的概念
    • 1.并发与并行
    • 2.多线程的概念
    • 3.什么时候需要多线程
  • 二.线程同步
    • 1.synchronized关键字
    • 2.Lock接口
    • 3.synchronized与Lock区别
    • 4.线程死锁
  • 三.线程通讯
  • 四.新增创建线程的方式

一.多线程的概念

多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务.

1.并发与并行

       我们先来理解"同时",就比如我们在使用计算机的时候,一边听歌,一边打游戏.看似是"同时".但是在操作系统底层不一定是"同时".实际上,对于单核CPU的计算机来说,在CPU中,同一时间是只能干一件事的.为了看起来像是同时干多件事.Windows这种操作系统是把CPU的时间划分为长短基本相同的时间区间,即"时间片".通过操作系统的管理,把这些时间片一次轮流的分配给各个应用使用.这样,给用户的感觉是他在同时听歌和打游戏,实际上,在操作系统中,CPU是在游戏进程和音乐播放器进程之间来回切换的.操作系统时间片的使用是有规则的:某个作业在时间片结束之前,整个任务还没有完成,那么该作业就被暂停下来,放弃CPU,等待下一轮循环再做.此时CPU又分配给另一个作业去使用.

由于计算机的处理速度很快,只要时间片的间隔取得适当,那么一个作业从用完这个时间片到获得下一个时间片,这个中间所有的停顿,用户是察觉不出来的.

所以,在单CPU的计算机中,我们看起来"同时干多件事情",其实是通过时间片技术,并发完成的.

那我们现在再来理解并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行.

那么上面的就可以这么解释了:打游戏和听音乐在同一个时间段内都是在同一台电脑上完成了从开始在结束的动作.那么就可以说听音乐和打游戏是并发的.

    我们两个人一起去吃午饭.在吃饭的过程中.你吃了米饭,土豆和鸡肉.相同的,我也和你点了一样的饭.那么我们两个人之间的吃饭就是并行的.两个人之间可以在同一个时间点吃土豆.当然也可以一个吃土豆,另一个吃牛肉.之间是互不影响的.

这个时候我们再来解释什么是并行:

当系统有一个以上的CPU时,当一个CPU执行一个进程,另一个CPU可以执行另一个进程.两个进程不抢占CPU资源.可以同时进行,这种方式我们称之为并行.

*这里面有一个很重要的一个点,那就是系统要有多个CPU才会出现并行.*在有多个CPU的情况下,才会出现真正意义上的同时进行.

所以:

并发就是指一段时间内宏观上多个程序同时运行.

并行指的是同一个时刻,多个任务真的在同时运行.

//并发,指的是多个事情在同一时间段内同时发生了.
//并行,指的是多个事情在同一个时间点上同时发生了.//并发的多个任务之间是互相抢占资源的
//并行的多个任务之间是不会互相抢占资源的//只有在多CPU的情况中,才会发生并行.否则,看似同时发生的事情,其实都是并发执行的.

2.多线程的概念

多线程是指程序中包含多个执行单元,即在一个程序可以同时运行多个不同的线程来执行不同的任务.也就是说允许单个程序创建多个并行执行的线程来完成各自的任务.

当然,多线程执行的方式并不是我们能决定的.有可能是并行,也有可能是并发.

3.什么时候需要多线程

程序需要同时执行两个或者多个任务.

程序需要实现一些等待的等待的任务时,如用户的输入,文件读写操作,网络操作,搜索等.

多线程的优点1.提高程序的响应.2.提高CPU的利用率.3.改善程序结构,将复杂任务分为多个线程,独立运行.
多线程的缺点1.线程也需要加载到CPU中执行,所以线程需要占用资源,程程越多占用的内存也越多.2.多线程也要协调和管理,所以需要CPU时间跟踪线程.3.线程之间对共享资源的访问会互相影响.

二.线程同步

多线程同步:多个线程同时访问同一个资源时,可能会引起冲突.因此引入线程同步机制.即各个线程间要有先来后到.

同步就是排队+锁.

确保一个时间只有一个线程访问共享资源.可以给共享资源加一把锁,哪个线程获取了这把锁,才有权利访问该共享资源.

我们用模拟卖票来实现这个场景:

public class Shou extends Thread{int num = 10;@Overridepublic void run() {while (true) {if (num > 0) {        System.out.println(Thread.currentThread().getName() + "还有" + num);num--;} else {System.out.println(Thread.currentThread().getName() + "票没了");break;}}}
}
public class Test1 {public static void main(String[] args) {Shou shou = new Shou();//这个线程表示12306卖票Thread thread = new Thread(shou);thread.setName("12306");//这个线程表示汉中站卖票Thread thread1 = new Thread(shou);thread1.setName("汉中站");thread.start();thread1.start();}}

通过上面的代码我们来分析,我们只创建了一个Shou对象,但是却有两个线程在运行(两个窗口在卖票).因此就会出现线程安全的问题.也就是说可能两个窗口同时卖票,有可能卖的是同一张票,也有可能两个加起来卖了11张票…

1.synchronized关键字

我们需要想办法来处理它.首先,我们用synchronized关键字来实现给线程加锁.

我们先来看在Shou类利用synchronized关键字给代码块加锁:

public class Shou extends Thread{int num = 10;static Object object = new Object();@Overridepublic void run() {while (true) {/*synchronized修饰的代码块称为形式代码块括号里的内容是唯一的一个对象*/synchronized (object) {if (num > 0) {System.out.println(Thread.currentThread().getName() + "还有" + num);num--;} else {System.out.println(Thread.currentThread().getName() + "票没了");break;}}}}
}

这样我们就可以解决线程安全问题.

我们再来看利用synchronized关键字给方法加锁.

public class Shou extends Thread{int num = 10;@Overridepublic void run() {while (true) {if (num <= 0) {System.out.println(Thread.currentThread().getName() + "票没了");break;} else {print();}}}public  synchronized void print(){if (num > 0) {System.out.println(Thread.currentThread().getName() + "还有" + num);num--;}}
}

那么这种方法也可以解决上面的问题.

来我们总结一下:有关synchronized的有关概念及注意事项

1.synchronized修饰的代码块称为同步代码块.

2.synchronized关键字修饰代码块时,后面()里的内容是任何的唯一的一个对象.叫做**锁对象,也叫同步对象.**此对象用来记录有没有线程进入到这个代码块.

同步代码块执行过程

(1.第一个线程访问,锁定同步对象,执行其中代码.

(2.第二个线程访问,发现同步对象被锁定,无法访问.

(3.第一个线程访问完毕,解锁同步对象.

(4.第二个线程访问,发现同步对象没有锁,然后锁定并访问.

当然到底哪个线程进去锁定代码块,这个线程出来解锁代码块,哪个线程又进去不是我们能够决定的.

3.synchronized关键字修饰非静态方法时,**锁对象是this.**也就是当前线程所对应的对象.

4.synchronized关键字修饰静态方法时,锁对象是**当前类的class对象.**每一个类加载到内存中时,系统就会为此类创建一个对应的class对象.用来获取对象的信息.一个类对应一个class类的对象.

上面我们介绍的就是对于第一种创建线程的方式.也就是继承Thread类实现多线程.但是我们还是考虑不够全面.来看看遗漏了那个部分.

首先第一个我们应该要注意的,我们只新建了一个对象,因此并没有使用到static关键字来使票数是共享的.那么在实际应用中我们势必会碰到使用到static关键字修饰的方法和变量的线程.那么这个时候我们如何使用synchronized关键字来解决线程安全的问题呢.

public class Test1 {/*因为我们实现线程的方法是通过继承来实现的因此当我们新建两个线程的时候有可能会new两个我们自建的线程类这个时候我们就需要自建类中的变量设置为静态变量*/public static void main(String[] args) {//这个线程表示12306卖票Shou thread = new Shou();thread.setName("12306");//这个线程表示汉中站卖票Shou thread1 = new Shou();thread1.setName("汉中站");thread.start();thread1.start();}}
public class Shou extends Thread{static int num = 10;@Overridepublic void run() {while (true) {if (num <= 0) {System.out.println(Thread.currentThread().getName() + "票没了");break;} else {print();}}}public  static synchronized void print(){if (num > 0) {System.out.println(Thread.currentThread().getName() + "还有" + num);num--;}}
}

那这个就是有关synchronized关键字对于我们使用继承来实现多线程造成的线程安全问题进行处理的有关注意事项以及实现方法.

当然在前面我们还说过,不仅可以通过使用继承来实现多线程,当然也可以通过实现Runnable接口来实现多线程.那么接下来我们看看使用实现接口这种方法如何解决线程安全问题.

public class Test1 {public static void main(String[] args) {//实现接口的话,并不需要再new出来多个类对象Shou shou = new Shou();Thread thread = new Thread(shou,"汉中站");Thread thread1 = new Thread(shou,"12306");thread.start();thread1.start();}
}
public class Shou implements Runnable{//因为是实现的接口,只需要new出来一个Shou对象,因此不需要添加static修饰,本身就是共享变量int num = 10;@Overridepublic void run() {while (true) {synchronized (this) {if (num > 0) {System.out.println(Thread.currentThread().getName() + "还有" + num);num--;} else {System.out.println(Thread.currentThread().getName() + "票没了");break;}}}}
}

当然我们既然只需要new出来一个Shou对象,因此不需要使用static来修饰我们的属性,所以我们的方法同样也不需要修饰,因此我们并不需要像继承哪些还需要注意synchronized修饰方法所带来的问题.

2.Lock接口

一个线程持有锁会导致其他所有需要此锁的线程挂起,在多线程竞争下加锁,释放锁会导致比较多的上下文切换和调度延时.

从jdk5.0开始,java提供了更强大的线程同步机制–**通过显式定义同步锁对象来实现同步.**同步锁使用lock对象充当.

java.util.concurrent.locks.Lock接口是**控制多个线程对共享资源进行访问的工具.**锁提供了对共享资源的独占访问,每次只能有一个线程对lock对象加锁,线程开始访问共享资源之前应该先获得lock对象.

因为lock是接口,因此需要具体的实现类.**ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义.**在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式的加锁,释放锁.

使用这个的话,加锁,释放锁就变得异常简单.我们来看一个实例.

public class Test1 {public static void main(String[] args) {Shou shou = new Shou();Thread thread = new Thread(shou,"汉中站");Thread thread1 = new Thread(shou,"12306");thread.start();thread1.start();}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Shou implements Runnable{int num = 10;Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try {lock.lock();if (num > 0) {System.out.println(Thread.currentThread().getName() + "还有" + num);num--;} else {System.out.println(Thread.currentThread().getName() + "票没了");break;}} finally {lock.unlock();}}}
}

当然这里我们依然有需要值得注意的.我们使用的是lock的实现类中的方法来实现加锁释放锁的.这会造成一个问题–无法释放锁.在上面我们使用的是synchronized关键字,使用关键字进行修饰的话,我们并不需要担心释放锁的问题.不论是出现异常还是线程结束都会自发的释放锁.但是使用lock()方法进行加锁的话.很有可能当我们加上锁之后,出现异常这条线程不能释放锁,造成其他线程也进不来的情况.因此我们需要添加try/catch语句来释放锁,以免这种情况的发生.

3.synchronized与Lock区别

存在层面:
synchronized是一个关键字,存在jvm层面上
Lock:是java中的一个接口

锁的释放:
synchronized:
1.获取锁的线程完成了同步代码块
2.线程执行发生异常,jvm会让线程释放锁
Lock:
在finally代码块中释放锁,以免造成线程死锁

锁的类型:
synchronized:可重入 不可判断 非公平
Lock:可重入 可判断 可公平也可不公平

底层实现:
synchronized:底层使用指令码方式来控制锁.
Lock:在语言层面.使用的是CAS乐观锁.

4.线程死锁

什么是线程死锁?当**不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己的同步资源.**就形成了线程的死锁.

出现死锁后,并不会出现异常,而且也不会出现提示,只是所有的线程都处于阻塞状态,无法继续.

就好比中国人和美国人吃饭.正常情况,中国人两个筷子,美国人使用刀和叉.线程死锁就好比与中国人拿了一只筷子还有一个刀,美国人拿了一只筷子还有一个叉子.这个时候两个人都没有办法吃饭.

在我们设计的时候应该考虑好锁的顺序,尽量减少嵌套的加锁交互数量.

public class MyThread extends Thread{//这是两把锁static Object object = new Object();static Object object1 = new Object();boolean flag;public MyThread(boolean flag) {this.flag = flag;}@Overridepublic void run() {while(true){if (flag){synchronized(object){System.out.println("if object");}synchronized (object1){System.out.println("if object1");}}else {synchronized(object1){System.out.println("else object1");}synchronized (object){System.out.println("else object");}}}}
}
public class Test {public static void main(String[] args) {new MyThread(true).start();new MyThread(false).start();}}

像这种情况里面有两把锁的嵌套.有可能第一个线程进去拿到第一把锁(进入的是if语句),此时第二个线程进来拿走了第二把锁(进入else)语句.这就导致两个线程卡在那里,谁也无法继续向下进行,因此就造成了线程死锁.

当然我们在实际编写代码的时候碰到的这种情况可能不是很常见,但是无论无何我们也应该注意一下,避免出现这种情况的发生.

三.线程通讯

这个通讯指的并不是两个线程打电话怎么怎么样.线程通讯指的是多个线程通过共享的数据变量相互牵制,相互调度.也就是线程之间的相互作用.

这里我们要涉及到Object类中三个方法.

wait:一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器.

notify:一旦执行此方法,就会唤醒被wait的一个线程.如果有多个线程被wait,就唤醒优先级高的那个.

notifyAll:一旦执行此方法,就会唤醒所有被wait的线程.

public class Test {public static void main(String[] args) {PrintNum printNum = new PrintNum();Thread thread = new Thread(printNum);Thread thread1 = new Thread(printNum);thread.start();thread1.start();}
}
public class PrintNum implements Runnable{int num = 0;@Overridepublic void run() {while(true){//我们只new出来一个对象,因此这个对象就可以用作锁对象synchronized(this){this.notify();//唤醒另一个线程,使用锁对象调用System.out.println(Thread.currentThread().getName()+":"+num++);if (num > 100){break;}try {//也是使用锁对象调用,使线程进入wait状态,同时释放锁this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}
}

wait,notify,notifyAll这三个方法必须使用在同步代码块或同步方法中.为什么要这么做呢?主要还是避免唤醒另一个线程的时候,对共享变量访问.造成线程安全问题.

wait与sleep方法的区别:

wait方法必须在同步代码块中出现.使用wait方法之后,锁会自动释放.

sleep方法并没有强制要求在同步代码块中,

我们再来看一个例题:
生产者将产品放在柜台,而消费者从柜台处取走产品,生产者一次只能生产含固定数量的产品,这个时候柜台不能再放产品.当生产者生产好了商品就可以唤醒消费者,等待消费者拿走产品.当消费者将产品取完后,又唤醒生产者生产,此时消费者开始等待.

public class Test {public static void main(String[] args) {//只有一个柜台,因此只需要创建一个类Counter counter = new Counter();new Customer(counter).start();new Productor(counter).start();}
}
public class Counter {//商品数量,成员变量不赋值默认为0//因为只创建一个柜台对象,因此也没有必要添加synchronized关键字修饰int num;//我们只new出来一个对象,因此我们可以使用synchronized关键字修饰非静态方法public synchronized void inPut(){//如果没有商品进行生产if (num == 0){num = 1;//生产完之后唤醒消费者进行消费System.out.println("生产者生产了一件产品");this.notify();}else{try {//如果还有产品,那么不需要继续生产,生产者进入等待状态this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}public synchronized void outPut(){if (num == 1){num = 0;System.out.println("消费者拿走一件产品");this.notify();}else{try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class Customer extends Thread{Counter counter;public Customer(Counter counter) {this.counter = counter;}@Overridepublic void run() {while(true){counter.outPut();}}
}
public class Productor extends Thread{Counter counter;public Productor(Counter counter) {this.counter = counter;}@Overridepublic void run() {while(true){counter.inPut();}}
}

四.新增创建线程的方式

在我们之前提到的两种新建线程的方式,虽然方法简单,但是依然存在一些不足之处.

我们上面两种方法都是重写run()方法.并没有返回值.我们来看一种新的新建线程的方法.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class Test {public static void main(String[] args) {Demo demo = new Demo();//这个类可以接受我们实现Callable接口的线程FutureTask<Integer>  futureTask = new FutureTask(demo);//Theard类不能接收我们实现Callable接口的线程,需要上面转换一下Thread thread = new Thread(futureTask);thread.start();try {//futureTask.get();方法返回值就是我们泛型的类型System.out.println(futureTask.get());} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}
}
import java.util.concurrent.Callable;public class Demo implements Callable<Integer> {//这个线程可以有返回值,并且支持泛型@Overridepublic Integer call() throws Exception {int i = 0;for (int j = 0; j < 100; j++) {i += j;}return  i;}
}

实现Callable接口与实现Runnable相比,前者功能更强大些.

1.相比于run方法,可以有返回值

2.方法可以抛出异常

3.支持泛型的返回值

4.需要一个接收类FutureTask类,获取返回结果.

多线程概念以及线程同步相关推荐

  1. 多线程:C#线程同步lock,Monitor,Mutex,同步事件和等待句柄(上)

    多线程:C#线程同步lock,Monitor,Mutex,同步事件和等待句柄(上) 转自 http://www.cnblogs.com/freshman0216/archive/2008/07/27/ ...

  2. Android SQLite多线程读写和线程同步源码分析

    没啥诀窍,只需保证几个线程都是用的一个SQLiteDataBase对象就行了. 如果我们非要在不同线程中用两个或更多的SQLiteDataBase对象呢,当然这些SQLiteDataBase对象所操作 ...

  3. 【记录】python多线程的使用 线程同步(LOCK和RLOCK) python与mysql数据库交互实现增加和查找 python的格式化输出

    文章目录 多线程: _thread模块 threading模块: 线程同步(LOCK和RLOCK) 和mysql交互: 附:python的格式化输出 附录 多线程: _thread模块 例一 impo ...

  4. Win32多线程编程(3) — 线程同步与通信

    一.线程间数据通信 系统从进程的地址空间中分配内存给线程栈使用.新线程与创建它的线程在相同的进程上下文中运行.因此,新线程可以访问进程内核对象的所有句柄.进程中的所有内存以及同一个进程中其他所有线程的 ...

  5. 细说C#多线程那些事 - 线程同步和多线程优先级

    上个文章分享了一些多线程的一些基础的知识,今天我们继续学习. 一.Task类 上次我们说了线程池,线程池的QueueUserWorkItem()方法发起一次异步的线程执行很简单 但是该方法最大的问题是 ...

  6. 多线程怎么保证数据安全_Python threading实现多线程 提高篇 线程同步,以及各种锁...

    本文主要讲多线程的线程之间的资源共享怎么保持同步. 多线程基础篇见,木头人:Python threading实现多线程 基础篇 Python的多线程,只有用于I/O密集型程序时效率才会有明显的提高,如 ...

  7. 多线程编程、线程同步|安全和线程通信

    多线程编程 多线程的优势 线程在程序中是独立的.并发的执行流,与分隔的进程相比,进程中的线程之间的隔离程度要小.他们共享内存.文件句柄和其他每个进程应有的状态. 因为线程的划分尺度小于进程,使得多线程 ...

  8. VC++中多线程学习(MFC多线程)三(线程同步包含:原子互锁、关键代码段、互斥器Mutex、Semaphores(信号量)、Event Objects(事件))

    目录 ​​​​​​​​​​​​ 线程同步的必要性: 2.解决同步问题的方法 2.1原子互锁家族函数 2.2Critical Sections(关键代码段.关键区域.临界区域) 2.3 互斥器Mutex ...

  9. 多线程概念,线程控制

    文章目录 多线程 线程概念 多任务处理: 多线程/多进程进行多任务处理的优缺点分析 多线程的优点 多进程的优点 共同的优点 线程控制 线程的创建 线程的终止 线程的等待 线程的分离 多线程 线程概念 ...

  10. iOS多线程编程:线程同步总结 NSCondtion

    1:原子操作 - OSAtomic系列函数 iOS平台下的原子操作函数都以OSAtomic开头,使用时需要包含头文件<libkern/OSBase.h>.不同线程如果通过原子操作函数对同一 ...

最新文章

  1. HDFS HA与QJM(Quorum Journal Manager)介绍及官网内容整理
  2. AD转换中参考电压的作用 .
  3. DX中材质不能正确显示的问题(要么黑色,要么白色)
  4. 计算机网络TCPP是一组什么,WWW的全称是什么?WWW中文名称是啥?
  5. Shell脚本中替换字符串等操作
  6. idea提交本地项目到git
  7. 华为手机微信小程序上传不了照片
  8. 把小写金额转成大写金额 (Java经典编程案例)
  9. 利用apktool查看apk源代码
  10. 学习django教程一
  11. iOS自动打包,并上传蒲公英
  12. 防泄密-应用软件研发行业源代码防泄密及技术文档防泄密解决方案
  13. MySQL 表的建立与多表联结查询
  14. Camera基本概念
  15. loss for bounding box
  16. dtls到srtp的整个流程
  17. SSOP封装和TSSOP封装能否兼容?
  18. 计算机简史这门课,现在补,并不晚
  19. 会声会影X6-高级运动等效果的练习实践-与您分享...
  20. C# IEnumerator枚举器

热门文章

  1. 输入一个大写或者小写,输出其相反的大小写。(c语言)
  2. 如何使用FLASHGOT下载网页FLASH
  3. Mysql从入门到入魔——6. 表联结、组合查询
  4. 四元数在旋转的运用-圆形烟火弹道轨迹
  5. ARM CORTEX M3
  6. Mac和iphone利用自带邮件客户端添加263企业邮箱
  7. python 打印下标和值
  8. Linux三个网络监视器之《三》——vnstat
  9. 人体一机竞技格斗机器人_在格斗机器人比赛中,如何判断输赢?
  10. 你未必知道的 WebRTC:前世、今生、未来