——-初学整理,有点乱
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
保证变量在多个线程之间的可见性。
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所映射的对象全局共享,降低了编码时的复杂度。