进程只能通过消息传递进行通讯(因为进程有自己私密的内存)
而线程可通过共享内存,消息传递进行通讯(线程:轻量级)
线程有自己独立的方法栈和寄存器
一个进程中可以包含多个线程
多线程:充分利用资源,快
创建线程的方法
1.使用线程类进行继承(从Thread类派生)
要对所有方法进行重写(故很少使用)
2.更常用的是用Runnable接口构造Thread对象(new Thread())
Thread类已经继承了Runnable接口
创建一个子类继承Thread类 ,但要重写所有方法(不推荐)
创建一个子类继承Runnable接口,只需要重写run()方法;
运行时new Thread(子类).start() (只能用start方法建立一个线程,直接调用run方法只是调用run,仍为单线程)
有几个start方法,有几个线程;如果在main线程中调用则+1(main方法对应的线程)(未调用start未创建线程)
(也可以使用匿名类)
时间分片是由OS自动调度的
单行,单条语句都未必是原子的
是否为原子语句是由操作系统决定的(Java有JVM决定)
但我们可以强制将一条语句“转化”成原子语句
线程的执行顺序与语句的顺序无关
race conditon(线程干扰)
执行时是按原子操作做单位操作,这样会导致一个线程一个语句未执行完时(只完成了若干原子步)
另一个线程的语句开始执行,因此产生的bug(会产生多种不同的bug结果)
eg. 对同一个x进行创建两个线程A,B(由共享内存引起的)
x=1
A:x=2x;
B:x=3x;
调用顺序 :A B
乘法操作包含:1取,2运算,3放回
如果 顺序为:A1->A2->A3->B1->B2->B3这样执行,结果是对的
但如果为 A1->B1->A2->A3->B2->B3 初始A,B获取的x值都为1。A执行完毕后,将2放回x;B执行完后得到的结果是3,放回后覆盖x,最终结果为3而不是6
(还有其他结果)//拓扑排序//
解决:将x=2x; 和x=3x 强制转化为原子操作
无论是基于内存共享还是消息传递都无法避免race condition
并发的bug很难复现,是随机性的bug
Thread.sleep() 令线程进行休眠
1.将某个线程休眠,让出资源,零其他线程有更多执行机会
2进入休眠的线程不会失去对现有monitor或锁的所有权
Thread.interrupt() 向线程发送中断信号
t.interrupt() 在其他线程中向t发送中断信号
刚开始是直接终止;后来要不要中断由t线程自己决定
(注:线程收到其他线程发来的中断信号,并不一定意味着要“停止”)
t.isInterrupted() 检查t线程的中断状态标识位
interrupted() 也可以检查线程是否中断,但他会对中断状态位进行改变(复位)
当t线程休眠时调用t.interrupt(),t线程会被唤醒并报出一个异常
Thread.join()
让当前线程保持执行 ,直到执行结束
可以用join()来维护执行顺序
也会报出中断异常
如何设计一个线程安全的ADT
读共享内存资源时不会引起错误,当修改时会引起错误
策略1:Confinement 限制数据共享
策略2:Immutability 共享不可变数据
策略3:Using Threadsafe Data Types 使用线程安全的数据类型
(提供的操作都是原子操作,不可切片)
策略4:同步机制
线程安全:多个线程并发执行时最用得到的结果与期望相同,不会出现冲突
策略1:
核心思想:线程之间不共享可变数据
避免全局/静态变量
如果一个ADT的rep中包含mutable属性且多线程之间对其进行mutator操作
那么就很难使用该策略来保证ADT是线程安全的
策略2:
使用不可变数据类型和不可变引用来避免race condition
不可变数据通常是线程安全的(但不是所有都是,不可变是从用户角度,并不是完全不可变)
如果ADT中使用了beneficent mutation,必须通过“加锁”机制来保证线程安全
策略3:
如果必须使用mutable类型数据,使用线程安全的数据类型(会影响性能)
Java 集合类都是线程不安全的(List,Set,Map)
Java API 提供了进一步的decorator:对它们每一个操作变成原子操作