线程-02

1. 线程简介
在操作系统中执行某一个功能,如听歌,看电影,那么就必须在操作系统中执行相应的软件。
软件本身是由某种编程语言而编写的指令的集合,所以执行软件,就等同于执行软件中的程序语言。
当操作系统执行这个软件时,会分配一定的内存空间并进行内存调度来运行程序,软件关闭时,操作系统会回收之前分配的内存。我们将软件执行的这个过程,称之为 进程
所以简单来讲,启动软件,就等同于启动了一个进程。
启动软件后,软件可能需要执行数据库交互,缓存数据存储或展示用户页面等操作,这些操作是同时执行,而不是一个执行完了后另外一个执行。每一个操作就等同于在当前软件进程中创建了一个执行路径,执行时,软件会按照这个路径进行操作,我们将这样的执行路径称之为 线程
软件执行时,会将操作系统分配的内存再次进行分配,分配给不同的线程,某一时刻只有一个线程有CPU的执行权,其他线程可能在等待(就绪),或者被阻塞,因为CPU非常快,所以给这线程的分配的时间非常短,这个时间一到,立马会把当前的线程切换一个状态,另一个等待的线程抢到CPU的执行权。
有的进程中只有一个线程,所以我们有时也将线程称之为 轻量级进程 (Lightweight Process,LWP)
JAVA执行程序中,会自动创建主线程(main),在主线程中调用其它的业务逻辑代码。
线程的使用,主要是为了抢占资源,所以开发程序时,应该用面向对象的方式考虑资源和线程的区别。编写相应的代码,根据实际的场景选择是否使用同步处理。
2. JUC 简介
从Java 5.0开始,java 提供了java.util.concurrent(简称JUC )包,在此包中增加了在 并发编程 中很常用的 实用工具类 ,用于定义类似于线程的自定义子系统,包括 线程池 、异步IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的Collection 实现等
3. 线程状态

4. 创建线程
继承父类Thread
实现接口Runnable
匿名内部类
Lambda表达式(jdk1.8)
实现接口Callable
线程池Executors的工具类
 
5. 线程常用方法
类&接口
方法
作用
Java.lang.Thread
start
启动线程,让线程处于就绪状态
Java.lang.Thread
stop
停止线程,方法存在问题,所以不推荐使用
Java.lang.Thread
sleep
休眠线程, 线程的静态方法
休眠时,不释放对象监听器(锁)
Java.lang.Thread
join
将指定的线程加入到当前线程中,只有当指定的线程运行完毕后,当前的线程才能继续执行。
Java.lang.Thread
yield
放弃当前线程的运行,让其他相同优先级的线程执行,但是方法本身存在问题,所以不推荐使用。
Java.lang.Object
wait
线程等待
线程在等待时,会释放对象监听器(锁)
Java.lang.Object
notify
通知
Java.lang.Object
notifyAll
通知其他所有线程
 
synchronized
同步关键字
 
6. 线程很难
内存可见性
1)     每一个线程都有自己独立的工作内存,执行数据访问时,都会从主存中复制数据,然后快速访问,操作完毕后,将数据放回到主存中。但是工作内存之间无法直接交互,我们管这样的现象称之为内存可见性问题。
 
2)     如果增加同步关键字,那么内存操作中,会首先将工作内存清空,重新获取主存的数据然后再加锁。如果释放锁,会将工作内存的数据同步到主存中。
 
3)     Volatile 关键字可以修饰变量,那么在访问这个变量时,都会从主存中获取。同时也不会使用JIT即时编译器优化字节码。
对象监听器 monitor
1)    每一个对象都有自己的监听器,当执行同步操作时,会给当前对象增加监听器,就是所谓的给对象加锁。
 
2)    Wait,notify,notifyAll方法应该和对象监听器(锁)发生关联,如果没有关联,会出现错误: java.lang.IllegalMonitorStateException
虚假唤醒 spurious wakeup
当线程经过等待后唤醒,不再判断条件,这样就会导致数据安全问题,这个问题,称之为虚假唤醒。
解决方案就是在wait之前的判断时进行条件的循环判断处理。
线程八锁( 同步关键字的八种使用场景 )
1.     一个对象,两个同步方法,打印的顺序
两个线程执行不同的方法,哪一个先打印,取决于线程调度机制
2.     一个对象,两个同步方法,在一个方法中睡眠4秒,打印的顺序
当调用同步方法时,等同于给当前的对象加锁,其他线程运行时,会判断是否对象有锁,如果有锁的话,那么线程需要等待
3.     一个对象,一个同步方法,一个非同步方法,打印顺序
当调用非同步方法时,线程不需要等待,不需要判断对象是否锁定。继续执行业务逻辑
4.     两个对象,两个同步方法,打印的顺序
同步关键字是对对象加锁,不同对象,加锁是不一样,线程之间不需要等待。
5.     一个对象,两个静态同步方法,打印的顺序
静态同步方法加锁,等同于给 方法区内存对象 加锁(Class),静态方法的监听器是方法区的,而非静态的方法监听器是堆,那个先执行要看谁先抢占资源
6.     两个对象,两个静态同步方法,打印的顺序
两个对象(类型不相同)调用静态同步方法时,会给方法区不同的内存对象加锁,所以多个线程不需要等待。
两个对象(类型相同)调用静态方法,等同于给方法区相同的内存对象加锁,所以多个线程需要等待
7.     一个对象,一个静态同步方法,一个成员同步方法,打印顺序
静态同步方法是获取方法区中内存对象的锁,而成员同步方法获取的是堆内存中对象的锁,锁不是同一个,所以多个线程不需要等待
8.     两个对象,一个静态同步方法,一个成员同步方法,打印顺序
如果两个对象的类型不一样,效果等同于7.
如果两个对象的类型一样,效果依然等同于7
7. 线程高级(JUC)
Callable
问:和Runnable接口的区别?
1)    Callable是juc中提供的接口,在jdk1.8后,和Runnable接口都是函数式接口。
2)    Runnable接口中的方法没有返回值并且不会抛出异常,Callable接口中的方法有返回值且会抛出异常,可以进行统一异常处理,Callable接口支持泛型操作
3)    Callable接口无法通过Thread类直接访问运行,必须通过FutureTask类进行转换后在线程中运行
4)    Callable接口中方法的返回值可以通过FutureTask对象的get方法获取
5)    FutureTask对象的get方法会阻塞当前线程的执行,然后计算Callable接口方法的逻辑处理结果,只有结果返回了,当前的线程会继续执行。
6)    Callable接口可以用于实现线程闭锁操作,也可以使用 CountDownLatch 类实现
问:如何关联两个无关系的对象?(下面是我自己写的)
他们是通过FutureTask这个类来关联Runnable和Callable,因为FutureTask实现了RunnableFuture的接口,而RunnableFuture继承了Runnable的接口,而在FutureTask里面有两个构造器,一个是传一个Callable的对象,还有一个是传一个Runnable的对象,这样就可以成功的把Callable转换成Runnable的,Callable 县城启动也是通过Thread的start的方法来实现的,new Thread(new FutureTask(new Callable<Integer>())).start();
Lock
问:和synchronized的区别?
1)    Synchronized是java中关键字,给对象加锁,所以系统级别的加锁,用户控制不了的。Lock是juc中提供的接口,给对象加锁,由于是用户自己创建出来的锁,所以可以自己控制
2)    Synchronized加锁后,必须获取监听器才可以执行,否则需要等待,无法中断,但是lock可以加锁等待时中断
3)    Synchronized释放锁的顺序,先加锁的操作后释放锁,lock接口中的释放锁没有顺寻操作,可以自定义
4)    Lock在使用时,不能同时使用wait,notify。NotifyAll方法,否则会出现错误
Condition: 增加条件操作,用于替代早期的同步处理中使用的wait,notify方法
     Await è wait
     signal è notify
     signalAll è notifyAll
TimeUnit : 时间单元,可以将时间控制的更加准确,容易识别
Volatile : 可以增加对象的内存可见性 可以不让JIT即时编译器优化字节码,但是无法解决数据的原子性问题
AtomicInteger : 原子类
CAS算法:CompareAndSwap算法
ABA问题:AèBèA
 
8. 线程应用
线程接力
案例:第一个线程打印5次,第二个线程打印10次,第三个线程打印15次,第三个线程执行完毕后,再从第一个线程继续打印,执行第2轮的操作,总共执行10轮
package com.atguigu;
 
import java.util.MissingFormatArgumentException ;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
/**
*
 */
class Loop{
 
  private int flg =1;
  private Lock lock = new ReentrantLock();
  private Condition c1 = lock .newCondition();
  private Condition c2 = lock .newCondition();
  private Condition c3 = lock .newCondition();
  public void print1(){
    lock .lock();
    try {
      while ( flg !=1){
         try {
           c1 .await();
         } catch (InterruptedException e ) {
           // TODO Auto-generated catch block
           e .printStackTrace();
         }
      }
   
      c2 .signal();
      for ( int i = 1; i <=5; i ++) {
        System. out .println(Thread. currentThread ().getName()+ i );
      }
      flg =2;
     
    } finally {
      lock .unlock();
    }
 
  }
  public void print2(){
    lock .lock();
    try {
      while ( flg !=2){
         try {
           c2 .await();
         } catch (InterruptedException e ) {
           // TODO Auto-generated catch block
           e .printStackTrace();
         }
      }
   
      c3 .signal();
      for ( int i = 1; i <=10; i ++) {
        System. out .println(Thread. currentThread ().getName()+ i );
      }
      flg =3;
     
    } finally {
      lock .unlock();
    }
 
  }
  public void print3(){
    lock .lock();
    try {
      while ( flg !=3){
         try {
           c3 .await();
         } catch (InterruptedException e ) {
           // TODO Auto-generated catch block
           e .printStackTrace();
         }
      }
     
      c1 .signal();
      for ( int i = 1; i <=15; i ++) {
        System. out .println(Thread. currentThread ().getName()+ i );
      }
      flg =1;
     
    } finally {
      lock .unlock();
    }
   
  }
}
public class TestJUC_loop_1 {
 
  public static void main(String[] args ) {
   
    final Loop l = new Loop();
    Thread t1 = new Thread( new Runnable(){
      public void run() {
         for ( int i = 1; i <=10; i ++) {
           l .print1();
         }
        
      }
     
    }, "第一个线程" );
    t1 .start();
    Thread t2 = new Thread( new Runnable(){
      public void run() {
         for ( int i = 1; i <=10; i ++) {
           l .print2();
         }
        
      }
     
    }, "第二个线程" );
    t2 .start();
 
    Thread t3 = new Thread( new Runnable(){
      public void run() {
         for ( int i = 1; i <=10; i ++) {
           l .print3();
         }
        
      }
     
    }, "第三个线程" );
    t3 .start();
 
 
  }
 
}
 
读写锁 (ReadWriteLock)
案例:(红蜘蛛屏幕共享软件)100个线程读取,1个线程写入
案例:多个线程访问缓存,如果缓存中没有,读取数据库,如果有,直接获取。
线程池 ( Executors )
案例:固定线程池打印20次业务操作
案例:单一线程池打印20次业务操作
案例:按需线程池打印20次业务操作
案例:让线程池支持时间调度
9. 线程面试题
线程有几种状态?之间是如何切换的?
有五种状态:new,就绪,运行,消亡,阻塞
New:Thread t=new Thread(new Runnable(){
Public void run(){
}
});
New-就绪:t.start();
就绪-运行:抢占cpu,运行
运行-消亡:有异常、线程执行完成、error错误
运行-阻塞:join插入,sleep,wait(synchronized),
阻塞-就绪:join的线程执行完成,sleep睡觉完成、wait被notify或notifyAll唤醒
运行-就绪:礼让,cpu被抢占,
Java中的volatile关键字是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?
Volatile他使数据为可见数据,他是从主存里面读取数据,保持了数据的可见性,而且JIT(just in time)即时编译器不进行优化字节文件(int i=10;i=20;i=30),但是他不能保证原子性(原子性就是要么都执行,要么都不执行,例如i++);他不是安全的
Volatile是修饰变量的
Synchronized是一个关键字,他可以修饰方法和在同步代码块上,
Synchronized也有数据可见性,他是先把线程私有的内存清空后,在主存里面拿数据,在加一把锁,当线程操作完数据后放入到主存里面后,释放锁的机制,来达到数据的可见性,在这个操作过程中,也会阻塞其他线程操作同样的数据,会影响一些性能,在这方面volatile要好一些,但是这样也保持了原子性操作(其实加锁来实现原子性的);他是安全的
 
Runnable接口和Callable接口的区别
Runnable他只有一个run方法,是@FunctionalInterface修饰的,
Callable他也只有一个方法call,也是@FunctionalInterface注解的
他们都是通过Thread的start方法启动的
他们两个的区别是Callable他有泛型,有返回值,也可以抛出异常
Callable<Integer> c=newCallable<Integer>();
FutureTask<Integer> task=new FutureTask<Integer>(c);
Thraed t=new Thread(task).start();//只用这样才能启动Callable的线程
Thread t=new Thread(new Runnable(){
Public void run(){
})
start()方法和run()方法的区别
start()是一个线程的方法,他把new出来的线程启动起来,使线程达到就绪状态,让他有抢占cpu的机会,使他有调度机制调度的机会
而run方法只是一个简单的方法,跟线程已经没有什么关系了
sleep方法和wait方法有什么区别
sleep是个静态方法,他是Thread,public static native void sleep(时间);
一个在睡眠时使不会释放锁的,其他线程要访问同一资源时,要等待他执行完成以后才可以
Sleep没有使用情景的要求,Thread.sleep(100);他只要休眠够就可以了(),他和对象没有关系
Wait是一个成员方法,public final void wait();线程在等待的时候,该线程会释放锁的,他只能等notify或者notifyAll来唤醒他,不然该线程会一直等下去,而不会执行(其实等待时间长了,线程也会出现超时,使线程死亡);一般情况下wait和notify、notifyAll、Synchronized一起使用,wait有使用条件的,wait要使用在同步方法后者同步代码块里面
ThreadLocal有什么用
他的主要作用就是共享数据,在同一个线程里面,任何对象都可以存数据,也可以取数据,但是他不能解决安全性问题(安全性问题是数据在并发操作是造成的)
synchronized和ReentrantLock的区别
synchronized是关键字,他在加锁后是不能被打断的,因为他是系统加的锁机制(jvm),用户是控制不了。而ReetrantLock是Lock接口的实现类,这个类是可以在加锁后,是可以打断的,是受自己控制的,比较灵活,
而ReetrantLock是不能和wait、notify、notifyAll这些搭配使用的,他一般的用Codition这额条件类的方法的await来替代Object类的wait,用signal来替代notify的,signalAll来替代notifyAll, Lock lock=new ReetrantLock();
Condition c=lock.newCondition();
这个ReetrantLock是一定要自己关闭的,而synchronized是不要我们自己去关闭的,
Synchronized释放锁的顺序是先加的后释放,而ReetrantLock释放锁比较灵活,可以自己定义
 
 
ConcurrentHashMap的并发度是什么
ConcurrentHashMap是一个线程安全的,他采取的机制是加了分段锁。在put数据时,Segement<k,v> s;
Static final class Segement<k,v> extends ReentrantLock;
为什么ConcurrentHashMap的并发度是16,是因为他默认的segement的容量为16个,
相当于把ConcurrentHashMap分成了16段,每一段的访问都不需要竞争,所以没有并发的操作,不会造成线程等待,大大提高了效率,也解决了安全性问题
并发度是16
编写Java代码,解决生产者——消费者问题。
package com.atguigu;
 
class Product{
  private int num =0;
  public synchronized void produce(){
    while ( num !=0){
      try {
         wait();
      } catch (InterruptedException e ) {
         e .printStackTrace();
      }
    }
    num ++;
    notifyAll();
    System. out .println( "生产了" + num );
  }
  public synchronized void consume(){
    while ( num ==0){
      try {
         wait();
      } catch (InterruptedException e ) {
         e .printStackTrace();
      }
    }
    num --;
    notifyAll();
    System. out .println( "消费了" + num );
  }
}
public class Consume_product {
  public static void main(String[] args ) {
   
    Product p = new Product();
    new Thread( new Runnable(){
 
      @Override
      public void run() {
         for ( int i = 0; i < 10; i ++) {
           p .produce();
         }
        
      }
     
    }).start();
   
    new Thread( new Runnable(){
 
      @Override
      public void run() {
         for ( int i = 0; i < 10; i ++) {
           p .consume();
         }
      }
     
    }).start();
  }
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值