跟着刚哥梳理java知识点——多线程(十六)

Posted 锲而不舍,金石可镂

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跟着刚哥梳理java知识点——多线程(十六)相关的知识,希望对你有一定的参考价值。

创建多线程
第一种方式
① 继承:继承Thread。
② 重写:重写Thread类的run()方法
③ 创建:创建一个子类的对象
④ 调用:调用线程的start()方法,启动此线程,调用run()方法

 1 class Work extends Thread{ //① 继承
 2   @Override
 3   //② 重写
 4   public void run() {
 5     for (int i = 1 ;i < 5; i++) {
 6       System.out.println(Thread.currentThread().getName()+":"+i);
 7     }
 8   }
 9 }
10 public static void main(String[] args) {
11   //③ 创建
12   Work work = new Work();
13   //④ 调用
14   work.start();
15   for (int i = 1 ;i < 5; i++) {
16     System.out.println(Thread.currentThread().getName()+":"+i);
17   }
18 }

输出结果:

main:1
main:2
main:3
main:4
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4

思考:把上面的start修改成run,想想会有什么结果呢?

main:1
main:2
main:3
main:4
main:1
main:2
main:3
main:4

走了两遍的main。因为Start是启动线程,run只是正常的调用了一下方法,和多线程没关系。


第二种方法
① 实现接口:实现Runnable接口的类
② 实现抽象方法:实现接口的run的方法
③ 创建对象:创建一个Runnable接口实现类的对象
④ 放入构造器:将此对象作为形参传递给Thread的构造器,创建Thread对象
⑤ 启动线程:启动这个线程

 1 class Work implements Runnable{ //① 实现接口
 2   //② 实现抽象方法
 3   public void run() {
 4     for (int i = 11 ;i < 100; i++) {
 5       System.out.println(Thread.currentThread().getName()+":"+i);
 6     }
 7   }
 8 }
 9 
10 public static void main(String[] args) {
11   //③ 创建对象
12   Work work = new Work();
13   //④ 放入构造器
14   Thread t1 = new Thread(work);
15   //⑤ 启动线程
16   t1.start();
17   for (int i = 11 ;i < 100; i++) {
18     System.out.println(Thread.currentThread().getName()+":"+i);
19   }
20 }

 

Thread(类) VS Runnable(接口)
① Runnable避免了java类的单继承局限性,接口可以多继承。
② 如果多个线程操作同一份资源更适合使用Runnable的方式


线程Thread的常用方法:
① start():启动线程并执行相应的run()方法
② run():将子线程要执行的代码放入run()方法
③ currentThread():静态的,调取当前的线程
    √ getName():获取此线程的名字
   例如:Thread.currentThread().getName()

    √ setName():设置线程的名字

④ yield():强制释放当前cpu执行权,(例如子线程和主线程都循环输出100次的数字,当主线程%10==0的时候,就调用主线程yield方法Thread.currentThread().yield(),强制主线程释放CPU执行权)需要说明的是释放线程的CPU执行权不代表其他线程就一定能抢到CPU的执行权。也可能释放的线程再次抢到资源。
⑤ join():在A线程中调用B线程join(参与进来的意思)方法,表示当执行到此方法,A线程停止执行,B执行完毕后,A再执行。
⑥ sleep():显式的让当前线程睡眠1毫秒

设置线程的优先级:优先级高只能说明抢到的几率高,不代表一定先完成
① getPriority():获取线程的优先级
② setPriority():设置线程的优先级
一共是10个等级.默认是等级5,Thread里的属性就是等级级别
Thread属性:
√ MAX_PRIORITY:最高的线程优先级
√ MIN_PRIORITY:最低的线程优先级
√ NORM_PRIORITY:默认的线程优先级
线程分为两类
①守护线程
   用来服务用户的,垃圾回收就是一个典型的守护线程
   若JVM都是守护线程,当前JVM将退出
②用户线程
   用户自己创建的线程
   用户线程-->守护线程: 
   通过在start()方法前调用thread.setDaemon(True)就可以

线程的生命周期:枚举status代表了状态

1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
   (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
   (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
   (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程安全性
原因:
由于一个线程在操作共享数据过程中未执行完的情况下,另外的线程参与进来,
导致共享数据存在安全问题。
共享数据:多个线程共共同操作同一个数据(变量)

解决:
必须让一个线程操作共享数据完毕之后,其他线程才有机会共享数据的操作

java如何解决的呢?
方式一:同步代码块
synchronized(同步监视器){
//需要同步的代码

}

同步监视器知识点:对于一般的方法,同步监视器可以考虑使用this关键字。synchronized(this)
                  对于静态方法而言,同步监视器使用当前类本身充当锁。synchronized(Singleton.class)

同步块包住谁呢?谁操作共享数据就包谁
注意:在实现的方式中,可以使用this充当锁,但是在继承的方式中,慎用this

单例模式:线程安全

 1 class Singleton{
 2     private Singleton(){
 3     
 4     }
 5     private static volatile Singleton instance = null;
 6     public static Singleton getInstance(){
 7     if(instance == null){
 8       synchronize(Singleton.calss){
 9         if(instance == null){
10          instance = new Singleton();
11        }
12      }
13    }
14    return instance;
15 }

方式二:同步方法
同步方法的锁:this

public synchronize void 方法(){

}

面试题:
银行有一个账号,有两个储户分别向一个账户存入3000元,每次存1000,存3次
,每次存完后打印账户余额

分析:
共享资源是什么?显然是一个账号。
是否需要用多线程?显然是用的,因为有两个储户

 1 class Account {
 2   double balance = 0;
 3   public synchronized void cunqian(double crm){
 4     balance += crm;
 5     System.out.println(Thread.currentThread().getName() + ":" + balance);
 6   }
 7 }
 8 class Customer implements Runnable{
 9   Account account;
10   public Customer(Account account) {
11     this.account = account;
12   }
13   @Override
14   public void run() {
15     for (int i = 0; i < 3; i++) {
16       account.cunqian(1000);
17     }
18   }
19 }
20 public static void main(String[] args) throws Exception {
21   Account account = new Account();
22   Customer customer = new Customer(account);
23   Thread t1 = new Thread(customer);
24   t1.start();
25   Thread t2 = new Thread(customer);
26   t2.start();
27   t1.setName("储户1");
28   t2.setName("储户2");
29 }

上面代码需要需要的是:由于是两个类,一定要保证共享资源类千万不要被多次实例化
所以一定要让第一个类实例化完成后当成形参出入到第二个中构造(看红色标记部分)


线程通信:(三个关键字使用的话,都必须在同步代码块或同步方法中,三个方法之所以不放在上面的Thread类的方法里,是因为这三个方法是在object里的方法
① wait():令当前线程挂起并放弃CPU、同步资源。让别的线程可访问并修改共享资源,而当前前程排队等候再次对资源的访问
② notify():唤醒正在排队等候同步资源的线程中优先级最高的锁
③ notifyAll():唤醒所有正在排队的等待的所有线程结束等待

 

面试题:

1、什么是线程?线程和进程的区别?
  进程:是是程序的一次动态执行,它经历了从代码加载,执行,到执行完毕的一个完整过程。这个过程也是进程本身从产生、发展,到最终消亡的一个的生命周期。

      线程:可以理解为进程的多条执行线索,每条线索又对应着各自独立的生命周期。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。

2、用户线程和守护线程的区别?

  用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安 和员工,各司其职,当员工都离开后,保安自然下班了。

3、创建线程有哪几种方式?

  这里就不说了,上面已经梳理了。

4、可以直接调用Thread类里的run()方法吗?

  当然可以调用,就是不会启动线程。

5、你如何理解多线程的优先级的?

  在操作系统中,线程可以划分优先级,优先级较高的线程得到CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务(其实并不是这样)。

在Java中,线程的优先级用setPriority()方法就行,线程的优先级分为1-10这10个等级,如果小于1或大于10,则抛出异常throw new IllegalArgumentException(),默认是5。

线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。

6、线程之间如何同步的?

  Java学习笔记---多线程同步的五种方法

7、如何确保线程安全?如何在两个线程之间共享数据?

  JAVA 并发编程-多个线程之间共享数据(六)

8、volatile关键字在Java中有什么作用?

  java中volatile关键字的含义

9、同步方法和同步块哪个更好?

  同步是一个高开销的操作,因此应该尽量减少同步的内容,故同步块是更好的选择,因为它不会锁住整个对象。同步方法会锁住整个对象,这通常会导致他们停止执行并需要等待获得这个对象上的锁。

10、什么是死锁?如何避免?

  死锁:如果线程1锁住了A,然后尝试对B进行加锁,同时线程2已经锁住了B,接着尝试对A进行加锁,这时死锁就发生了。

  如何避免:

  ① 加锁顺序:如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生

  ② 加锁时限:在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求

  ③ 死锁检测:当获取锁的时候做好记录,释放锁的时候做好记录,一旦检测到有死锁发生的时候,就要对进行回退操作。

11、什么是乐观锁和悲观锁?

  悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

  乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

   两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

12、什么是线程池?为什么要使用它?

  创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

13、Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?

  Java中的锁-Lock接口解析

14、什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?

  JAVA中的阻塞队列

15、多线程中栈与堆是公有的还是私有的?

  栈私有,堆公有

16、写一个线程安全的单例模式?双重锁是做什么用的?

 1  public class Singleton {  
 2        private static volatile Singleton instance;  
 3        private Singleton (){
 4        }   
 5        public static Singleton getInstance(){    //对获取实例的方法进行同步
 6          if (instance == null){
 7              synchronized(Singleton.class){
 8                  if (instance == null)
 9                      instance = new Singleton(); 
10             }
11         }
12         return instance;
13       }
14       
15   }

17、有1个全局变量,int sum=0,运行2个线程,代码如下
     for(int i=1;i<=50;i++){

    sum=sum+1;

   }
       问2个线程都运行完之后,sum的取值范围。

 1 public class Main implements Runnable{
 2     private int sum = 0;
 3     @Override
 4     public void run() {
 5         for (int i = 1 ;i <= 50; i++) {
 6             sum = +1;
 7         }
 8         System.out.println(sum);
 9     }
10     public static void main(String[] args) {
11         Main main = new Main();
12         Thread thread1 = new Thread(main);
13         Thread thread2 = new Thread(main);
14         thread1.start();
15         thread2.start();
16     }
17 }

  取值范围:1-100

18、有三个线程T1,T2,T3,怎么确保它们按顺序执行

  使用join方法即可。代码: 有三个线程T1 T2 T3,如何保证他们按顺序执行

19、编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。

  http://www.cnblogs.com/baizhanshi/p/6428810.html

20、子线程循环 10 次,接着主线程循环 20次,接着又回到子线程循环 10 次,接着再回到主线程又循环 20 次,如此循环50次,试写出代码。

 1 package com.wang.reflect;
 2 //编写功能类,实现子线程和主线程的功能
 3 class Function{
 4     private boolean flag=false;
 5     //子线程要实现的功能
 6     public synchronized void sub(){
 7         while(flag){
 8             try {
 9                 this.wait();
10             } catch (InterruptedException e) {
11                 e.printStackTrace();
12             }
13         }
14                
15         for(int i=0;i<10;i++){
16             //for循环内定义子线程的功能,这里简单的假设为打印一句话,主线程同理
17             System.out.println("sub"+i);
18         }
19         
20         flag=true;
21         this.notify();
22     }
23     //主线程要实现的功能
24     public synchronized void main(){
25         while(!flag){
26             try {
27                 this.wait();
28             } catch (InterruptedException e) {
29                 e.printStackTrace();
30             }
31         }
32         for(int i=0;i<20;i++){
33             System.out.println("main"+i);
34         }
35         
36         flag=false;
37         this.notify();
38     }
39     
40 }
41 
42 public class Demo01 {
43   
44     public static void main(String[] args) {
45          final Function f=new Function();
46         new Thread(
47                 new Runnable(){
48 
49                     @Override
50                     public void run() {
51                         for(int i=0;i<50;i++){
52                             f.sub();
53                         }
54                     }
55                 
56                 }
57                 ).start();
58         
59         for(int i=0;i<50;i++){
60             f.main();
61         }
62     }
63 }

JDK1.5以后,出现了Lock和condition,Lock类似于synchronized功能,用来进行线程同步,Condition功能类似于Object类中的wait和notify方法,用于线程间的通信.上面的代码可以用Lock和Condition来改进,如下:

 1 package com.wang.reflect;
 2 
 3 import java.util.concurrent.locks.Condition;
 4 import java.util.concurrent.locks.Lock;
 5 import java.util.concurrent.locks.ReentrantLock;
 6 
 7 //编写功能类,实现子线程和主线程的功能
 8 class Function{
 9     private boolean flag=false;
10     
11     Lock lock=new ReentrantLock();
12     Condition con=lock.newCondition();
13     //子线程要实现的功能
14     public  void sub(){
15         lock.lock();
16         try {
17             
18             while(flag){
19                 try {
20                     con.await();
21                 } catch (InterruptedException e) {
22                     e.printStackTrace();
23                 }
24             }
25                        
27             for(int i=0;i<10;i++){
28                 //for循环内定义子线程的功能,这里简单的假设为打印一句话,主线程同理
29                 System.out.println("sub"+i);
30             }
31             
32             flag=true;
33             con.signal();
34         } finally{
35             lock.unlock();
36         }
37     }
38     //主线程要实现的功能
39     public synchronized void main(){
40         lock.lock();
41         try {
42             while (!flag) {
43                 try {
44                     con.await();
45                 } catch (InterruptedException e) {
46                     e.printStackTrace();
47                 }
48             }
49             for (int i = 0; i < 20; i++) {
50                 System.out.println("main" + i);
51             }
52             flag = false;
53             con.signal();
54         } finally{
55             lock.unlock();
56         }
57     }
59 }
60 
62 public class Demo01 {
66     public static void main(String[] args) {
67          final Function f=new Function();
68         new Thread(
69                 new Runnable(){
70 
71                     @Override
72                     public void run() {
73                         for(int i=0;i<50;i++){
74                             f.sub();
75                         }
76                     }
77                 
78                 }
79                 ).start();
80         
81         for(int i=0;i<50;i++){
82             f.main();
83         }
84     }
85 }

 

以上是关于跟着刚哥梳理java知识点——多线程(十六)的主要内容,如果未能解决你的问题,请参考以下文章

跟着刚哥梳理java知识点——注释

跟着刚哥梳理java知识点——泛型和注解(十四)

跟着刚哥梳理java知识点——运算符

跟着刚哥梳理java知识点——基本数据类型

跟着刚哥梳理java知识点——变量之间的类型转换

跟着刚哥梳理java知识点——反射和代理(十七)