Threading 1 |学习笔记

简介: 快速学习 Threading 1

开发者学堂课程【高校精品课-上海交通大学-企业级应用体系架构:Threading 1 】学习笔记,与课程紧密联系,让用户快速学习知识。

课程地址:https://developer.aliyun.com/learning/course/75/detail/15834


Threading 1

 

内容介绍

一、Processes and Threads

二、Thread Objects

、Defining and Starting a Thread

、Synchronization

 

一、Processes and Threads

事务隔离级别是四个数据库有很大的缓存左边有a用户右边有b用户进行访问操作的时候是在缓存里当能提交的时候才会写到硬盘上无论是a还是B在对个数据进行操作的时候数据都在服务器的缓存里面如果A能看到B正在处理的数据并且数据是没提交的会碰到脏问题如果在里面写数据比如转账从M账户里面减一加到N账户上转账的操作如果a在做B也在做M现在的值是0,按道理a做完一次b做完一次里面应该变成2,因为俩同时在做读走的M都等于0,做加1操作的时候B会变成1,如果A能看到B现在正在做0变成一没有做隔离直接能看到1,做加一操作的变成2,B操作放弃掉回滚在账户上做一次操作但是最终写回的值变成二解决问题加一次隔离什么都不读什么都不可以看到没有提交的东西加控制只能读到提交之后的数据A没有办法读到别人正在缓存处理数据于是只能读到数据库里的数据读走的只能是0,再写成1的时候往回写B在内存里面把改成1,a是读不到的所以看到仍然0,如果B被放弃仍然是基于a仍然是基于0进行操作只会写进1动作不会有问题一个隔离级别是缓存里不允许只能读在硬盘上提高数据但是现在防止的是B在缓存里操作 about操作本身不会反映到数据库上只会去读已经在数据库里写进去的情况如果B不设报警确实提交会变成a 读走 m 变成0,b读走M也是也是0,加一之后先写回在数据库里m+1,A再把数据写回数据库中比如做表比较防范性的编程写回去的时候读数据库里的数据是不是跟读的数据一样发现M等于一而不是0,基于一做加一变成二的动作再到数据库读一读看是不是才能写否则有问题在过程当中可能又有C用户读过问题在当读走数据时没有做任何的处理数据在数据库里没有做任何的处理没有加所以导致不可重复的问题所以数据读走以后在上面加如果a读走加个锁B和C能读但是不能写可以保证数据在写回之前是没有人可以进行改写可以重复读只要在事务执行期间不断的读数据读到的都是同一个不会发现被改写正的加锁别人只能读力度也可以更粗一点更强一点不能读事务提交也没关系不会产生影响但是还有另外一种情况a 进行 queny比如给员工涨工资所有人的工资只要小于1000块钱给每个人涨两百块钱读到数据库里面的五条记录对着五条记录锁死别人不可能修改五条加钱的动作没问题但是 C 用户里面插入新员工的工资也小于1000,这时会发现插入条件满足 queny 条件非常的麻烦确定一批人正在做操作时又有新的数据加入而且有可能新的数据满足 queny不但要把读走的所有小于1000的人的数据锁死要把整个表锁死只要处理员工的薪资整个表没有任何人能往里写可以防止有人会插入一条满足查询条件的记录整个是 server library 完全串进化等于a做完把表释放给 B 或者给 C尽管 B 和 C 不会操作 A 读走的数据但是操作的数据在同一张表里面也不让 B 和 C 做把整个表锁死第一种隔离级别是什么都没做在缓存里直接可以读到别人操作数据第二种隔离级别把给锁死不能读隔离只能读真正写入数据库里的第三种隔离级别在读走数据的时候把读走数据锁死第四个级别在读数据时候把整个表锁死别人也不能操作每提升一次级别解决一个问题一个级别是所有问题不存在锁在多线程编程里面非常常用

1、In concurrent programming, there are two basic units of

execution: processes and threads.

In the Java programming language, concurrent programming is mostly concerned with threads.

2、Processes

A process has a self-contained execution environment. A process generally has a complete, private set of basic run-time resources; in particular, each process has its own memory space.

3、Threads

Threads are sometimes called lightweight processes. Both processes and threads provide an execution environment, but creating a new thread

requires fewer resources than creating a new process.

Threads exist within a process - every process has at least one. Threads share the process's resources, including memory and open files. This makes for efficient, but potentially problematic, communication.

进程有自己独立的内存空间而线程在共享整个进程因为所有的线程在共享进程里的所有的资源包括内存包括打开的文件等等资源共享它们的差异在 java 中如何实现多线程

 

二、Thread Objects

Each thread is associated with an instance of the class Thread.

There are two basic strategies for using Thread objects to create a concurrent application.

To directly control thread creation and management, simply

instantiate Thread each time the application needs to initiate an asynchronous task.

To abstract thread management from the rest of your application, pass the application's tasks to an executor.

在 Java 中做多线程编程线程都是做thread对象进行抽象thread 对象进行抽象有两种方式每一次应用想要创建线程实例化thread 对象出实例化之后线程在做异步的任务创建 thread 对象run 自己主线程还继续往下执行进行线程等待用户输入程序没有卡死鼠标都不能动还可以做其操作在等待用户输入的时候还可以做其事情所以每一个线程都是异步执行的任务

 

三、Defining and Starting a Thread

An application that creates an instance of Thread must provide the code that will run in that thread.

There are two ways to do this:

Provide a Runnable object.

The Runnable interface defines a single method, run, meant to contain the code executed in the thread. The Runnable object is passed to the Thread constructor, as in the HelloRunnable example:

public class HelloRunnable implements Runnable {

public void run() {

System. out . println("Hello from a thread!");

}

public static void

main(String args[]) {

(new Thread( new HelloRunnable())) .start();

}

}

定义线程在 java 中实例时候有两种方式一种方式是通过实现接口的方式runnabe 接口里面有run方法定义线程在执行时要干什么可以定义成定时器任务也可以定义成死循环或者语句实现接口后有一个run方法可以被线程化的对象所有的对象都是 thread 对象创建的时候给构造器传递进去实现 runnable 的实例启动新的线程在创建线程之后要调 start 启动启动的时候调用 runnable 里面的 run 方法这种方法是是以实现接口方式做

An application that creates an instance of Thread must provide

the code that will run in that thread.

There are two ways to do this:

Subclass Thread.

The Thread class itself implements Runnable, though its run method

does nothing. An application can subclass Thread, providing its own implementation of run, as in the HelloThread example:

public class HelloThread extends Thread {

public void run() {

System . out. println("Hello from a thread!" ) ;

}

public static void main(String args[]) {

(new HelloThread()) . start();

}

}

直接扩展 thread run 方法创建方式基本上类似但是因为已经是扩展的 thread所以直接创建 hellothread即可不需要创建 runnable 对象作为参数去调构造器器做初始化动作run 方法里面只是一条语句跑完之后语句就结束都是类似的语句虽然很简单从输出输出一条信息但实际上通过线程当前程序里有两个线程一个主程序在执行一个是创建 hellothread 实例的线程主线程开启新的线程线程跑结果跑完之后主线程结束在java 中有约定单根集成在扩展语句只能扩展一个类c++可以扩展bc扩展两个类但是在 java中只能扩展一个类所以 thread还需要实现其它接口比如实现 listener 接口thread 方式不合适因为只能是扩展一个可以让它实现 runnable 或者实现 extends listener如果 listener 不是是接口直接在后面加接口即可因为实现java 的类可以实现多个接口所以用实线或者扩展都可以只会有一点小差异

Thread.sleep causes the current thread to suspend execution for a specified

period.

public class SleepMessages {

public static void main(String args[] ) throws

InterruptedException

{

String importantInfo[] = {

"Mares

eat oats", "Does

eat oats" ,

"Little lambs eat ivy", "A kid will eat ivy too"

};

for (int i = 0; i < importantInfo. length; i++) {

/ /Pause for 4 seconds

Thread. sleep(4000);

//Print a message

System. out . println( importantInfo[i]);

}

}

}

主线程在跑直接调用 thread 类的静态方法如果不加特别声明直接在 thread 类调用静态方法会作用于当前的线程执行 main 的线程每四秒输出一条消息把数组里文件消息输出例子thread 调用当前线程表执行main的线程每四秒输出一条语句4000是不是四秒是不是精确的四秒不一定如果有其它的语言经验比如c++,时间不能精确的保证有时候差异会比较大隔一段时间如果要精确定时不能依靠依靠 sleep其它更精确的东西比如 timer 类可以做到精确时间给改成一秒运行快所以在执行的时候会发现每一秒钟输出个东西输出很慢一条一条输出在 sleep 的时候会碰成各种各样问题当前的线程被挂起可能会休眠

在四秒之后恢复不出两个县城在进行竞争线程可能饿死抛异常如果 slepp 调用catch里面捕获异常做处理如果不做捕获会看到会异常往外跑继续跑所以 main 函数有可能会抛出异常输出把所有的四条语句输出两遍一遍没有进行 catch一遍进行 catch如果进行 catchmain 函数不会抛出异常如果没做自然往外抛抛给当前 main 函数,main 函数抛出异常

An interrupt is an indication to a thread that it should stop what it is doing and do something else.

It's up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate.

A thread sends an interrupt by invoking interrupt on the Thread object for the thread to be interrupted.

for (int i = 0; i < importantInfo. length; i++) {

// Pause for 4 seconds

try {

Ihread. sleep(4000);

}catch (InterruptedException e) {

// We've been interrupted: no more messages.

return;

//Print a message

System. out . println( importantInfo[i]);

}

没有人告诉睡眠四秒程序一定能正常执行四秒之后一定能够恢复回恢复不出来时线程会抛异常捕获掉一旦休眠歇在阶段还会做执行在四秒之后外部的时间触发把它唤醒不一定能唤醒所以良好的习惯是 interrupted 捕获中断异常休眠暂时被中断掉如果线程在执行时需要外界唤醒唤醒之后进行操作

What if a thread goes a long time without invoking a method

that throws InterruptedException?

Then it must periodically invoke Thread.interrupted, which

returns true if an interrupt has been received. For example:

for (int i = 0; i < inputs .length; i++) {

heavyCrunch(inputs [i]);

if (Thread . interrupted()) {

// We've been interrupted: no more crunching .

return;

}

}

In more complex applications, it might make more sense to throw

an InterruptedException:

if (Thread . interrupted()) {

throw new InterruptedException( ) ;

}

主动发现存在问题线程有属性 interrupted东西会标识当前线程是不是处于一种中断状态在循环里会进行操作操作是虚拟的没有写代码假设进行非常重的计算任务耗时会非常长,线程执行完之后,再判断当前的线程会不会被中断,是不是已经处于中断状态,在多线程线系统里面线程是 cpu 赋的时间再执行非常长的动作在执行动作的中途可能被剥夺 cpu 的执行时间于是被中断掉方法执行不下去回过头执行到底下判断当前的线程是不是处于中断状态如果是做事情如果不是不做主动发现线程是不是已经没有在正常中断

Joins

The join method allows one thread to wait for the completion

of another.

If t is a Thread object whose thread is currently executing,

t.join();

causes the current thread to pause execution until t's thread terminates.

Overloads of join allow the programmer to specify a waiting period.

However, as with sleep, join is dependent on the OS for timing, so you should not assume that join will wait exactly as long as you specify.

Like sleep, join responds to an interrupt by exiting with an InterruptedException.

被外部中断人为的也可以主动的要求中断join 方法让其中一个线程等到另外一个线程执行完成t是thread对象t上面调 join,把当前正在执行的代码线程终止掉一直到t完成位置再恢复过来上面有节代码下面有节代码执行代码在某一个线程上执行一旦在线程里调用t.join 把当前的线程挂起t执行完之后才会再恢复出继续执行下面的在过程中要注意和不是线程里面跑把t的 join 插在这里面跑看起来一样如果正常执行下来功能一样因为已经执行完前面的整个线程挂起执行T终止完之后回执行但是现在是两个线程在跑如果在执行很有可能T线程在执行时又被别中断或者因为某种原因T线程崩掉但不管如何线程还是处于挂起状态只要能被唤醒可以继续执行所以只是 T所以为什么用 join看起来t任务加入到当前的线程里面一样但是又不影响线本身会不会被放弃掉或者会不会崩溃相对独立的两个像 sleepjoin 也被挂起中断等到满足某个条件比如时间到或者t执行完恢复恢复可能会存在问题所以只要掉 join 或者 sleep 都要让函数抛出中断方法中断异常抛出进行捕获进行处理join 什么都没做参数什么都没写载的版本可以指定时间在给定时间直接回让线程恢复掉只给t一秒钟时间执行时间本身是依赖于操作系统不一定精确有可能会有一点差异

SimpleThreads consists of 'two threads.

The first is the main thread that every Java application has.

The main thread creates a new thread from the Runnable object, MessageLoop, and waits for it to finish.

If the MessageLoop thread takes too long to finish, the main thread interrupts it.

The MessageLoop thread prints out a series of messages.

If interrupted before it has printed all its messages,

the MessageLoop thread prints a message and exits.

执行 main 函数是一个线程主线程创建一个新线程main 是主线程在执行 main 的时候如果在执行时传参数可以等待多长时间再传参数参数表示可以等待多长时间时间是秒数成立1000将传递给 join 或者 sleep创建线程MessageLoop 执行MessageLoop 是用实现 runnable 方法实现另一个线程的类run 就是分装的逻辑四个消息每隔四秒钟打印出一个消息现在打印只用 threadMessage助手函数助手函数在前面编译给一个字符串输出当前的线程currentthread 的静态方法返回当前正在执行的线程如果在某一个 Message 入口里面输出 Message 入口的实例的线程输入线程的名字用 system.out 输出线程名字和 message知道是哪个线程产生输出启动 Runnable loop 开始进入循环等待不断的判断当前的进程是不是活的等待主线程让掉挂起等t确定时间一秒一秒之后即使不执行完也要继续往下执行把时间抢回来当前时间减去线程已经起始的时间获取系统时间如果让系统执行的时间已经超出了容忍范围并且t还是活着的把t打断整个线程没有办法往下执行所以整个线程要控制主线程结束t到后面自己再做定的时间比较短才能看到效果

即使 join 也会抛异常只要 interrupted 人为设置把线程终止掉又把自己挂起输出继续执行抛异常如果把时间设长默认值是一秒等四秒休眠两秒可以两条信息之后才会终止message 入口什么都没做直接挂掉输出一条信息又输出第二条信息四秒钟到不想等还没做完结束例子是有主线程在跑

主线程创建新的线程 messageloop如果花很长时间做完比如每两秒输出一条要输出八秒时间非常长主线程打断因为主线程最多只等待四秒四秒之后把 message 入口中断掉相当于把 messageloop tinterrupted属性设置为 true中断之后 messageloop 继续做一旦被中断只能到 cach 中打印消息还没完整个线程就结束了main 就结束了输出 simple 的代码在t结束之后join 之后还没做完输出一个 finally 的结束例子中可以看到两个线程互相之间的交互,一个是如何 join 一个是如何中断

The SimpleThreads Example

image.png

messageloop 正常情况下会执行循环四条语句全部执行完一旦把 interrupted 设置好就会抛出异常即使再让线程恢复也只能在 catch 捕获异常输出信息

image.png

开启一个线程让线程跑自身执行线程不断着盯着t有没有活着只要活着就给一秒钟做每一秒钟判断一次给的时间已经大于能容忍的时间每隔一秒做一次能容忍四秒所以前四次什么都没做再判断是不是活着一旦时间超过并且线程还活着还没死还没执行完中断再继续执行把自己收尾的工作给收掉比较暴力把线程终止很多的弊端比如线程循环的做现在中断没什么问题但是如果在线程里面获取数据库的连接还有其它的 Timer 对象如果比较暴力直接中断不管操作并不好应该在 catch 里面想办法把连接释放否则还占用连接timer 清零clear比较好的终止 message 方法而不是仅仅中断因为中断只是被挂还在占用系统资源在等待时机有人去唤醒代码虽然很简单实现主线程和定义的线程之间的交互并且安全的关闭 Message loop的线程比较合适比较优雅的方式


四、Synchronization

Threads communicate primarily by sharing access to fields and the objects reference fields refer to.

This form of communication is extremely efficient, but makes two kinds of errors possible: thread interference and memory consistency errors.

The tool needed to prevent these errors is synchronization.

However, synchronization can introduce thread contention, which occurs when two or more threads try to access the same resource

simultaneously and cause the Java runtime to execute one or more threads more slowly, or even suspend their execution. Starvation and livelock are forms of thread contention.

跟a和b转账的例子类似如果在一个 java 的程序里面比如有一个变量a加一b减一再共同访问内存里一个变量的时候跟转账操作类似线程之间也会产生类似的干扰导致内存的不一致性错误比如值现在是0做加一操作应该变成一减一操作应该变成负一比如a先执行完变成一在做简易操作的时候恢复到里面如果同时在读改写为一输出都是零b把零改写为负一谁后提交就把前面覆盖跟事务的操作一样线程跟资源产生竞争这就是临近资源竞争都想写它都想有多个线程都想访问相同的资源并且同时访问同时访问就会产生很多的问题这就是解决它的思路考虑的问题在哪里某一个线程饿死死锁活锁三种情况

Thread Interference

Consider a simple class called Counter

class Counter {

private int c= 0; 只有一个变量c标识基础值

public void increment() { c++; }方法加一

public void decrement() {c--; }减一

public int value(){ return c; }返回值

}

if a Counter object is referenced from multiple threads, interference between threads may prevent this from happening as expected.

Counter 对象可能会被多个线程进行处理

Thread Interference

Suppose Thread A invokes increment at about the same time

Thread B invokes decrement.

If the initial value of c is 0, their interleaved actions might

follow this sequence:

- Thread A: Retrieve c.

- Thread B: Retrieve c.

- Thread A: Increment retrieved value; result is 1. .

- Thread B: Decrement retrieved value; resultis -1.

- Thread A: Store resultin c; c is now 1.

- Thread B: Store resultin c; c is now-1.

Thread A's result is lost, overwritten by Thread B.

一个要做加一操作一个要做减一操作c的值是零同时取出c的值取出来都是零a操作变成一b操作变成负一A写回c是一B写回c负负一把一覆盖看不到曾经做过一次加一操作希望最后c是零因为做一次加一操作做一次减两个线程如果同时访问B会把a的值覆盖

Memory consistency errors

Memory consistency errors occur when different threads have

inconsistent views of what should be the same data.

The key to avoiding memory consistency errors is

understanding the happens-before relationship.

This relationship is simply a guarantee that memory writes by one specific statement are visible to another specific statement.

问题是内存的不一致性无论是哪种语言在并发编程里面考虑的不是只是 java在任何编程语言里面都强调当有多个线程在进行临近资源的访问线程是在共享进城里面所有资源变量是进程的资源所以会被所有的线程访问到所以竞争是不可避免的但是操作必须要按照 happens-before 的顺序进行执行A的操作和B的操作满足happens-before 的关系确保对内存进行写操作的时候必须是某一条具体的语句写内存的语句对于另一条语句是可视的A在加一B在减一A加一的动作必须要让B减一的动作看见反过也是一样想满足条件必须a先发生B再发生B要跟着a发生之后已经变成基础之上才操作才能看到是负一计算机可以并行微观上还是串行宏观上是并行说的同时是不可能两个同时做在微观上一定是a先B后或者是B先a后有差异但是在多核系统里面不太保险确实有可能两条指令在两个河里加载同时执行必须保证俩在时序上有先后必须是执行完才能执行能看到前执行的效果确保 happens-before 的关系关系一旦确保不会存在问题A一旦执行完B必须能看到a执行完的结果一直执行不可能是 B 和 a 真的是完全同步执行

Memory Consistency Errors

Suppose a simple int field is defined and initialized:

int counter= 0;

The counter field is shared between two threads, A and B. Suppose thread A increments counter:

counter++;

Then, shortly afterwards, thread B prints out counter:

System.out.println(counter);

If the two statements had been executed in the same thread, it would be safe to assume that the value printed out would be "1".

But if the two statements are executed in separate threads, the value printed out might well be "O", because there's no guarantee that thread

A's change to counter will be visible to thread B - unless the

programmer has established a happens-before relationship between these two statements.

B 线程要在 a 线程之后执行必须要建立 A 和 B 两条语句加一减一之间的 happens before 的关系加一和减一的动作不能直接同时执行

image.png

在 java 提供关键字不安全的多线程创建 counter 对象定义 counterloop 的 runnable 线程类每隔一秒钟对 C 做加一的操作获取 C 的值对 C 的值做加一操作

image.png

在主线程里面创建两个新的线程对象两个线程都是 counterloop

启动两个同时做操作都在做加一操作输出里面的值做加一操作没有做任何同步的控制,00,突然间变成二二四四两个线程做加的操作中间看不到一三的状态因为读走都是0在往回写的时候先顺序并发的执行看不到一的状态看不到三状态有问题执行两遍

Synchronized Methods

To make a method synchronized, simply add the synchronized keyword to

its declaration:

public class SynchronizedCounter {

private intc= 0;

public synchronized void increment() { c++; }

public synchrorized void decrement(){C--; }

public synchronized int value(){ return c; }

If count is an instance of SynchronizedCounter, then making these methods

synchronized has two effects:

First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.

Second, when a synchronized method exits, it automatically establishes a happens- before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.

Synchronized 关键字在一个类里面Synchronized 关键字在任何一个时刻都不可能有两个对方法调用同时执行如果 a 在调 Synchronized 修饰过的 incrementb 在调 Synchronized 修饰过的 decrement方法一定不可能在时间上有交重复的部分执行执行另外一个不管先执行 a 还是先执行 b只能先执行一个再执行另外一个这是 Synchronized关 键词的作用当有人想要获取基础值的时候不能有人对做加减一的操作方法返回之后才能做加一减一的操作不可能在读取基础值的同时执行人为建立三个方法不可能同时执行只要有方法执行另外两个方法不可能被执行一定要等方法结束另外两个方法才有可能得到执行

相关文章
|
6月前
|
Python
Threading
Threading
38 3
|
存储 安全 Java
Threading 2 |学习笔记
快速学习 Threading 2
111 0
Threading 2 |学习笔记
|
缓存 安全 Java
Threading 3|学习笔记
快速学习 Threading 3
100 0
Threading 3|学习笔记
|
Python
python多线程(threading库)
我们在平常工作中会遇到需要同时做操作的功能,这里我举一些实例给大家看下多线程的处理
175 0
python多线程(threading库)
|
安全 调度 Python
Python 多线程之threading介绍
Python 多线程之threading介绍
640 0
Python 多线程之threading介绍
C#编程-145:Threading线程基础
C#编程-145:Threading线程基础
C#编程-145:Threading线程基础
|
Python
python多线程执行任务Threading
python多线程执行任务Threading
|
Python
Python编程:threading多线程
Python编程:threading多线程
116 0