1、线程与进程

进程:

  • 进程是程序运行以及资源分配的基本单位,一个程序至少有一个进程。
  • 如下图所示:

线程:

  • 线程是CPU调度和分配的基本单位,一个进程至少有一个线程。
  • 同一个进程中的线程共享进程资源(减少切换,可提高效率),且可以并发执行。

2、并发和并行

并发:

  • 指的是同一时间间隔执行多个事件,强调的是一段时间内可以做多个不同的事。
  • 比如在一分钟内,先吃一口饭,再喝一口水,接着说一句话等等。

并行:

  • 指的是同一时刻执行多个事件,强调的是同一时刻可以做多个不同的事。
  • 比如在吃饭的同时,一边听歌,看电视。

3、线程的上下文切换

基本原理:

  • 一个CPU在任意时刻只能执行一个线程,如果有多个线程(超过CPU个数)存在,CPU将会采用时间片轮转的方式进行线程切换,即给每一个线程分配一个时间片,当一个线程的时间片用完的时候便会处于就绪状态,并让出CPU给其他线程使用,这就是一次上下文切换。
  • 上下文切换时是通过运行时数据区中的程序计数器来存储各个线程的运行状态的,以便于下次运行线程时可以接着上次指令继续运行(比如线程A做了一次计算准备返回数据时,切换到了线程B,然后又切回线程A,是直接返回数据,而不是再去计算一遍)。
  • 程序计数器指的是JVM中的一块内存区域,它可以看作是当前线程所执行字节码的行号指示器,通过它,Java可以知道每个线程执行到了哪一步指令(由此可以看出程序计数器是线程私有的)。
  • 一般而言,上下文切换是比较耗费CPU时间的一种操作。

4、线程的几种状态

基本原理:

  • 创建:指的是生成线程对象,此时并没有调用start方法。
  • 就绪:调用线程的start方法后,线程便进入了就绪状态,此时等待系统调度。
  • 运行:通过系统调度,开始运行线程中的run函数。
  • 等待:调用了Object.wait()会进入等待队列。一般需要等待其他线程做出一些特定的通知或者中断。
  • 超时等待:该状态与等待状态有一点区别就是它会自动返回,比如调用Threa.sleep(long),在超时之后,会自动返回进入就绪状态。
  • 阻塞:指的是去获取锁时(等待进入synchronized方法或块),发现同步锁被占用了,这时线程会被放入锁池,等待获取锁。
  • 死亡:运行完run方法,main方法(main方法指的是主线程)之后正常退出,也有可能出现异常导致死亡;死亡的线程不能重新启用,否则报错。

5、创建线程的几种方式

继承Thread类:

  • 创建一个类继承Thread,重写其中的run方法,即线程执行体。
  • 创建继承了Thread类的子类实例,即线程对象
  • 调用线程对象的start()方法启动线程。
  • 示例代码如下;
public class ThreadTest {
public static void main(String[] args){
new TestThread().start();
}
} class TestThread extends Thread{
@Override
public void run(){
System.out.println("Test Thread");
}
}

实现Runnable接口:

  • 创建一个类实现Runnable接口,重写其中的run方法,即线程执行体。
  • 创建实现Runnable接口的类实例,接着将该实例对象作为target传入Thread类的构造器,此时创建的Thread类才是线程对象。
  • 调用线程对象的start()方法启动线程。
  • 示例代码如下;
public class ThreadTest {
public static void main(String[] args){
new Thread(new TestRunnable()).start();
}
} class TestRunnable implements Runnable{
@Override
public void run() {
System.out.println("Test Runnable");
}
}

实现Callable类:

  • 创建一个类实现Callable接口,重写其中的call方法,即线程执行体,call方法存在返回值。
  • 创建实现Callable接口的类实例,将该实例对象作为callable传入FutureTask类的构造器,此FutureTask对象封装了该Callable对象的call()方法的返回值,可使用get方法获取。
  • 将FutureTask实例对象作为target传入Thread的构造器,此时创建的Thread类才是线程对象。
  • 调用线程对象的start()方法启动线程。
  • 示例代码如下;
public class ThreadTest {
public static void main(String[] args){
FutureTask<Integer> task = new FutureTask(new TestCallable());
new Thread(task).start();
try {
System.out.println(task.get());//get方法会得到call执行完之后的值
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} class TestCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int num = 0;
while(num < 5){
num++;
}
return num;
}
}

6、sleep() 和 wait()

sleep():

  • sleep()方法是线程类Thread类的一个静态方法,如若是在synchronized中调用,sleep()方法不会释放锁。

wait():

  • wait()方法是线程类Object类的一个方法,如若是在synchronized中调用,wait()方法会释放锁。
  • 示例代码如下(从控制台输出的时间戳结果可以看出sleep没有释放锁,是阻塞进行的,而wait释放了锁):
public class ThreadTest {
protected static volatile Object lock = "lock"; public static void main(String[] args){ for(int i=0;i<5;i++){
new Thread(() -> {
synchronized(lock){
System.out.println(Thread.currentThread().getName() + ":等待开始当前时间戳:" + System.currentTimeMillis());
try {
// lock.wait(1000);//让当前线程进入等待池
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}

7、关于wait() 与 notify(),notifyAll()

notify(),notifyAll():

  • notify()指的是随机唤醒一个wait线程进入对象锁池中竞争锁,而notifyAll()是唤醒所有的wait线程进入对象锁池中竞争锁。
  • 关于wait()与notify()方法示例代码如下:
public class ThreadTest {
protected static volatile Object lock = "lock"; public static void main(String[] args){
new Thread(new TestRunnable(lock)).start();
try {
Thread.sleep(1000);
System.out.println("sleep 1000ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
new TestThread(lock).start(); }
} class TestRunnable implements Runnable{
private Object lock;
public TestRunnable(Object lock){
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock){
System.out.println("begin wait:" + System.currentTimeMillis());
System.out.println("Release lock........");
lock.wait();//让当前线程进入等待池
System.out.println("end wait:" + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} class TestThread extends Thread{
private Object lock;
public TestThread(Object lock){
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
System.out.println("begin notify:" + System.currentTimeMillis());
System.out.println("do something........");
lock.notify();//从等待池中随机唤醒一个wait线程进入锁池竞争锁
// lock.notifyAll();//从等待池中随机唤醒所有wait线程进入锁池竞争锁
System.out.println("end notify:" + System.currentTimeMillis());
}
}
}

8、start() 和 run()

start():

  • 此方法是用来启动一个线程的,执行了start()方法之后,线程将进入就绪状态,之后会自动执行run函数中的内容,无需等待,是真正意义上的实现了多线程。

run():

  • 单独运行此方法,只是将其当做一个普通函数在使用,每次执行必须等待run函数里面的内容完成,才能接着往下执行,无法实现多线程。

9、线程安全性

主要体现在下面三方面:

  • 原子性:同一时刻只允许一个线程对数据进行操作(atomic开头的原子类,synchronized,Lock)。
  • 可见性:一个线程对共享变量的修改,可以及时地被其他线程观察到,(synchronized,volatile,Lock)。
  • 有序性:指的是可以观察到其他线程的指令执行顺序,由于指令重排,一般情况下是无序的(happens-before原则)。

10、线程死锁

死锁:

  • 指的是多个线程在执行过程中,因争夺资源而陷入环路阻塞的一种现象。
  • 示例代码如下(运行之后,两个线程陷入死锁中,如果没有外力中断,将会一直锁定):
public class ThreadTest {
//共享资源A
private static Object A = new Object();
//共享资源B
private static Object B = new Object(); public static void main(String[] args){
new Thread(() -> {
synchronized (A){
System.out.println(Thread.currentThread().getName() + ":得到资源---A");
try {
Thread.sleep(1000);//此处休眠是为了让其他线程获得执行机会
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":去获取资源---B");
synchronized (B){
System.out.println(Thread.currentThread().getName() + ":得到资源---B");
}
}
},"Thread-01").start(); new Thread(() -> {
synchronized (B){
System.out.println(Thread.currentThread().getName() + ":得到资源---B");
try {
Thread.sleep(1000);//此处休眠是为了让其他线程获得执行机会
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":去获取资源---A");
synchronized (A){
System.out.println(Thread.currentThread().getName() + ":得到资源---A");
}
}
},"Thread-02").start();
}
}

死锁四个必要条件即如何避免死锁:

  • 互斥条件:一个资源在任意时刻只能被一个线程占用,如果有其他线程请求该资源,需要等待原线程释放资源(此条件不能被破坏,因为锁本身就是为了互斥访问)。
  • 请求和保持条件:一个线程已经获取了一部分资源,又去请求其他资源,如果其他资源正被占用,原线程陷入阻塞,且不会释放自己占用的资源(一次性申请所有资源;或者阻塞时,释放自己占用的资源)。
  • 不可剥夺条件:一个线程已经获得的资源,在自己未使用完之前不能被其他线程剥夺,只能自己使用结束后释放(如果去获取正在被其他线程使用的资源而阻塞时,可以释放自己占用的资源)。
  • 环路等待条件:多个进程之间形成一种头尾相接的循环等待资源关系(按一定的顺序来获取资源)。
  • 针对上述例子而言,可以让现线程1先获取资源AB,执行完之后,再让线程2获取资源AB,改动如下(执行顺序为:线程1先获取资源A,接着释放CPU,线程2执行准备去获取资源A,发现资源A已被占用,此时线程2阻塞,然后一秒之后释放CPU,线程1接着执行,去获取资源B,正常获取,执行完毕,释放CPU;线程2开始获取资源A,由于线程1已经执行完毕释放了锁,所以线程2正常获取资源A,接着休眠一秒释放CPU,然后又去获取资源B,同样正常获取,执行完毕):
public class ThreadTest {
//共享资源A
private static Object A = new Object();
//共享资源B
private static Object B = new Object(); public static void main(String[] args){
new Thread(() -> {
synchronized (A){
System.out.println(Thread.currentThread().getName() + ":得到资源---A");
try {
Thread.sleep(1000);//此处休眠是为了让其他线程获得执行机会
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":去获取资源---B");
synchronized (B){
System.out.println(Thread.currentThread().getName() + ":得到资源---B");
}
}
},"Thread-01").start(); new Thread(() -> {
//线程2去获取A时被阻塞
synchronized (A){
System.out.println(Thread.currentThread().getName() + ":得到资源---A");
try {
Thread.sleep(1000);//此处休眠是为了让其他线程获得执行机会
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":去获取资源---B");
synchronized (B){
System.out.println(Thread.currentThread().getName() + ":得到资源---B");
}
}
},"Thread-02").start();
}
}

(以上所有内容皆为个人笔记,如有错误之处还望指正。)

Java中的多线程基础的更多相关文章

  1. java中的多线程 // 基础

    java 中的多线程 简介 进程 : 指正在运行的程序,并具有一定的独立能力,即 当硬盘中的程序进入到内存中运行时,就变成了一个进程 线程 : 是进程中的一个执行单元,负责当前程序的执行.线程就是CP ...

  2. Java中的多线程=你只要看这一篇就够了

    如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...

  3. Java中的多线程技术全面详解

    本文主要从整体上介绍Java中的多线程技术,对于一些重要的基础概念会进行相对详细的介绍,若有叙述不清晰或是不正确的地方,希望大家指出,谢谢大家:) 为什么使用多线程 并发与并行 我们知道,在单核机器上 ...

  4. Java 中传统多线程

    目录 Java 中传统多线程 线程初识 线程的概念 实现线程 线程的生命周期 常用API 线程同步 多线程共享数据的问题 线程同步及实现机制 线程间通讯 线程间通讯模型 线程中通讯的实现 @(目录) ...

  5. 第87节:Java中的Bootstrap基础与SQL入门

    第87节:Java中的Bootstrap基础与SQL入门 前言复习 什么是JQ? : write less do more 写更少的代码,做更多的事 找出所有兄弟: $("div" ...

  6. Java中使用多线程、curl及代理IP模拟post提交和get访问

    Java中使用多线程.curl及代理IP模拟post提交和get访问 菜鸟,多线程好玩就写着玩,大神可以路过指教,小弟在这受教,谢谢! 更多分享请关注微信公众号:lvxing1788 ~~~~~~ 分 ...

  7. 【转】Java中的多线程学习大总结

    多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的. 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程 ...

  8. Java中的 多线程编程

    Java 中的多线程编程 一.多线程的优缺点 多线程的优点: 1)资源利用率更好2)程序设计在某些情况下更简单3)程序响应更快 多线程的代价: 1)设计更复杂虽然有一些多线程应用程序比单线程的应用程序 ...

  9. Android学习记录(5)—在java中学习多线程下载之断点续传②

    在上一节中我们学习了在java中学习多线程下载的基本原理和基本用法,我们并没有讲多线程的断点续传,那么这一节我们就接着上一节来讲断点续传,断点续传的重要性不言而喻,可以不用重复下载,也可以节省时间,实 ...

随机推荐

  1. 剑指offer学习--实现单例模式

    只能生成一个实例的类是为了实现单例模式的类型. 加同步锁前后两次判断实例是否已存在 我们只是在实例还没有创建之前加锁操作,以保证只有一个线程创建出实例.而当实例已经创建之后,我们已经不需要再做加锁操作 ...

  2. centos误删除文件如何恢复

    当意识到误删除文件后,切忌千万不要再频繁写入了,否则你的数据恢复的数量将会很少. 而我们要做的是,第一时间把服务器上的服务全部停掉,直接killall 进程名 或者 kill -9 pid . 然后把 ...

  3. 【转】 linux硬链接与软链接

    转自:http://www.cnblogs.com/yfanqiu/archive/2012/06/11/2545556.html Linux 系统中有软链接和硬链接两种特殊的“文件”. 软链接可以看 ...

  4. PHP: thinkPHP踩坑记录(实现API接口以及处理莫名其妙的500问题)

    因为各种原因开始学习PHP,并且要在两周内能够对PHP项目进行二次开发,还好PHP够简单,至少入门很简单,很快就接触thinkPHP框架. 在了解了路由匹配视图的规则之后,开始着手尝试编写API接口, ...

  5. 【串线篇】spring boot配置嵌入式servlet容器

    SpringBoot默认使用Tomcat作为嵌入式的Servlet容器 问题? 一.如何定制和修改Servlet容器的相关配置 1.方法1修改和server有关的配置(ServerProperties ...

  6. 如何使您的Wifi路由器更安全,网络安全专家告诉您!

    中国知名“黑客”教父,网络安全专家郭盛华曾说过,Wifi路由器这样设置最安全.因为无线路由器都有不同的接口,不同的设置方式以及可以调整的不同设置.在本文中,我将探讨TP-LinkArcher的界面.您 ...

  7. JDBC连接Hive数据库

    一.依赖 pom <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncodi ...

  8. 策略模式优化过多的IF ELSE

    前言: 当if else的条件少的话,代码可阅读性及逻辑不影响阅读和扩展.一旦if else过多的话会导致逻辑比较混乱,不易扩展并且很容易出错. 实现方案: 1.定义一个@HandlerType注解, ...

  9. java.nio.channels.IllegalBlockingModeException

    报错信息如下: Exception in thread "main" java.nio.channels.IllegalBlockingModeException at java. ...

  10. java实现js端的escape和unescape

    1.今天遇到这么个问题,需要把一些特殊字符传递到后台进行处理,例如Aa111111!@#,结果到了后台出现了个别字符中文符号了.这个时候需要转码.常见的就是js端的escape和unescape这种函 ...