多线程学习笔记

——-初学整理,有点乱

1.进程和线程

进程:程序或者任务的执行过程,拥有资源和线程。
线程:系统中的最小执行单位,一个进程可以有多个线程,多个线程共享进程的资源

2.线程的交互方式

同步和互斥

3.线程的安全问题

当多个线程共享一个全局变量的时候,做写的时候可能会受到其它线程的干扰,从而导致数据产生错误的问题。(做读的时候不会,操作局部变量也不会有这种问题)。因为当多个线程同时写一个全局变量的时候会产生不可见性。
以卖票为例子:
public class Thread01 {
    public static void main(String[] args) {
        SaleTickets saleTickets=new SaleTickets();
        Thread t1=new Thread(saleTickets,"window1");
        Thread t2=new Thread(saleTickets,"window2");
        t1.start();
        t2.start();
    }
}


class SaleTickets implements  Runnable{

    //总票数
    private int totalTicket=100;
    //剩余票数
    private int surplusTicket=100;

    @Override
    public void run() {

        //剩余票数大于0,卖票
        while (surplusTicket>0){
            sale();
        }
    }

    public void sale(){
        System.out.println(Thread.currentThread().getName()+"正在在卖第:"+(totalTicket-surplusTicket+1)+"张票");
        surplusTicket--;
    }
}

运行结果:
这里写图片描述

问题:两个窗口同时在卖第一张票,如果在run方法中加入sleep方法可能会出现,有人在卖第101张票。

4.解决线程安全问题的方法.

    *使用同步,synchronized或者lock。
    *加synchronized的地方:可能会发生线程安全的代码块
    *使用同步的前提:
1.至少有两个或者以上的线程,(单线程加锁会增加资源的消费)。
2.线程之间使用的锁是同一把锁。
public class Thread02 {
    public static void main(String[] args) {
        SaleTickets2 saleTickets2=new SaleTickets2();
        Thread t1=new Thread(saleTickets2,"window1");
        Thread t2=new Thread(saleTickets2,"window2");
        t1.start();
        t2.start();
    }
}


class SaleTickets2 implements  Runnable{

    //private Object obj=new Object();
    private String a="123";
    //总票数
    private int totalTicket=100;
    //剩余票数
    private int surplusTicket=100;
    @Override
    public void run() {
        while (surplusTicket>0){
            try {
                Thread.sleep(50);
            }catch (Exception e){
            }
            //剩余票数大于0,卖票 //剩余票数大于0,卖票
            sale();
        }
        }
    public void sale(){
        //加同步
        if(surplusTicket>0){
            synchronized (a) {//这把锁可以是对象,也可以是字符串、this等其他类型
                System.out.println(Thread.currentThread().getName() + "正在在卖第:" + (totalTicket - surplusTicket + 1) + "张票");
                surplusTicket--;
            }
        }
    }
}

运行结果:
这里写图片描述

5.同步函数

*对方法使用synchronized关键字进行修饰。
*同步函数使用的是this锁。因此一个线程使在同步代码块使用this锁,一个线程使用同步函数它们同样能达到同步的目的(因为它们用的都是this锁)。

6.静态同步函数

使用static和synchronized修饰方法

它使用的锁是当前字节码文件(如:Student.class),如 public static synchronized void sale(){//---}
因此一个线程使用同步函数,另一个线程使用静态同步函数并不能实现同步。

字节码文件锁:

public void run(){
    synchronized(Student.class){
        //---
}
}

7.多线程死锁

1。两个线程之间相互等待对方的锁而互不释放,导致线程无法推进而产生死锁。
2。死锁产生的原因:1.资源竞争;2.线程间推进顺序非法
3。产生死锁的必要条件:
产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。
互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。
4。避免死锁的方法
1.加锁顺序:确保所有的线程按照一定的顺序获得锁;
2.加锁时限:在尝试获得锁的时候加一个时限,超过这个时间就放弃获取锁,去干其它的事情,过一段时间再来;
3.死锁检测(比较实在)

8.java内存模型

多线程的3大特性:原子性,可见性,有序性
原子性:一个线程或者多个线程要么都执行要么都不执行
可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。(java内存模型)
有序性:程序执行的顺序按照代码的先后顺序执行,它可以通过join wait notify实现,与多线程通讯有关。
java啊内存模型主要分为:主内存和本地线程内存。主内存用于存放全局共享变量,本地线程内存主要用于存放线程的本地变量。
这里写图片描述
当t1操作count的时候它会把count复制一份到本地私有内存,操作完后再更新到主内存。
但是如果当t1操作count的时候,t2也复制一份到它本地内存操作(比如都是+1操作)就会造成,即使t1和t2都进行了+1操作,但是主内存结果还是1,就会导致线程安全问题。
解决办法:当t1操作完后通知t2重新获取count

9.volatile

保证变量在多个线程之间的可见性。

扫描二维码关注公众号,回复: 1014206 查看本文章

10.threadlocal


为每一个线程提供一个局部变量,比如上图中的t1和t2都有一个相同的变量,但是他们操作的时候互不影响
原理:它把局部变量set进去的时候使用的是Map

package com.xie.threadStudy;

/**
 * Created by Administrator on 2018/4/6.
 */
public class ThreadLocalDemon implements Runnable{

    public int count =0;
    public ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() { //要这个重写方法,否则会报空异常
            //return super.initialValue();
            return count;
        }
    };


    public  int getNumber(){
        count=threadLocal.get()+1;
        threadLocal.set(count);
        return count;
    }
    @Override
    public void run() {
        for(int i=0;i<3;i++){
            System.out.println(Thread.currentThread()+":"+getNumber());
        }
    }

    public static void main(String[] args) {
        ThreadLocalDemon threadLocalDemon=new ThreadLocalDemon();

        Thread t1=new Thread(threadLocalDemon);
        Thread t2=new Thread(threadLocalDemon);
        Thread t3=new Thread(threadLocalDemon);
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:
运行结果

ThreadLocal总结
1.使用ThreadLocal可以在每个线程中保存同一个变量的副本,值相同,但是各线程操作的是自己线程中所保存的副本,不会互相影响,不同于同步操作思路解决了多线程并发问题。
2.ThreadLocal类将自己的引用作为Key值,存储对象作为Value,存储在每个线程所维护的Map映射表中,这样设计可以使Map与线程绑定,当线程被销毁后,所维护的Map映射表也会被销毁。
3.在创建ThreadLocal建议重写initialValue()方法,因为该方法默认返回null值,存在NPE隐患。
4.由于ThreadLocal提供了线程内部的局部变量,因此该变量生命周期与线程相同,当线程销毁时该局部变量也会销毁。
5.因为ThreadLocal是在每个线程中都存储一个对象的副本,因此对内存的消耗相比不使用ThreadLocal更大。
ThreadLocal也解决了变量传递问题,在整个线程内ThreadLocal所映射的对象全局共享,降低了编码时的复杂度。

猜你喜欢

转载自blog.csdn.net/wu_0916/article/details/79841805