前言
多线程和高并发专题
系统编程(多任务)
什么是多任务
操作系统如何实现多任务
1.时间片切换
2.优先级别调度
java是如何实现多任务 – 多线程
java如何实现多线程的方法:
1.继承Thread
2.实现Runable接口
3.实现Callabe和Future接口(1.5提供的)
4.jdk提供了线程池用来获取线程(1.5)
线程安全问题:
加锁
1.synchronized关键字的使用
2. Lock锁
正文
(一)synchronized关键字
同步锁:
有三种写法
1、放在方法上:整个方法都是同步 如果某个方法中所有代码,都有可能出现线程安全问题,建议将synchronized直接写在方法上面,如果将synchronized写在方法上面,该方法就是锁。如下代码示例:
public class TestThread implements Runnable {
private int count = 0;
@Override
public synchronized void run() {
for (int i = 0; i < 1000000; i++) {
count++;
}
}
System.out.println("结果是:"+ count);
}
2、同步块:将有可能出现线程安全的代码放在一个同步块中key就是一个对象,什么对象都可以,一般建议使用this关键字充当synchronized (key) { // 可能出现线程安全问题放在这里面 count++;} 如下代码示例:
public class TestThread implements Runnable {
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
synchronized(this){
count++;
}
}
}
System.out.println("结果是:"+ count);
}
3、静态方法:比较特殊,单独说该静态方法加synchronized关键字,因为静态方法属于类,所以该类(本质就是该类的字节码文件)在充当锁。如下代码示例:
public class TestThread implements Runnable {
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
synchronized(TestThread.class){
count++;
}
}
System.out.println("结果是:"+ count);
}
}
最后补充一下关于synchronize:
synchronized锁的锁升级问题
jdk7.0 synchronized进行了打的改动:
synchronized直接向操作系统申请锁,资源的消耗太大了,太重了。
Oracle进行了改造:
1、无锁状态(偏向锁)
2、锁就会从偏向锁升级到自旋锁(CAS)CAS(compare and swap):比较并且交换自旋锁很容易引起:ABA问题 。
3、自旋锁升级为重量级锁
(二)DCL(double check lock)
双重锁
1.懒汉式,单例设计模式中的写法!!
2.要注意,指令重排序问题!!!
懒汉式:
1.在需要使用对象的时候,再去创建对象,解决掉了饿汉式的内存占有问题。
2.懒汉式是无法直接使用的多线程中,因为是非线程安全的.
如果要解决线程安全问题,需要加锁处理:
1.直接在方法上面加锁,直接解决掉了线程安全,但是效率不高(锁的范围太大)
2.在创建对象的代码上加锁(锁的范围小,效率高)<======如果这种写法,需要两次判断非空
public class Single2 {
//volatitle
// 1、起到了内存可见性;2、禁止指令重排序
private static volatile Single2 single = null;
// 私有化构造函数
private Single2() {}
// 懒汉式
public static Single2 getInstance() {
if (single == null) {
synchronized (Single.class) {
if (single == null) {
single = new Single2();
}
}
}
return single;
}
}
(三)volatile
volatile关键字:
高并发:可见性、有序性、原子性。
多线程情况下,每一个线程都独立拥有一个执行栈,每一个线程都是独立有用数据的,彼此之间是内存不可见的。
在java中,volatile关键字有两大核心作用:
1、被它修饰的变量,在多线程中,可以打破内存屏障,也就是说被它修饰的变量,就有可见性的。(每一次数据变化,每个线程都是知道的)
2、禁止指令重排序!!!
public class TestThread03 {
public static void main(String[] args) throws InterruptedException {
MyThread03 mt = new MyThread03();
mt.start();
Thread.sleep(3000);
mt.flag = true;
System.out.println("此时count ="+ mt.count);
}
}
class MyThread03 extends Thread {
public int count;
// volatile 关键字,让变量可见性
public volatile boolean flag;
@Override
public void run() {
// 程序并没有被终止
while (!flag) {
count++;
}
}
}
(四)死锁(dead lock)
多线程情况下,线程安全问题,通过加锁来解决问题。
死锁现象:一定要避免!!!造成大量的资料浪费,同时有解决不了问题。
避免死锁:一种比较优秀的解决方案:银行家算法
形成死锁有四个必要条件:
1.互斥
2.请求保持
3.环路等待
4.不可剥夺条件
死锁是一种资源的浪费,在正常的编程中,一定要避免死锁。