3. Java 多线程基础课堂笔记

1.线程是独立的执行路径,多线程就是指多条路径

2.线程一旦开启后是由操作系统的调度器安排调度的

3.创建线程:

(1)继承Thread类,重写run方法,.start

(2)实现runnable接口,重写run方法,new Thread,调用start

(3)实现Callable接口,重写call方法

4. Lambda:

如果是一个参数可以省略括号(两个参数以上不可以省略,但都可以省略参数类型),如果方法里只是一条语句,则可以省略花括号(两条以上不可以),如果只有一句return返回值,则可以省略return关键字,例如:

love = a-> System.out.println("I like Labmda1  --->" + a);
interest = (a,c) -> a+c;

5. 五大状态:

(1)新生状态:线程对象一旦创建就进入到了新生状态

(2)就绪状态:当调用start()方法,线程立即进入就绪状态,但是不意味着立即调度执行,runnable

         a. 进入就绪状态的四种情况:

            a1.  start()

            a2.  阻塞解除

            a3.  yield(避免一个线程占用cpu太多,中断),让当前正在执行的线程暂停,转为就绪

                   让cpu调度器重新调度

            a4.  jvm(jvm本身将本地线程切换到其他线程)

(3)运行状态:从就绪状态被cpu调用后进入运行状态,runnable

(4)阻塞状态:

          a. 进入阻塞状态的四种情况:

            a1.  sleep(),抱着资源不给别人用,停止运行一段时间,TIME_WAITING

                   sleep指定当前线程阻塞的毫秒数

                   sleep存在异常InterruptedException

                    sleep可以模拟网络延时或者倒计时

            a2.  wait(),不占用资源  BLOCKED

            a3.  join,插队,合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞,TIME_WAITING

            a4.   有些操作会进入阻塞,比如IO的read,Write  BLOCKED

(5)死亡状态:代码执行完毕或者停止了线程,不推荐stop()/destory(),不安全,TERMINATE

          线程停止方法:提供一个boolean类型的终止变量,当这个变量为False时,终止线程运行。

//1. 加入标识,标记线程体是否可以运行
private boolean flag = true;
//2. 关联标识,true--运行 false--停止
while (flag){
    System.out.println(name+"-->"+(i++));
}
//3. 对外提供方法改变标识
public void terminate(){
    this.flag = false;
}
//4. 线程终止
tt.terminate(); 

6. 优先级 Priority

   * 1. NORM_PRIORITY 5 - 默认

   * 2. MIN_PRIORITY 1

   * 3. MAX_PRIORITY 10

   * 不代表绝对的优先级顺序,只是获得调度的概率大小问题

   * 概率高的可能会先执行,概率低的不代表不执行

//设置优先级要在启动之前
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(10);
t3.setPriority(Thread.MIN_PRIORITY);
t4.setPriority(1);

t1.start();
t2.start();
t3.start();
t4.start();

7. 线程分为用户线程和守护线程,JVM必须确保用户线程执行完毕才会停止,而不用等待守护线程执行完毕。默认都是用户线程,可以使用 .setDaemon(true); //将用户线程调整为守护线程,这样,jvm在执行完用户线程后就停止运行了,而不管守护线程是否运行完毕。

8.线程同步:形成等待池队列,形成锁机制(synchronized)

(1)同步方法
          public d synchronized void method(int args) {}

           若将一个大的方法声明为synchronized 将会大大影响效率

(2)同步块:

           synchronized (obj){ },obj称之为同步监视器
           • obj可以是任何对象,但是推荐使用共享资源作为同步监视器
           • 同步方法中无需指定同步监视器,因为同步方法的同步监视器是this即该对象本身,或                    class即类的模子
           • 同步监视器的执行过程:
                    • 第一个线程访问,锁定同步监视器,执行其中代码
                    • 第二个线程访问,发现同步监视器被锁定,无法访问
                    • 第一个线程访问完毕,解锁同步监视器
                    • 第二个线程访问,发现同步监视器未锁,锁定并访问

            • 尽可能锁定合理的范围(不是指代码,而是指数据的完整性)。synchronized锁定的对象应                该不变化的。

 //尽可能锁定合理的范围(不是指代码,而是指数据的完整性)
    //double checking
    public void test5() {
        if (ticketNums <= 0) {  //考虑的是没有票的情况
            flag = false;
            return;
        }
        synchronized (this) {
            if (ticketNums <= 0) {  //考虑的是最后一张票的情况
                flag = false;
                return;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
        }
    }

9. 死锁:多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。

               某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

     避免死锁发生: * 不要在同一个代码块中,同时持有多个对象的锁

10. 线程协作(生产者消费者模式)

       (1)在生产者消费者问题中,仅有synchronized是不够的
            • synchronized可阻止并发更新同一个共享资源,实现了同步
            • synchronized不能用来实现不同线程之间的消息传递(通信)

       (2)线程通信
                解决方式1:  管程法-----借助缓冲区

                解决方式2:  信号灯法------借助标志位

       (3)Java提供了3个方法解决线程之间的通信问题(均是java.lang.Object类的方法都只能在同步方法或者同步代码块中使用,否则会抛出异常)
                         

11.知识扩展

(1)任务定时调度:通过Timer和Timetask,我们可以实现定时启动某个线程。

      Timer timer = new Timer();
      //执行安排
      timer.schedule(new MyTask(),1000);  //执行任务一次
      timer.schedule(new MyTask(),1000,200);  //1秒后每个200ms执行一次

(2)QUARTZ:

       //1.创建Schedule的工厂
      SchedulerFactory sf = new StdSchedulerFactory();
      //2.从工厂中获取调度器
      Scheduler sched = sf.getScheduler();
      //3.创建JobDetail
      JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
      //时间
      Date runTime = evenSecondDateAfterNow(); //下一秒
      // 4.触发器
      //Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build(); //下一秒执行一次
        Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime)
          .withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(4)).build(); //下一秒执行,然后每5秒执行一次,共执行4次
      // 5. 注册任务和触发条件
      sched.scheduleJob(job, trigger);
      //6. 启动
      sched.start();
      try {
        //100秒后停止
        Thread.sleep(50L * 1000L);
      } catch (Exception e) {
      }
      sched.shutdown(true);
    }
     //HelloJob 
     public class HelloJob implements Job {
     private static Logger _log = LoggerFactory.getLogger(HelloJob.class);
     public HelloJob() {
      }
     public void execute(JobExecutionContext context)
        throws JobExecutionException {
        // Say Hello to the World and display the date/time
        //_log.info("Hello World! - " + new Date());
        System.out.println("-------------Start--------------");
        System.out.println("Hello World! - " + new Date());
        System.out.println("-------------End--------------");
    }
}

(3)HappenBefore(指令重排):代码执行顺序与预期不一致 , 如果数据和数据之间没有依赖,有可能会出现指令重排 ;目的:提高性能

(4)volatile:用于保证数据的同步也就是保证线程间变量的可见性,线程对变量进行修改之后,要立刻回写到主内存。

(5)单例模式(DCL模式):懒汉式套路 在多线程环境下,对外存在一个对象

          * 1. 构造器私有化 -- 避免外部new构造器

          * 2. 提供私有的静态属性---存储对象的地址

          * 3. 提供公共的静态方法-- 获取属性

public class DoubleCheckedLocking {
    public static void main(String[] args) {
        //如果加了同步,对象的地址相同
        //     Thread t = new Thread(()->{
        //         System.out.println(DoubleCheckedLocking.getInstance());
        //     });
        //     t.start();
        //  System.out.println(DoubleCheckedLocking.getInstance());
        //}

        //如果不加锁,对象的地址有可能不相同
        Thread t = new Thread(() -> {
            System.out.println(DoubleCheckedLocking.getInstance1(500));
        });
        t.start();
        System.out.println(DoubleCheckedLocking.getInstance1(1000));
    }


    // 2. 提供私有的静态属性---存储对象的地址
    //避免重排,等对象创建好了再用,
    // 如果没有volitile,有可能其他线程会访问到一个还没有进行初始化的对象
    private static volatile DoubleCheckedLocking instance;

    // 1. 构造器私有化  -- 避免外部new构造器
    private DoubleCheckedLocking() {

    }

    // 3. 提供公共的静态方法-- 获取属性
    public static DoubleCheckedLocking getInstance() {
        //再次检测
        if (null != instance) {
            return instance;  //避免不必要的同步,已经存在对象
        }
        synchronized (DoubleCheckedLocking.class) {  //模子,class对象
            if (null == instance) {
                instance = new DoubleCheckedLocking();
                //new 一个对象时,三步:
                //1.开辟空间
                //2.初始化对象信息
                //3.返回对象的地址给引用
                //所以在此期间有可能发生指令重排,所以要加volatile
            }
            return instance;
        }

    }

    public static DoubleCheckedLocking getInstance1(long time) {

        if (null == instance) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new DoubleCheckedLocking();
            //new 一个对象时,三步:
            //1.开辟空间
            //2.初始化对象信息
            //3.返回对象的地址给引用
            //所以在此期间有可能发生指令重排,所以要加volatile
        }
        return instance;
    }

}

(6)ThreadLocal: 每个线程自身的存储本地、局部区域,

         (1)建议ThreadLocal定义为private static

         (2)常用的方法:get/set/initialValue

public class ThreadLocalTest2 {

    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);

    public static void main(String[] args){
        for (int i =0;i<5;i++){
            new Thread(new MyRun()).start();  //每个线程都会得到1,剩下0.彼此各不影响
        }
    }
    public static class MyRun implements Runnable{
        @Override
        public void run() {
            Integer left = threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"得到了-->"+left);
            threadLocal.set(left-1);
            left = threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"还剩下-->"+left);
        }
    }
}

 (3)ThreadLocal 分析上下文环境 :    重点看起点

           * 1. 构造器 : 哪里调用,就属于哪里 找线程体

           * 2. run方法:本线程自身

public class ThreadLocalTest3 {

    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);

    public static void main(String[] args) {
        new Thread(new MyRun()).start();  //main
        new Thread(new MyRun()).start();
    }
    public static class MyRun implements Runnable{

        public MyRun(){
            threadLocal.set(-100);
            System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get()); //main
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get()); //另外的线程
        }
    }
}

运行结果:
main-->-100
main-->-100
Thread-0-->1
Thread-1-->1

(4)InheritableThreadLocal:继承上下文环境的数据 找起点,拷贝一份给子线程,子线程可以进行设置

public class ThreadLocalTest4 {

   //private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();  //mian为2,另一个线程为0
    private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();//mian为2,如果不设置值另一个线程也为2

    public static void main(String[] args) {
        threadLocal.set(2);
        System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());

        //此线程由main开辟
        new Thread(()->{
            threadLocal.set(200);
            System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());  //子线程设置后为200
        }).start();
    }
}

(5)ReentrantLock(可重入锁) : 锁可以延续使用+每个锁里边有一个计数器

public class LockTest4 {

    ReentrantLock lock = new ReentrantLock();

    public void a() throws InterruptedException {
        lock.lock();
        System.out.println(lock.getHoldCount());
        doSomeThing();
        lock.unlock();
        System.out.println(lock.getHoldCount());
    }

    //不可重入锁
    public void doSomeThing() throws InterruptedException {
        lock.lock();
        System.out.println(lock.getHoldCount());
        //........
        lock.unlock();
        System.out.println(lock.getHoldCount());
    }

    public static void main(String[] args) throws InterruptedException {

        LockTest3 test = new LockTest3();
        test.a(); //使用了锁没有释放
        Thread.sleep(1000);

        System.out.println(test.lock.getHoldCount());
    }
}

(6)CAS:Compare and Swap 比较并交换:

         (1)乐观锁的实现;
                  • 有三个值:一个当前内存值V、旧的预期值A、将更新的值B。先获取到内存当中当前的内存值V,再将内存值V和原值A作比较,要是相等就修改为要修改的值B并返                        回  true,否则什么都不做,并返回false;
         (2)CAS是一组原子操作,不会被外部打断;
         (3)属于硬件级别的操作(利用CPU的CAS指令,同时借助JNI来完成的非阻塞算法),效率比加锁操作高。

public class CASTest {
    //库存原子型操作
    private static AtomicInteger stock = new AtomicInteger(5);
    public static void main(String[] args){
           for (int i =0;i<5;i++){
               new Thread(()->{
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   Integer left = stock.decrementAndGet();  //相减同时获取:CAS思想
                   if (left<1){
                       System.out.println("抢完了");
                       return;
                   }
                   System.out.print(Thread.currentThread().getName()+"抢了1件");
                   System.out.println("-->还剩"+left+"件");
               }).start();
           }
      }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值