首页 > 其他分享 >多线程复习总结

多线程复习总结

时间:2024-08-11 16:06:18浏览次数:8  
标签:总结 复习 Thread void 线程 println new 多线程 public

 

1基本概念

1什么是进程什么是线程

进程:是程序执行一次的过程,他是动态的概念,是资源分配的基本单位。一个应用程序(1个进程是一个软件)。

线程:一个进程可以有多个线程,线程是cpu调度的单位,一个进程中的执行场景/执行单元。

对于java程序来说,当在DOS命令窗口中输入:
java HelloWorld 回车之后。会先启动JVM,而JVM就是一个进程。

JVM再启动一个主线程调用main方法(main方法就是主线程)。
同时再启动一个垃圾回收线程负责看护,回收垃圾。

 

2、进程和线程是什么关系?

进程:可以看做是现实生活当中的公司。

线程:可以看做是公司当中的某个员工。

进程和进程

进程跟进程资源不是共享的,内存独立不共享

线程和线程:

线程与线程的共享资源是“堆,方法区” 每个线程都有自己独立的“

假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

 3为什么要学习多线程

java中之所以有多线程机制,目的就是为了 提高程序的处理效率

4思考一个问题

使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束?
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。

5分析一个问题对于单核的CPU来说,真的可以做到真正的多线程并发吗?

对于多核的CPU电脑来说,真正的多线程并发是没问题的。4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。

单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。

对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给别人的感觉是:多个事情同时在做!!!

eg.
线程A:播放音乐

线程B:运行魔兽游戏

线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,
给我们的感觉是同时并发的。(因为计算机的速度很快,我们人的眼睛很慢,所以才会感觉是多线程!)

6什么是真正的多线程并发?

t1线程执行t1的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。

7关于线程对象的生命周期?★★★★★

  1. 新建状态
  2. 就绪状态
  3. 运行状态
  4. 阻塞状态
  5. 死亡状态

8线程构造方法

构造方法名备注
创建线程第一种方式  
Thread()  
Thread(String name) name为线程名字
创建线程第二种方式  
Thread(Runnable target)  
Thread(Runnable target, String name) name为线程名字

 

 

9java创建线程(Thread)的5种方式

1继承thread类 重写run方法

2实现runnable接口 重写run方法

3实现callable接口  重写call方法

4使用线程池

5使用匿名类

第一种方式:继承Thread类重写run方法

编写一个类,直接 继承 java.lang.Thread,重写 run方法

  1. 怎么创建线程对象? new继承线程的类。
  2. 怎么启动线程呢? 调用线程对象的 start() 方法。

 

public class Demo01 {
    public static void main(String[] args) {
        tese tese =new tese();
        tese.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("这个主线程的方法"+i);
        }
    }
}
class tese extends  Thread{

   public void run(){
       for (int i = 0; i < 100; i++) {
           System.out.println("这个副线程的方法"+i);
       }
   }
/*
* 运行结果1           运行结果2
这个主线程的方法0   这个副线程的方法0
这个副线程的方法0   这个副线程的方法1
这个主线程的方法1   这个主线程的方法1
这个副线程的方法1   这个主线程的方法2
这个主线程的方法2   这个主线程的方法3
这个副线程的方法2   这个主线程的方法4
* 两个线程单独运行
* */

 

注意:

t.run() 不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)

t.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。

调用run()方法内存图:                                                                                                    调用start()方法内存图:

第二种方式:实现Runnable接口

编写一个类,实现 java.lang.Runnable 接口,实现run方法

  1. 怎么创建线程对象? new线程类传入可运行的类/接口。
  2. 怎么启动线程呢? 调用线程对象的 start() 方法。

例子

 1 package 多线程.实现runnable接口重写run方法;
 2 //方法一写一个实现runnable的类
 3 public class Demo01 {
 4     public static void main(String[] args) {
 5       Thread t1 = new Thread(new Test() {});
 6         t1.start();
 7         for (int i = 0; i < 1000; i++) {
 8             System.out.println( "主线程"+i);
 9         }
10 
11     }
12 }
13 class Test implements Runnable {
14     @Override
15     public void run() {
16         for (int i = 0; i < 1000; i++) {
17             System.out.println( "分支线程"+i);
18         }
19     }
20 }

 注意:
第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。

第三种方式实现callable接口

实现Callable接口的方式创建线程的强大之处

  • call()可以有返回值的
  • call()可以抛出异常,被外面的操作捕获,获取异常的信息
  • Callable是支持泛型的
 1 package 多线程.实现callable接口重写call方法;
 2 
 3 import java.util.concurrent.Callable;
 4 import java.util.concurrent.ExecutionException;
 5 import java.util.concurrent.FutureTask;
 6 
 7 public class Demo0 {
 8   public static void main(String[] args) {
 9       Demo1 demo1 = new Demo1();
10       FutureTask<Integer> futureTask = new FutureTask<Integer>(demo1);
11       Thread thread = new Thread(futureTask);
12       thread.start();
13       int i =0 ;
14       try{
15            i = futureTask.get();
16           System.out.println("总和为:" + i);
17       } catch (ExecutionException e) {
18           throw new RuntimeException(e);
19       } catch (InterruptedException e) {
20           throw new RuntimeException(e);
21       }
22       for (int j = 0; j < 10; j++) {
23           System.out.println("j:" +j);
24           System.out.println("i:" +i);
25           int x =j+i;
26           System.out.println("主线程:"+x);
27       }
28   }
29 }
30 class Demo1 implements Callable {
31 
32     @Override
33     public Object call() throws Exception {
34         int x=0;
35         for (int i = 0; i < 100; i++) {
36             x++;
37         }
38         return x;
39     }
40 }

方法四:使用线程池

线程池好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理

核心参数:

  • corePoolSize:核心池的大小
  • maximumPoolSize:最大线程数
  • keepAliveTime:线程没有任务时最多保持多长时间后会终止

 

 1 package 多线程.使用线程池;
 2 
 3 
 4 import java.util.concurrent.Executors;
 5 import java.util.concurrent.Callable;
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.ThreadPoolExecutor;
 8 
 9 class Demo01 implements Runnable{
10     @Override
11     public void run() {
12      for (int i = 0; i < 10; i++) {
13          if (i % 2 != 0) {
14              System.out.println(  "实现runnable接口的线程 " + i);
15          }
16      }
17     }
18 }
19 class Demo02 implements Callable {
20 
21     @Override
22     public Object call() throws Exception {
23         int j=0;
24         for (int i = 0; i < 10; i++) {
25             if (i % 2 == 0) {
26                 System.out.println("实现Callable接口的线程" + ": " + i);
27             }
28             j=i++;
29         }
30         return j;
31     }
32 
33 }
34 public class ThreadPool {
35 
36     public static void main(String[] args) {
37         //1. 提供指定线程数量的线程池
38         ExecutorService service = Executors.newFixedThreadPool(10);
39 
40         //输出class java.util.concurrent.ThreadPoolExecutor
41         System.out.println(service.getClass());
42 
43         ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
44         //自定义线程池的属性
45 //        service1.setCorePoolSize(15);
46 //        service1.setKeepAliveTime();
47 
48         //2. 执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
49         service.execute(new Demo01());//适用于Runnable
50         service.submit(new Demo02());//适合使用于Callable
51 
52         //3. 关闭连接池
53         service.shutdown();
54     }
55 
56 }

5第五中方式匿名类

package 多线程.使用匿名类;
public class Demo01 {
    public static void main(String[] args) {
        //匿名类实现线程创建
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程开始启动....");
                for (int i = 0; i < 30; i++) {
                    System.out.println("子 i:" + i);
                }
            }
        });
        //start是启动线程
        t1.start();
        System.out.println("主线程开始启动....");
        for (int i = 0; i < 30; i++) {
            System.out.println("主 i:" + i);
        }
    }
}

 

 1 package 多线程.使用匿名类;
 2 
 3 public class Demo02 {
 4     public static void main(String[] args) {
 5         new Thread(() -> {
 6             System.out.println(Thread.currentThread().getName() + "\t上自习,进入教室");
 7             for (int i = 1; i < 100; i++) {
 8                 System.out.println(Thread.currentThread().getName() + "学习时间"+i+ "分钟");
 9             }
10             System.out.println(Thread.currentThread().getName() + "\t学习完成,离开教室");
11         }, "赵克明").start();
12         new Thread(() -> {
13             System.out.println(Thread.currentThread().getName() + "\t上自习,进入教室");
14             for (int i = 1; i < 50; i++) {
15                 System.out.println(Thread.currentThread().getName() + "学习时间"+i+ "分钟");
16             }
17             System.out.println(Thread.currentThread().getName() + "\t学习完成,离开教室");
18         },"1帅").start();
19     }
20 }

10、获取当前线程对象、获取线程对象名字、修改线程对象名字

 

方法名作用
static Thread currentThread() 获取当前线程对象
String getName() 获取线程对象名字
void setName(String name) 修改线程对象名字


例子

 1  Thread t1 = new Thread(new Runnable() {
 2             public void run() {
 3                System.out.println("测试线程");
 4             }
 5         });
 6         //获取当前线程对象
 7         Thread thread = t1.currentThread();
 8         System.out.println(thread);
 9         //或许这个线程名字
10         String name = thread.getName();
11         System.out.println(name);
12         //获取当前线程的名字
13         String name1 = t1.currentThread().getName();
14         System.out.println(name1);
15         //修改这个线程的名字
16         t1.setName("赵线程");
17         //运行线程
18         t1.start();

 11静态代理模式  

package 多线程.静态代理;
public class Demo01 {
    public static void main(String[] args) {
        //实例化对象我
//         new wo();
        //实例化对象婚庆公司
        hunQing HunQing = new hunQing(new wo());
        //婚庆公司举行仪式
        HunQing.yishi();
        //线程的底层实现原理就是静态代理
//        Demo03 demo03 = new Demo03();
//        new Thread(demo03,"兔子").start();
    }
}
//结婚
interface jieHun{
  public void yishi();
}
//我
class wo implements jieHun{
    @Override
    public void yishi() {
        System.out.println("赵克明要结婚了");
    }
}
//婚庆公司
class hunQing implements jieHun{
    private jieHun jieHun;
    public hunQing(jieHun jieHun) {
         this.jieHun = jieHun;
     }
    @Override
    public void yishi() {
      //布置场景
      x();
     this.jieHun.yishi();
        //收取尾款
        w();
    }
    private void w() {
        System.out.println("收取尾款");
    }
    private void x() {
        System.out.println("布置场景");
    }
}

线程的底层用的是这种静态代理

12Lamda表达式

 

 1 package 多线程.Lamda表达式;
 2 
 3 public class Demo01 {
 4     //静态内部类
 5     static class Deno04 implements Deno02 {
 6         @Override
 7         public void test() {
 8             System.out.println("test2");
 9         }
10 
11         public static void main(String[] args) {
12             //普通接口调用
13             Deno02 d = new Deno03();
14             d.test();
15             //静态内部类调用
16             Deno04 d2 = new Deno04();
17             d2.test();
18             //局部内部类
19             class Deno05 implements Deno02 {
20                 @Override
21                 public void test() {
22                     System.out.println("test3");
23                 }
24               }
25             //局部内部调用
26             Deno05 d3 = new Deno05();
27             d3.test();
28             //匿名内部类
29             Deno02 deno02 = new Deno02() {
30                 @Override
31                 public void test() {
32                     System.out.println("test4");
33                 }
34             };
35             //匿名内部类调用
36             deno02.test();
37 
38             //Lamda表达式
39             deno02  = ()-> {
40                  System.out.println("test5");
41             };
42             deno02.test();
43         }
44 }
45 //定义的接口 接口只有一个抽象方法的接口叫函数式接口
46 interface Deno02{
47     public void test();
48 }
49 //接口的实现
50 static class Deno03 implements Deno02{
51     @Override
52     public void test() {
53         System.out.println("test1");
54     }
55 }
56 }

lamda表达式简化了代码 这个使用必须在函数式接口中才能使用

总结
Lambda 表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。
优点:
代码简洁,开发迅速
方便函数式编程
非常容易进行并行计算
Java 引入 Lambda,改善了集合操作
缺点:
代码可读性变差
在非并行计算中,很多计算未必有传统的 for 性能要高
不容易进行调试

13线程的sleep方法

方法名作用
static void sleep(long millis) 让当前线程休眠millis秒

 

1静态方法:Thread.sleep(1000);
2参数是毫秒
3作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
Thread.sleep()方法,可以做到这种效果:
4间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。

 1 public class ThreadTest06 {
 2     public static void main(String[] args) {
 3         //每打印一个数字睡1s
 4         for(int i = 0; i < 10; i++){
 5             System.out.println(Thread.currentThread().getName() + "--->" + i);
 6 
 7             // 睡眠1秒
 8             try {
 9                 Thread.sleep(1000);
10             } catch (InterruptedException e) {
11                 e.printStackTrace();
12             }
13         }
14     }
15 }

14线程中断sleep()的方法

方法名作用
void interrupt() 终止线程的睡眠

 

 1 public class ThreadTest08 {
 2     public static void main(String[] args) {
 3         Thread t = new Thread(new MyRunnable2());
 4         t.setName("t");
 5         t.start();
 6 
 7         // 希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了。)
 8         try {
 9             Thread.sleep(1000 * 5);
10         } catch (InterruptedException e) {
11             e.printStackTrace();
12         }
13         // 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)
14         t.interrupt();
15     }
16 }
17 
18 class MyRunnable2 implements Runnable {
19     @Override
20     public void run() {
21         System.out.println(Thread.currentThread().getName() + "---> begin");
22         try {
23             // 睡眠1年
24             Thread.sleep(1000 * 60 * 60 * 24 * 365);
25         } catch (InterruptedException e) {
26             e.printStackTrace();
27         }
28         //1年之后才会执行这里
29         System.out.println(Thread.currentThread().getName() + "---> end");
30 }

15java中强行终止一个线程的执行(不推荐使用,了解即可!)

 package 多线程.终止线程;

public class Demo01 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyThread() {});
        t1.start();
        // 模拟5秒
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 5秒之后强行终止t线程
        t1.stop(); // 已过时(不建议使用。)
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
           System.out.println(i);
        }
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

注意:

这种方式存在很大的缺点:容易丢失数据。

因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。不建议使用。

16Java中合理结束一个进程的执行(常用)

在线程中加个判断为true运行false保存数据然后不运行代码让他自动结束

package 多线程.终止线程;

public class Demo02 {
    public static void main(String[] args) {
        A a = new A();
        Thread t1 = new Thread(a);
        t1.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+i);
            if (i==100){
                a.stop1();
                break;
            }
        }
    }
}
class A implements Runnable{
    boolean flag = true;

    public void stop1() {
       this.flag = false;
    }
    @Override
    public void run() {
        for (;;) {
            if (flag==true){
              System.out.println(Thread.currentThread().getName()+"运行中");
            }else {
                //处理你线程结束需要保存的数据
                System.out.println(Thread.currentThread().getName()+"停止");
                break;
            }
        }
    }
}

 

17补充小知识:线程调度(了解)

1.常见的线程调度模型有哪些?

抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
java采用的就是抢占式调度模型

均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。

2.java中提供了哪些方法是和线程调度有关系的呢?

2.1实例方法:

方法名作用
int getPriority() 获得线程优先级
void setPriority(int newPriority) 设置线程优先级
  • 最低优先级1
  • 默认优先级是5
  • 最高优先级10

优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)

2.2静态方法:

方法名作用
static void yield() 让位方法,当前线程暂停,回到就绪状态,让给其它线程。

 

yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。

yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。

注意:在回到就绪之后,有可能还会再次抢到

2.3实例方法:

方法名作用
void join() 将一个线程合并到当前线程中,当前线程受阻塞,加入的线程执行直到结束

 join将一个线程a合并到当前线程b中,当a运行结束b才运行

 1 class MyThread1 extends Thread {
 2     public void doSome(){
 3         MyThread2 t = new MyThread2();
 4         t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
 5     }
 6 }
 7 
 8 class MyThread2 extends Thread{
 9 
10 }

18、Java进程的优先级

常量:

常量名备注
static int MAX_PRIORITY 最高优先级(10)
static int MIN_PRIORITY 最低优先级(1)
static int NORM_PRIORITY 默认优先级(5)

 

方法:

方法名作用
int getPriority() 获得线程优先级
void setPriority(int newPriority) 设置线程优先级

 

public class ThreadTest11 {
    public static void main(String[] args) {
        System.out.println("最高优先级:" + Thread.MAX_PRIORITY);//最高优先级:10
        System.out.println("最低优先级:" + Thread.MIN_PRIORITY);//最低优先级:1
        System.out.println("默认优先级:" + Thread.NORM_PRIORITY);//默认优先级:5
        
        // main线程的默认优先级是:5
        System.out.println(hread.currentThread().getName() + "线程的默认优先级是:" + currentThread.getPriority());

        Thread t = new Thread(new MyRunnable5());
        t.setPriority(10);
        t.setName("t");
        t.start();

        // 优先级较高的,只是抢到的CPU时间片相对多一些。
        // 大概率方向更偏向于优先级比较高的。
        for(int i = 0; i < 10000; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

class MyRunnable5 implements Runnable {
    @Override
    public void run() {
        for(int i = 0; i < 10000; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

注意:

  • main线程的默认优先级是:5
  • 优先级较高的,只是抢到的CPU时间片相对多一些。大概率方向更偏向于优先级比较高的。

19、关于线程的yield()方法 (让位)

方法名作用
static void yield() 让位,当前线程暂停,回到就绪状态,让给其它线程。

 

public class ThreadTest12 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable6());
        t.setName("t");
        t.start();

        for(int i = 1; i <= 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

class MyRunnable6 implements Runnable {

    @Override
    public void run() {
        for(int i = 1; i <= 10000; i++) {
            //每100个让位一次。
            if(i % 100 == 0){
                Thread.yield(); // 当前线程暂停一下,让给主线程。
            }
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

注意: 并不是每次都让成功的,有可能它又抢到时间片了。

20,关于线程的join()方法 (强制执行)

方法名 作用
void join() 将一个线程a合并到当前线程b中,a执行完b才能执行
void join(long millis) 接上条,等待该线程终止的时间最长为 millis 毫秒
void join(long millis, int nanos) 接第一条,等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒
public class ThreadTest13 {
    public static void main(String[] args) {
        System.out.println("main begin");

        Thread t = new Thread(new MyRunnable7());
        t.setName("t");
        t.start();

        //合并线程
        try {
            t.join(); // t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main over");
    }
}

class MyRunnable7 implements Runnable {

    @Override
    public void run() {
        for(int i = 0; i < 10000; i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

注意: 一个线程.join(),当前线程会进入”阻塞状态“。等待加入线程执行完!

21补充小知识:多线程并发环境下,数据的安全问题(重点)

1.为什么这个是重点?

以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。

最重要的是: 你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:★★★★★

2.什么时候数据在多线程并发的环境下会存在安全问题呢?★★★★★

满足三个条件:

条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。

3.怎么解决线程安全问题呢?

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?

线程排队执行。(不能并发)。用排队执行解决线程安全问题。

这种机制被称为:线程同步机制

专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。

线程同步就是线程排队了,线程排队了就会 牺牲一部分效率 ,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。

线程同步:队列+锁

4.两个专业术语:

异步编程模型:

线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。

其实就是:多线程并发(效率较高。)

异步就是并发。

同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。

效率较低。线程排队执行。

同步就是排队。

22守护线程(Deamon)

一、什么是Daemon线程

Daemon线程也是守护线程,它是一种支持型的线程,主要用在程序的后台调度以及一些支持性(服务性)的工作,常见的例子:JVM中垃圾回收线程就是典型的守护线程

二、守护线程和用户线程的区别

守护线程与用户线程的区别发生在JVM的离开:

可以说JVM想要运行,用户线程也必须运行
守护线程是服务于用户线程的,如果用户线程不在了,那么守护线程的存在是没有意义的,此时该程序(进程)就没有运行的必要了,JVM也就退出了
守护线程的优先级是低于用户线程的

三、用户设置守护线程

守护线程并不是固定死的,用户也可以将自己的线程(服务性质)设置为守护线程,设置方法为Thread.setDaemon()方法,

语法:

 1 package 多线程.守护线程;
 2 
 3 public class Demo01 {
 4  public static void main(String[] args) {
 5               I i =new I();
 6               J j =new J();
 7      Thread thread = new Thread(j);
 8      thread.setDaemon(true);//定义为守护线程 一般线程默(用户线程)认为false
 9      thread.start();
10      Thread thread1 = new Thread(i);
11      thread1.start();
12  }
13 }
14 class I implements Runnable {
15 
16     @Override
17     public void run() {
18         for (int i = 0; i < 100; i++) {
19             if (i==0){
20                 System.out.println("你出生了");
21             }
22             System.out.println("你已经活了"+i+"岁了");
23         }
24         System.out.println("你已经死了");
25     }
26 }
27 class J implements Runnable {
28     @Override
29     public void run() {
30         while (true){
31             System.out.println("上帝守护这你");
32         }
33 
34     }
35 }

在Daemon中产生的新线程也是守护线程
前面说到了,服务性质的线程可以设置为守护线程,类似于读写操作、计算逻辑。
如果带有这些操作的线程被设置为守护线程,在它执行时,有用户线程在执行的话还好说,但是可能会出现该线程创建完成且启动但是还没有执行到读写操作,尤其是写操作,可能是作为某个程序的最后一步执行,那么此时该程序中的用户线程都已经执行完毕了,而main函数只负责创建守护线程并启动,然后,main线程也会关闭,JVM发现没有用户线程了,便会退出,守护线程也会终止,此时可能还来不及进行写操作。

22线程安全

1.synchronized-线程同步

线程同步机制的语法是:

synchronized(){
    // 线程同步代码块。
}

重点:
synchronized后面小括号() 中传的这个“数据”是相当关键的。这个数据必须是 多线程共享 的数据。才能达到多线程排队。

1.1 ()中写什么?

那要看你想让哪些线程同步。

假设t1、t2、t3、t4、t5,有5个线程,你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?

你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。

注意:
在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁。)
100个对象,100把锁。1个对象1把锁。

1.2 代码的执行原理?(★★★★★)
1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。

2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
占有这把锁的。直到同步代码块代码结束,这把锁才会释放。

3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
t2占有这把锁之后,进入同步代码块执行程序。

4、这样就达到了线程排队执行

重中之重:
这个共享对象一定要选好了。这个共享对象一定是你需要排队
执行的这些线程对象所共享的。

1.3 在实例方法上可以使用synchronized

synchronized出现在实例方法上,一定锁的是 this

没得挑。只能是this。不能是其他的对象了。所以这种方式不灵活。

1.3.1 缺点

synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低。所以这种方式不常用。

1.3.2 优点

代码写的少了。节俭了。

1.3.3 总结

如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。

 1     public synchronized void withdraw(double money){
 2         double before = this.getBalance();
 3         double after = before - money;
 4         try {
 5             Thread.sleep(1000);
 6         } catch (InterruptedException e) {
 7             e.printStackTrace();
 8         }
 9         this.setBalance(after);
10     }

1.4 在方法调用处使用synchronized

在方法上用synchronized 要锁的是线程共享的对象

 1 package 多线程.线程同步;
 2 
 3 public class demo02 {
 4 
 5     public static void main(String[] args) {
 6         Account a1 =new Account(1000,"赵可明");
 7         new Thread(new A(a1,50,"赵小可")).start();
 8         new Thread(new A(a1,100,"赵1可")).start();
 9         new Thread(new A(a1,100,"赵2可")).start();
10         new Thread(new A(a1,100,"赵3可")).start();
11         new Thread(new A(a1,100,"赵4可")).start();
12         new Thread(new A(a1,500,"赵5可")).start();
13         new Thread(new A(a1,500,"赵6可")).start();
14     }
15 }
16 
17 //银行取钱
18 class A implements Runnable{
19     //账户的钱
20     Account account;
21     //取多少钱
22     private int getAccount;
23     //你拿到多少钱
24     private int own;
25     //取钱人
26     private  String name;
27     public A(Account account, int getAccount , String name){
28         this.account = account;
29         this.getAccount=getAccount;
30         this.name=name;
31     }
32     public void run() {
33         //这个锁的是银行A 这个银行对象不是共享的 这些线程共享的应该是账户
34         //synchronized(this){
35         //synchronized(this){和在方法上加   public synchronized void run() {  是一样的
36         synchronized (account){
37             if (account.getMoney()-getAccount<0){
38                 System.out.println("余额不足,你的账户余额:"+account.getMoney());
39                 return;
40             }else {
41                 try {
42                     Thread.sleep(1000);
43                 } catch (InterruptedException e) {
44                     throw new RuntimeException(e);
45                 }
46                 System.out.println("你的账户余额:"+account.getMoney());
47                 account.setMoney(account.getMoney() - getAccount);
48                 System.out.println(account.getName()+"你的取款金额为:"+getAccount+"你的账户余额:"+account.getMoney()+"操作人"+name);
49             }
50 
51         }
52     }
53 }
54 //账户类
55 class Account {
56     private int money;
57     private String name;
58 
59     public Account(int money, String name) {
60         this.money = money;
61         this.name = name;
62     }
63 
64     public int getMoney() {
65         return money;
66     }
67 
68     public String getName() {
69         return name;
70     }
71 
72     public void setMoney(int money) {
73         this.money = money;
74     }
75 
76     public void setName(String name) {
77         this.name = name;
78     }
79 }

23、Java中有三大变量?★★★★★

  • 实例变量:在堆中。
  • 静态变量:在方法区。
  • 局部变量:在栈中。

以上三大变量中:

局部变量永远都不会存在线程安全问题。

  • 因为局部变量不共享。(一个线程一个栈。)
  • 局部变量在栈中。所以局部变量永远都不会共享。
  1. 实例变量在堆中,堆只有1个。
  2. 静态变量在方法区中,方法区只有1个。

堆和方法区都是多线程共享的,所以可能存在线程安全问题。

总结:

  • 局部变量+常量:不会有线程安全问题。
  • 成员变量(实例+静态):可能会有线程安全问题。

24、以后线程安全和非线程安全的类如何选择?

如果使用局部变量的话:

建议使用:StringBuilder

因为局部变量不存在线程安全问题。选择StringBuilder。

StringBuffer效率比较低。

反之:

使用StringBuffer

  • ArrayList是非线程安全的。
  • Vector是线程安全的。
  • HashMap HashSet是非线程安全的。
  • Hashtable是线程安全的。

25总结synchronized

第一种:同步代码块

灵活

synchronized(线程共享对象){
    同步代码块;
}

第二种:在实例方法上使用synchronized

表示共享对象一定是 this 并且同步代码块是整个方法体。

第三种:在静态方法上使用synchronized

表示找 类锁。类锁永远只有1把。

就算创建了100个对象,那类锁也只有1把。

注意区分:

对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。

26、我们以后开发中应该怎么解决线程安全问题?

是一上来就选择线程同步吗?synchronized不是,synchronized会让程序的执行效率降低,用户体验不好。

系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。

第一种方案:尽量使用局部变量 代替 “实例变量和静态变量”。

第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)

第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

27、死锁(DeadLock)

死锁代码要会写。一般面试官要求你会写。
只有会写的,才会在以后的开发中注意这个事儿。
因为死锁很难调试。

 1 package 多线程.死锁DeadLock;
 2 
 3 public class Demo01 {
 4 public static void main(String[] args) {
 5     C c1 = new C();
 6     D d1 = new D();
 7     Thread threadA = new Thread( new A(c1,d1));
 8     threadA.setName("小胖子");
 9     Thread threadB = new Thread(new B(c1,d1));
10     threadB.setName("小瘦子");
11     threadA.start();
12     threadB.start();
13 }
14 }
15 class A implements Runnable {
16     C c;
17     D d;
18 
19     public A(C c1, D d1) {
20         this.c = c1;
21         this.d = d1;
22     }
23 
24     @Override
25     public void run() {
26         synchronized (c){
27             System.out.println(Thread.currentThread().getName()+":我穿裤子A");
28             try {
29                 Thread.sleep(5000);
30             } catch (InterruptedException e) {
31                 throw new RuntimeException(e);
32             }
33             synchronized (d){
34                 System.out.println("====================");
35             }
36         }
37 
38     }
39 }
40 class B implements Runnable{
41     C c;
42     D d;
43 
44     public  B( C c,D d){
45         this.c =c;
46         this.d=d;
47     }
48     @Override
49     public void run() {
50         synchronized (d){
51             System.out.println(Thread.currentThread().getName()+":我穿上衣B");
52             try {
53                 Thread.sleep(5000);
54             } catch (InterruptedException e) {
55                 throw new RuntimeException(e);
56             }
57             synchronized (c){
58                 System.out.println("====================");
59             }
60         }
61     }
62 }
63 class C  {
64 
65 }
66 class D  {
67 
68 }

lock锁;

 

这个程序就卡死啦 你需要上衣我需要裤子

28、定时器

1定时器的作用:

间隔特定的时间,执行特定的程序。

每周要进行银行账户的总账操作。
每天要进行数据的备份操作。

在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:

可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)

在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

构造方法:

构造方法名 备注
Timer()  创建一个定时器
Timer(boolean isDaemon) isDaemon为true为守护线程定时器
Timer(String name) 创建一个定时器,其线程名字为name
Timer(String name, boolean isDaemon) 结合2、3

方法:

方法名作用
void schedule(TimerTask task, Date firstTime, long period) 安排指定的任务在指定的时间开始进行重复的固定延迟执行
void cancel() 终止定时器

29、使用定时器实现日志备份

平时格式

 1 class TimerTest01{
 2     public static void main(String[] args) {
 3         Timer timer = new Timer();
 4 //        Timer timer = new Timer(true);//守护线程
 5         String firstTimeStr = "2021-05-09 17:27:00";
 6         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 7         try {
 8             Date firstTime = sdf.parse(firstTimeStr);
 9             timer.schedule(new MyTimerTask(), firstTime, 1000 * 5);//每5s执行一次
10         } catch (ParseException e) {
11             e.printStackTrace();
12         }
13     }
14 }
15 
16 class MyTimerTask extends TimerTask{
17     @Override
18     public void run() {
19         Date d = new Date();
20         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
21         String time = sdf.format(d);
22         System.out.println(time + ":备份日志一次!");
23     }
24 }

匿名内部类格式

class TimerTest02{
    public static void main(String[] args) {
        Timer timer = new Timer();
        String firstTimeStr = "2021-05-09 17:56:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date firstTime = sdf.parse(firstTimeStr);
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    Date d = new Date();
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    String time = sdf.format(d);
                    System.out.println(time + ":备份日志一次!");
                }
            }, firstTime, 1000 * 5);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

30、关于Object类的wait()、notify()、notifyAll()方法

1方法:

方法名作用
void wait() 让活动在当前对象的线程无限等待(释放之前占有的锁)
void notify() 唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁)
void notifyAll() 唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁)

 

2方法详解

第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是 Object类中自带 的。

wait方法和notify方法不是通过线程对象调用,
不是这样的:t.wait(),也不是这样的:t.notify()…不对

第二:wait()方法作用?

 Object o = new Object(); o.wait(); 

表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止

o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。

第三:notify()方法作用?

 Object o = new Object(); o.notify();  

表示:唤醒正在o对象上等待的线程。

 

第四:notifyAll() 方法 作用?

 Object o = new Object(); o.notifyAll(); 

表示:这个方法是唤醒o对象上处于等待的所有线程。

 

3图文 

4总结 ★★★★★(呼应生产者消费者模式)

1、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。

2、wait方法和notify方法建立在 线程同步 的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。

3、wait方法作用:o.wait() 让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。

4、notify方法作用:o.notify() 让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。

31、生产者消费者模式(wait()和notify())

1什么是“生产者和消费者模式”?

  • 生产线程负责生产,消费线程负责消费。
  • 生产线程和消费线程要达到均衡。
  • 这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。

2模拟一个业务需求

模拟这样一个需求:

  • 仓库我们采用List集合。

  • List集合中假设只能存储1个元素。

  • 1个元素就表示仓库满了。

  • 如果List集合中元素个数是0,就表示仓库空了。

  • 保证List集合中永远都是最多存储1个元素。

  • 必须做到这种效果:生产1个消费1个。

使用wait方法和notify方法实现“生产者和消费者模式”

public class ThreadTest16 {
    public static void main(String[] args) {
        // 创建1个仓库对象,共享的。
        List list = new ArrayList();
        // 创建两个线程对象
        // 生产者线程
        Thread t1 = new Thread(new Producer(list));
        // 消费者线程
        Thread t2 = new Thread(new Consumer(list));

        t1.setName("生产者线程");
        t2.setName("消费者线程");

        t1.start();
        t2.start();
    }
}

// 生产线程
class Producer implements Runnable {
    // 仓库
    private List list;

    public Producer(List list) {
        this.list = list;
    }
    @Override
    public void run() {
        // 一直生产(使用死循环来模拟一直生产)
        while(true){
            // 给仓库对象list加锁。
            synchronized (list){
                if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。
                    try {
                        // 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到这里说明仓库是空的,可以生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                // 唤醒消费者进行消费
                list.notifyAll();
            }
        }
    }
}

// 消费线程
class Consumer implements Runnable {
    // 仓库
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        // 一直消费
        while(true){
            synchronized (list) {
                if(list.size() == 0){
                    try {
                        // 仓库已经空了。
                        // 消费者线程等待,释放掉list集合的锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到此处说明仓库中有数据,进行消费。
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                // 唤醒生产者生产。
                list.notifyAll();
            }
        }
    }
}

 

标签:总结,复习,Thread,void,线程,println,new,多线程,public
From: https://www.cnblogs.com/zhao-ke-ming/p/18335142

相关文章

  • 操作系统知识,应付考研复习、期末考试或者工作面试,2h便可看完
    本文是看b站清华大学博主@五道口一只鸭,整理出的学习笔记,主要目的是为了让自己以后方便复习。一、操作系统的概念及特征1、计算机系统的概念:计算机系统由软件和硬件两部分组成。软件:包括系统软件和应用软件。软件(就是程序)定义:完成一定任务的程序及其数据。系统软件:操作系统......
  • 【Redis进阶】Redis单线程模型和多线程模型
    目录单线程为什么Redis是单线程处文件事件理器的结构文件处理器的工作流程总结文件事件处理器连接应答处理器命令请求处理器命令回复处理器多线程为什么引入多线程多线程架构多线程执行流程关于Redis的问题Redis为什么采用单线程模型Redis为什么要引入多线程呢......
  • Linux C++ 多线程编程
    LinuxC++多线程编程参考教程:c++:互斥锁/多线程的创建和unique_lock<mutex>的使用_mutex头文件vc++-CSDN博客1.编写unique_mutex1.1创建文件夹通过终端创建一个名为unique_mutex的文件夹以保存我们的VSCode项目,在/unique_mutex目录下打开vscode。rosnoetic@rosnoetic-Virt......
  • Python和多线程(multi-threading)
    在Python中,实现并行处理的方法有几种,但由于Python的全局解释器锁(GIL,GlobalInterpreterLock)的存在,传统意义上的多线程(使用threading模块)并不总能有效利用多核CPU来实现真正的并行计算。GIL确保任何时候只有一个线程在执行Python字节码。不过,仍然有几种方法可以绕过这个限制,......
  • Java - 多线程
    三种实现方式常用成员方法1.线程name默认“Thread-”+"序号"2.可以通过重写构造方法在创建时给线程命名线程的生命周期与状态同步代码块格式synchronized(锁对象){操作共享数据的代码}1.锁对象随机,但只要是有static修饰的唯一对象,一般写本类class文件,如MyTh......
  • 散知识点总结(持更)
    有一些小trick,专门用一整篇博客来写不太合适,所以都放在这里吧。逆序对考试的时候树状数组做法显然比其他的都好写。考虑每个元素对答案的贡献,我们需要知道在它之前有多少元素比它大。我们只需要维护一个权值树状数组,在枚举到\(i\)的时候查询当前树状数组中的元素有多少比它......
  • 堆总结(C语言)
    堆总结(C语言)二叉树的顺序结构及实现堆是什么堆的分类堆的实现堆的向下调整堆的向上调整堆的应用堆排序TOP-K问题思路:堆是什么堆总是一棵完全二叉树,堆是用来存完全二叉树的,如果存普通的二叉树就会浪费空间。堆(一种二叉树)使用顺序结构的数组来存储。堆不是简单的......
  • 第六周总结
    本周,我专注于科目三的驾驶练习,逐渐熟悉了道路行驶的各种操作要求,包括起步、换挡、加减速、变道等实际驾驶技能。在反复的练习中,我对车辆的操控感更加得心应手,为即将到来的考试奠定了基础。除此之外,我还深入了解了Spark在大数据领域中的重要作用。Spark是一种快速、通用、可扩展的......
  • 快速多项式全家桶 简略总结 (不确定里面的内容对不对)
    多项式牛顿迭代解决的问题:求一个[多项式函数](?)\(G\),使得\(F(G)\equiv0\pmod{x^n}\)。(听XK提到泛函分析)\[G_{k+1}\equivG_k-\frac{F(G_k)}{F'(G_k)}\pmod{x^{2^{k+1}}}\]求导时把\(G\)当成未知数,不要对\(G\)求导。倍增。加法每一项对应......
  • CUDA--内存访问越界或无效的索引操作解决办法--总结
    设备端的断言错误(device-sideasserttriggered)通常发生在CUDA代码中访问无效的内存地址或执行了无效的操作。解决这种错误需要系统地排查代码中的潜在问题。以下是详细的解决方案:1.检查数组边界确保所有访问数组或指针的操作都在有效范围内。检查线程索引和块索引的计算,确......