多线程随笔
最近在准备秋招,看了一些多线程的基础,个人记一下。
线程与进程
简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
java 多线程
java 多线程的实现一般实现Runnable接口,重写run()方法,调用start方法开启线程。多线程最重要解决数据安全问题,所以引入JMM,java内存模型,JMM的关键技术都是围绕多线程的原子性,可见性,有序性的。简单介绍一下吧。
- 原子性:是指操作的不可中断的,在多线程情况,不被其他线程干扰。
- 可见性:是指线程修改了共享变量的值,其他线程是马上知道这个修改。
- 有序性:Happen-Before 规则 详见博客
线程的状态
新建线程:
start() run() 区别 run只是在当前线程运行,start()创建新的线程 自定义线程实现runnable 接口 重载run ()。
线程终止:
stop()被废弃了 stop()会立即释放这个线程所只有的锁,会造成数据不一致。
线程中断:
Thread.interrupt()设置中断标识位
Thread.isInterrupted()判断是否被中断 查看检查中断标识
Thread.interrupted()判断是否被中断 并清除中断标 识线程让步:
线程让步,其方法如下:
static voidyield()
暂停当前正在执行的线程对象,并执行其他线程
线程让步用于正在执行的线程,在某些情况下让出CPU资源,让给其它线程执行线程睡眠:
sleep() Thread 的方法 线程睡眠的过程中,如果是在synchronized线程同步内,是持有 锁(监视器对象)的,也就是说,线程是关门睡觉的,别的线程进不来
线程合并: join()
线程合并是优先执行调用该方法的线程,再执行当前线程
java多线程中的关键字、
- synchronized
其作用是实现线程间的同步。对同步的代码加锁,使每一次,只有一个线程进入同步块。在需要同步的变量,方法和类上加入关键字。 - volatile
volatile是一种弱的同步手段,相对于synchronized来说,某些情况下使用,可能效率更高,因为它不是阻塞的,尤其是读操作时,加与不加貌似没有影响,处理写操作的时候,可能消耗的性能更多些
列子:
public class Test {
static volatile int i = 0, j = 0;
static void one() {
i++;
j++;
}
static void two() {
System.out.println("i=" + i + " j=" + j);
}
public static void main(String args[]){
for(int i=0;i<10;i++){
new Thread() {
public void run() {
one();
}
}.start();
}
for(int i=0;i<10;i++) {
new Thread() {
public void run() {
two();
}
}.start();
}
}
}
运行后发现i与j的值一样
加上volatile可以将共享变量i和j的改变直接响应到主内存中,这样保证了主内存中i和j的值一致性,所以volatile可以保证内存可见性,但是没有原子性
3. lock
Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,例如
public class LockTest {
public static void main(String[] args) {
final Outputter1 output = new Outputter1();
new Thread() {
public void run() {
output.output("zhangsan");
};
}.start();
new Thread() {
public void run() {
output.output("lisi");
};
}.start();
}
}
class Outputter1 {
private Lock lock = new ReentrantLock();// 锁对象
public void output(String name) {
// TODO 线程输出方法
lock.lock();// 得到锁
try {
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
} finally {
lock.unlock();// 释放锁
}
}
}
这样就实现了和sychronized一样的同步效果,需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内。
4. 读写锁:直接上例子
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//写和写是互斥的,但是读和读是不需要互斥的
public class ReadWriteLockTest {
public static void main(String[] args) {
final Data data = new Data();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 5; j++) {
data.set(new Random().nextInt(30));
}
}
}).start();
}
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
for (int j = 0; j < 5; j++) {
data.get();
}
}
}).start();
}
}
}
class Data {
private int data;// 共享数据
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public void set(int data) {
rwl.writeLock().lock();// 取到写锁
try {
System.out.println(Thread.currentThread().getName() + "准备写入数据");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + "写入" + this.data);
} finally {
rwl.writeLock().unlock();// 释放写锁
}
}
public void get() {
rwl.readLock().lock();// 取到读锁
try {
System.out.println(Thread.currentThread().getName() + "准备读取数据");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "读取" + this.data);
} finally {
rwl.readLock().unlock();// 释放读锁
}
}
}
在读数据时,不需要加锁,效率更高