java多线程相关笔记

变量空间开辟条件:

1.数据类型
2.变量名称
3.变量的初始化值

变量的作用域

1.变量的作用域:从变量定义的位置开始到所在的那对大括号结束
2.生命周期:从定义的位置活着-》到达其所在作用域的时候在内存中消失

数据类型

byte,short,int,long,float,double,char,boolean

java内存

1.分为5块内存:寄存器,本地方法区,方法区,栈,堆
2.栈:存储的是局部变量(如函数的参数,语句中的变量,函数中定义的变量)
3.堆:用于存储实体(数组和对象),实体:封装多个数据的
4.
(1)每一个实体都有内存首地址值
(2)堆内存中的变量都有默认初始化值。数据类型不同,值夜不一样
(3)垃圾回收机制

多线程

1.线程和进程
1.线程:进程中程序的执行单元,但是线程本身依靠程序运行
2.进程:执行中的程序,其中一个进程至少包含一个线程
2.多线程的作用
1.发挥多核CPU的优势

随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程"同时"运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。

2.防止阻塞

从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
例子:java的垃圾回收是由专门的线程来处理的

3.线程的实现
1.继承Thread类
(1)继承Thread类,重写run()方法
(2)Thread的run()方法不会创建新的进程,start()会创建新的线程
(3)只有调用start()才会表现出多线程的特性,而run()还是同步去执行的

public class Test {
    public static void main(String[] args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyThread thread1 = new MyThread("thread1");
        thread1.start();
        MyThread thread2 = new MyThread("thread2");
        thread2.run();
    }
}


class MyThread extends Thread{
    private String name;

    public MyThread(String name){
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
    }
}

2.实现Runnable接口(常用,又称作并发编程,就是将大的任务拆成一个一个的小的任务即Runable)
(1)实现Runnable接口,重写run方法
(2)将Runnale作为参数传入Thread类中,再调用Thread.start()
public class Test {
    public static void main(String[] args)  {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
} 
class MyRunnable implements Runnable{
    public MyRunnable() {
    }

    @Override
    public void run() {
        System.out.println("子线程ID:"+Thread.currentThread().getId());
    }
}

3.other 使用ExecutorService,Callable,Future实现由返回结果的多线程
(1)Executors框架功能类:用ExecutorService,Callable,Future可以实现
(2)
->可返回值的任务必须实现Callable接口,无返回值的必须实现Runnable接口
class MyCallable implements Callable<Object> {  
    private String taskNum;  
    
    MyCallable(String taskNum) {  
       this.taskNum = taskNum;  
    }  
    
    public Object call() throws Exception {  
       System.out.println(">>>" + taskNum + "任务启动");  
       Date dateTmp1 = new Date();  
       Thread.sleep(1000);  
       Date dateTmp2 = new Date();  
       long time = dateTmp2.getTime() - dateTmp1.getTime();  
       System.out.println(">>>" + taskNum + "任务终止");  
       return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";  
    }
}

->ExecutorService.newFixedThreadPool(5);//创建线程池(拥有5个线程)
注:可以创建固定数目的线程池,单线程,可缓存的线程池

->创建5个有返回值的任务
for (int i = 0; i < 5; i++)  {  
    Callable c = new MyCallable(i + " ");  
    // 执行任务并获取Future对象  
    Future f = pool.submit(c); 
    list.add(f);  
}  

// 关闭线程池
pool.shutdown();

// 获取所有并发任务的运行结果
for (Future f : list) {
// 从Future对象上获取任务的返回值,并输出到控制台
System.out.println(">>>" + f.get().toString());
}
// 关闭线程池
pool.shutdown();
【注:ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,
返回Future。如果Executor后台线程池还没有完成Callable的计算,
这调用返回Future对象的get()方法,会阻塞直到计算完成】

4.线程的状态
(1)创建(new):准备好了一个多线程的对象
(2)就绪(Runnable):调用了start(),等待CPU分配
(3)运行(running):执行run方法
(4)阻塞(blocked):暂时停止执行,wait,sleep,IO阻塞或者被同步块阻塞
注:sleep()和wait()区别
——sleep()是Thread类的方法,wait是Object类中定义的方法
——Thread.sleep不会改便锁行为的改变,进而不会让线程释放锁
——Thread.sleep和Object.wait都会暂停当前进程,
  sleep可以到了时间会自动获取到CPU执行时间
  wait需要别的线程执行notify/notifyAll才能重新获得CPU执行时间
5.线程的切换(上下文切换)
(1)线程上下文切换过程中会记录程序计数器,CPU寄存器装填等数据
OR:存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。
(2)多线层的优缺点:
优点:发挥了多核CPU的作用,任务执行效率得到提升
缺点:线程的切换需要保存CPU的状态,开销代价,并且多个线程会导致系统资源的
      占用增加。
6.线程间通信
1.同步
多个线程通过synchronized关键字来实现线程间的通信
public class MyObject {

synchronized public void methodA() {
    //do something....
}

synchronized public void methodB() {
    //do some other thing
}

}

public class ThreadA extends Thread {

    private MyObject object;
//省略构造方法
    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}

public class ThreadB extends Thread {

    private MyObject object;
//省略构造方法
    @Override
    public void run() {
        super.run();
        object.methodB();
    }
}

public class Run {
    public static void main(String[] args) {
        MyObject object = new MyObject();

        //线程A与线程B 持有的是同一个对象:object
        ThreadA a = new ThreadA(object);
        ThreadB b = new ThreadB(object);
        a.start();
        b.start();
    }
}
线程A和B持有同一个对象object,线程B需要等待线程A执行完了methodA()后才能执行
methodB(),这样就实现了线程A和B的“共享内存”式的通信。
注:多个线程需要访问同一个变量,谁拿到了锁(访问权限),谁就可以执行

2.while轮询的方式
线层A不停改变某个类里集合的条件,而线程B则是不断轮询(死循环)当条件成立时
则跳出循环
import java.util.ArrayList;
import java.util.List;

public class MyList {

    private List<String> list = new ArrayList<String>();
    public void add() {
        list.add("elements");
    }
    public int size() {
        return list.size();
    }
}

import mylist.MyList;

public class ThreadA extends Thread {

    private MyList list;

    public ThreadA(MyList list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                list.add();
                System.out.println("添加了" + (i + 1) + "个元素");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

import mylist.MyList;

public class ThreadB extends Thread {

    private MyList list;

    public ThreadB(MyList list) {
        super();
        this.list = list;
    }

    @Override
    public void run() {
        try {
            while (true) {
                if (list.size() == 5) {
                    System.out.println("==5, 线程b准备退出了");
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

import mylist.MyList;
import extthread.ThreadA;
import extthread.ThreadB;

public class Test {

    public static void main(String[] args) {
        MyList service = new MyList();

        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
    }
}
线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件
(list.size()==5)是否成立 ,从而实现了线程间的通信。
浪费CPU资源,因为B线程只是在不断的检测某个条件是否成立,没做撒有用的工作

3.wait/notify机制
import java.util.ArrayList;
import java.util.List;

public class MyList {

    private static List<String> list = new ArrayList<String>();

    public static void add() {
        list.add("anyString");
    }

    public static int size() {
        return list.size();
    }
}


public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                if (MyList.size() != 5) {
                    System.out.println("wait begin "
                            + System.currentTimeMillis());
                    lock.wait();
                    System.out.println("wait end  "
                            + System.currentTimeMillis());
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                for (int i = 0; i < 10; i++) {
                    MyList.add();
                    if (MyList.size() == 5) {
                        lock.notify();
                        System.out.println("已经发出了通知");
                    }
                    System.out.println("添加了" + (i + 1) + "个元素!");
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {

    public static void main(String[] args) {

        try {
            Object lock = new Object();

            ThreadA a = new ThreadA(lock);
            a.start();

            Thread.sleep(50);

            ThreadB b = new ThreadB(lock);
            b.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程A要等待某个条件满足时(list.size()==5),才执行操作。
线程B则向list中添加元素,改变list 的size。

A,B之间如何通信的呢?也就是说,线程A如何知道 list.size() 已经为5了呢?
这里用到了Object类的 wait() 和 notify() 方法。

当条件未满足时(list.size() !=5),线程A调用wait()
放弃CPU,并进入阻塞状态。---不像②while轮询那样占用CPU
当条件满足时,线程B调用 notify()通知线程A,
所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。
这种方式的一个好处就是CPU的利用率提高了。

但是也有一些缺点:比如,线程B先执行,一下子添加了5个元素
并调用了notify()发送了通知,而此时线程A还执行;
当线程A执行并调用wait()时,那它永远就不可能被唤醒了。
因为,线程B已经发了通知了,以后不再发通知了。
这说明:通知过早,会打乱程序的执行逻辑。
7.线程常用方法
start():启动线程
sleep():休眠线程-交出CPU
注:sleep不会释放锁,当前线程持有某个对象的锁,其他线程不能访问该对象
join():使其他进程等待当前进程终止后再执行
currentThread():返回代码块正被哪个线程调用
Thread.getId():线程唯一标识
isAlive():线程是否处于活动状态(是否在运行)
interrupt():暂停线程
getPriority和setPriority:获取和设置线程优先级
8.多线程的原子性可见性有序性
1.原子性
cpu不可以再中途暂停然后再调度,即不被中断操作,必须执行完成.
2.可见性
详见volatile关键字解析
3.有序性
(1)如果在本线程内观察,所有操作都是有序的【线程内表现为串行语义】
如果在一个线程中观察另一个线程,所有操作都是无序的
【指令重排序”现象和“工作内存中主内存同步延迟”现象】
(2)Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性
(3)Java“先行发生”(Happen-Before)的原则
指的是Java内存模型中定义的两项操作之间的依序关系
如果说操作A先行发生于操作B,其实就是说发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包含了修改了内存中共享变量的值、发送了消息、调用了方法等。它意味着什么呢?如下例:

//线程A中执行 
i = 1;

//线程B中执行 
j = i;

//线程C中执行 
i = 2; 
假设线程A中的操作”i=1“先行发生于线程B的操作”j=i“,那么我们就可以确定在线程B的操作执行后,变量j的值一定是等于1,结出这个结论的依据有两个,一是根据先行发生原则,”i=1“的结果可以被观察到;二是线程C登场之前,线程A操作结束之后没有其它线程会修改变量i的值。现在再来考虑线程C,我们依然保持线程A和B之间的先行发生关系,而线程C出现在线程A和B操作之间,但是C与B没有先行发生关系,那么j的值可能是1,也可能是2,因为线程C对应变量i的影响可能会被线程B观察到,也可能观察不到,这时线程B就存在读取到过期数据的风险,不具备多线程的安全性。

下面是Java内存模型下一些”天然的“先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,可以在编码中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们进行随意地重排序。

a.程序次序规则(Pragram Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环结构。

b.管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而”后面“是指时间上的先后顺序。

c.volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读取操作,这里的”后面“同样指时间上的先后顺序。

d.线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。

e.线程终于规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值等作段检测到线程已经终止执行。

f.线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测是否有中断发生。

g.对象终结规则(Finalizer Rule):一个对象初始化完成(构造方法执行完成)先行发生于它的finalize()方法的开始。

g.传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

一个操作”时间上的先发生“不代表这个操作会是”先行发生“,那如果一个操作”先行发生“是否就能推导出这个操作必定是”时间上的先发生“呢?也是不成立的,一个典型的例子就是指令重排序。所以时间上的先后顺序与先生发生原则之间基本没有什么关系,所以衡量并发安全问题一切必须以先行发生原则为准。
volatile关键字
1.java的内存模型
——>每一个线程都有自己的内存空间(栈空间)
——>线程执行后会读取主内存中的共享变量到自己的内存中
——>对变量操作后在某个时间刷新回主内存

2.volatile作用
使变量在多个线程间可见:强制线层从主内存中去取volatile修饰的变量
注(使用场景):多个线程间需要根据某个条件确定哪个线程可以执行,要确保整个条件
是可见的,即(这个条件是volatile修饰过的)线程从主内存中取这个条件的值

3.volatile修饰的变量并不保证对它的操作(自增)具有原子性
例子:
->public volatile static int count;
->创建100个线程,每个线程执行100次累加1的动作,但是到最后count确没达到10000
仅靠volatile不能保证线程的安全性(原子性)

4.volatile 与 synchronized 的比较
——volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
——volatile只能保证数据的可见性,不能用来同步,
因为多个线程并发访问volatile修饰的变量不会阻塞
——synchronized不仅保证可见性,而且还保证原子性
因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行
多个线程争抢synchronized锁对象时,会出现阻塞
总结:线程安全性,①可见性。②原子性
仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性
synchronized关键字
(1)synchronized 关键字来实现,所有加上synchronized和块语句
,在多线程访问的时候,同一时刻只能有一个线程能够用.

(2)synchronized 关键字锁住的是当前对象。这也是称为对象锁的原因
 public class MyObject {
    synchronized public void methodA() {
        //do something....
    }
 }
methodA()是实例方法,要想执行methodA()必须实例化MyObject才能对象.方法()调用
synchronized就是把MyObject这个对象加锁了

(3)特点
线程A和B持有同一个对象object,线程B需要等待线程A执行完了methodA()后才能执行
methodB(),这样就实现了线程A和B的“共享内存”式的通信。
注:
public class MyObject {//对象object有连个方法
    synchronized public void methodA() {
         //do something....
    }

    synchronized public void methodB() {
        //do some other thing
    }
}

(4)使用synchronized避免 因数据不一致性而导致读脏数据的情况
public class MyObject {

    private String userName = "b";
    private String passWord = "bb";
    
    synchronized public void methodA(String userName, String passWord) {
        this.userName = userName;
        try{
            Thread.sleep(5000);
        }catch(InterruptedException e){
            
        }
        this.passWord = passWord;
    }

    synchronized public void methodB() {
        System.out.println("userName" + userName + ": " + "passWord" + passWord);
    }
}
——》methodA()负责更改用户名和密码。在现实中,一个用户名对应着一个密码
methodB()负责读取用户名和密码
——》线程A执行methodA()去更改密码,线程B执行methoB()去读取密码
未用synchronized修饰methodA和methodB,线程B读取到的用户名
——》线程A更改了的用户名("a"),但是密码却是原来的密码("bb")。
因为,线程A睡眠了,还没有来得及更改密码。
——》用synchronized修饰methodA和methodB后,就避免了数据的不一致性而导致的脏读问题
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343