java中的多线程 // 基础

java 中的多线程

简介

  进程 : 指正在运行的程序,并具有一定的独立能力,即 当硬盘中的程序进入到内存中运行时,就变成了一个进程

  线程 : 是进程中的一个执行单元,负责当前程序的执行。线程就是CPU通向程序的路径

       一个进程中只有一个线程,单线程程序

       一个进程中是可以有多个线程的,这个应用程序是多线程程序

 

程序的运行分类

  分时调度

    所有线程轮流使用CPU 的使用权,平均分配每个线程占用CPU 的时间

  抢占式调度

    优先让优先级高的线程使用CPU,如果线程的优先级相同,那么就会随机选择一个线程(线程的随机性)。

    java 使用的为抢占式调度

   抢占式调度简介:

    现在的操作系统都支持多进程并发运行,比如:一边用office ,一边使用QQ,一边看着视频  等等,

    看着好像这些程序都在同一时刻运行,实际上是 CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。

    对于CPU的一个核而言,某个时刻只能执行一个线程,而CPU 在多个线程之间切换的速度相对我们而言感觉要快,看上去就是在同一时刻运行。

    注意:

      多线程并不能提高程序的运行速度,但能够提高程序的运行效率,让CPU 的使用效率更高。

 

多线程的由来  

  jvm启动后,必然有一个执行线程(路径)从main方法开始的,一直执行到main方法结束,这个线程在java中称之为主线程(main线程)。 

  若主线程遇到循环,并循环次数较多,则导致程序在指定的位置停留时间过长,无法马上执行下面的程序,则需要等待循环结束后才能够执行代码。效率慢。 

  主线程负责执行其中的一个循环,由另一个线程执行另一个循环,最终实现多部分代码同时执行。多线程之间互不影响。

 

 多线程的创建方式

  1、继承 Thread 类

    创建一个类,继承 Thread 类,并重写Thread 类的 run 方法

    创建对象,调用 run 方法 ,就相当于执行其他线程的 main 方法。

    步骤:

      1、自定义一个类,继承Thread 类

      2、重写Thread 类中的 run 方法 ,设置线程任务

      3、创建自定义类的实例

      4、实例化 Thread 类,并传入自定义的类

      4、调用start ,开启进程。

 

    示例:

 1 1、自定义类
 2 // 创建一个类,继承Thread
 3 public class Thread_01 extends Thread{
 4     // 重写run 方法,在run方法中定义线程任务
 5     public void run(){
 6         System.out.println(Thread.currentThread().getName());
 7     }
 8 }
 9 2、main方法
10 public class ThreadDemo {
11     public static void main(String[] args) {
12         // 创建线程对象 t1
13         Thread_01 t1 = new Thread_01();
14         // 为了启动Thread_01 这个线程,需要实例化Thread,并传入自己的Thread_01实例
15         Thread thread = new Thread(t1);
16         // 通知CPU 要启动线程
17         thread.start();
18         System.out.println(Thread.currentThread().getName());
19     }
20 }
多线程继承Thread 示例

 

2、实现 Runnable 接口

     创建一个类,实现 Runnable 接口,重写 run  方法

    步骤:

      1、自定义一个类,实现 Runnable 接口

      2、重写run 方法,在run方法中设置线程任务

      3、在main 方法中,创建自定义类的实例化

      4、实例化Thread 类,并传入自定义类的实例化

      5、调用start 方法,开启进程

 1 1、自定义类,实现runnable 接口
 2 // 自定义类,实现Runnable 接口
 3 public class Runnable_01 implements Runnable{
 4     // 重写run 方法,在run方法中设置线程任务
 5     @Override
 6     public void run() {
 7         System.out.println(Thread.currentThread().getName());
 8     }
 9 
10 }
11 
12 2、在main方法中,调用
13 public class ThreadDemo {
14     public static void main(String[] args) {
15         // 创建线程对象 t1
16         Runnable_01 t1 = new Runnable_01();
17         // 为了启动Runnable_01 这个线程,需要实例化Thread,并传入自己的Runnable_01实例
18         Thread thread = new Thread(t1);
19         // 通知CPU 要启动线程
20         thread.start();
21         System.out.println(Thread.currentThread().getName());
22     }
23 }
多线程实现 Runnable 接口

    注意:

      调用 start 方法,开启新线程。若没有调用start 方法,只调用run 方法,只是调用了一个方法而已,并没有开启新线程

      一个线程只能调用一次start 方法,若线程执行结束后,不能再调用。

 

常用API 

 1 public class Demo {
 2     public static void main(String[] args) {
 3         // 调用currentThread().getName(),获取当前线程的名称
 4         System.out.println(Thread.currentThread().getName() + "123");
 5         // 调用currentThread().getId(),获取当前线程的标识符
 6         System.out.println(Thread.currentThread().getId());
 7         // 调用currentThread().getPriority(),获取当前线程的优先级
 8         System.out.println(Thread.currentThread().getPriority());
 9         // 调用currentThread().setName() 给当前线程设置新名称
10         Thread.currentThread().setName("线程新名称");
11         // 调用currentThread().getName(),获取当前线程的名称
12         System.out.println(Thread.currentThread().getName() + "123");
13         /**
14          * 打印结果 :main123
15          *         1
16          *         5
17          *         线程新名称123
18          */
19 
20     }
21 }
线程API 使用示例

 

线程安全

  若多线程调用全局变量时,会出现线程安全问题。

  即:使用java 模拟窗口卖票时,一个窗口就是一个线程,若同时卖票,可能会出现几个窗口同时卖一张票,或者卖出不存在的票(就剩一张票时,两个窗口同时卖出)

  所以,使用多线程时,要注意线程安全问题,解决线程安全问题有三种方式,

  方式一:同步代码块

    同步代码块:就是在方法块声明上加上 synchronized

synchronized (锁对象) {
	可能会产生线程安全问题的代码
}

  注意: 

    同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

    对象可以是this, 哪个对象调用,方法中的this就是哪个对象

  示例:

 1 /*
 2  * 开启3个线程,同时卖100张票
 3  */
 4 public class Demo01PayTicket {
 5     public static void main(String[] args) {
 6         //创建接口的实现类对象
 7         RunnableImpl r = new RunnableImpl();
 8         //创建线程对象
 9         Thread t0 = new Thread(r);
10         Thread t1 = new Thread(r);
11         Thread t2 = new Thread(r);
12         //开启多线程
13         t0.start();
14         t1.start();
15         t2.start();
16     }
17 }
18 
19 /*
20  * 发现程序出现了安全问题:卖出了重复的票和不存在的票
21  * 
22  * 多线程安全问题的第一种解决方案:使用同步代码块
23  * 
24  * 注意:
25  *         代码块中传递的锁对象必须保证唯一,多个线程使用的是同一个锁对象
26  *         锁对象可以是任意的对象
27  * 
28  */
29 public class RunnableImpl implements Runnable{
30     
31     //定义一个共享的票源
32     private int ticket = 100;
33     //创建一个锁对象
34     Object obj = new Object();
35 
36     @Override
37     public void run() {
38         //让卖票重复执行
39         while(true){
40             //同步代码块
41             synchronized (obj) {
42                 //判断是否还有票
43                 if(ticket>0){
44                     
45                     //提高安全问题出现的概率,增加一个sleep
46                     try {
47                         Thread.sleep(10);
48                     } catch (InterruptedException e) {
49                         e.printStackTrace();
50                     }
51                     
52                     //进行卖票
53                     System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
54                     ticket--;
55                 }
56             }
57         }    
58     }
59 }
多线程 同步代码块

 

  方式二:同步方法

    1、同步方法:在方法声明上加上 synchronized

public synchronized void method(){
   	可能会产生线程安全问题的代码
} 

  同步方法中的锁对象是 this,哪个对象调用,方法中的this就是哪个对象

 

    2、静态同步方法:在方法声明上加上 static synchronized

public static synchronized void method(){
     可能会产生线程安全问题的代码
}

  静态同步方法中的锁对象不是对象,是本类的 .class 文件

   示例:

 1 /*
 2  * 开启3个线程,同时卖100张票
 3  */
 4 public class Demo01PayTicket {
 5     public static void main(String[] args) {
 6         //创建接口的实现类对象
 7         RunnableImpl r = new RunnableImpl();
 8         //创建线程对象
 9         Thread t0 = new Thread(r);
10         Thread t1 = new Thread(r);
11         Thread t2 = new Thread(r);
12         //开启多线程
13         t0.start();
14         t1.start();
15         t2.start();
16     }
17 }
18 
19 /*
20  * 发现程序出现了安全问题:卖出了重复的票和不存在的票
21  * 
22  * 多线程安全问题的第二种解决方案:使用同步方法
23  * 
24  * 实现步骤:
25  *         1.把访问了共享数据的代码提取出来放在一个方法中
26  *         2.在方法上添加一个synchronized修饰符
27  * 
28  * 格式:
29  *     修饰符 synchronized 返回值类型 方法名(参数){
30  *         访问了共享数据的代码;
31  *     }
32  * 
33  * 把选中的代码提取到方法中快捷键:alt+shift+m
34  * 
35  */
36 public class RunnableImpl implements Runnable{
37     
38     //定义一个共享的票源
39     private static int ticket = 100;
40 
41     @Override
42     public void run() {
43         //让卖票重复执行
44         while(true){
45             payTicketStatic();
46         }
47         
48     }
49     
50     /*
51      * 静态的同步方法,锁对象不是this
52      * 静态优先于非静态加载到内存中,this是创建对象之后才有的
53      * 锁对象是本类的class属性(反射-->class文件对象)
54      */
55     public static synchronized void payTicketStatic() {
56         synchronized (RunnableImpl.class) {
57             //判断是否还有票
58             if(ticket>0){
59                 //提高安全问题出现的概率,增加一个sleep
60                 try {
61                     Thread.sleep(10);
62                 } catch (InterruptedException e) {
63                     e.printStackTrace();
64                 }
65                 //进行卖票
66                 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
67                 ticket--;
68             }
69         }
70     }
71 
72     /*
73      * 定义一个卖票的方法
74      * 使用synchronized修饰
75      * 使用锁对象把方法锁住
76      * 这个锁对象是谁?
77      * 创建的实现类对象new RunnableImpl();
78      * 也就是this,哪个对象调用的方法,方法中的this就是哪个对象
79      */
80     public synchronized void payTicket() {
81         //System.out.println(this);//cn.itcast.demo08.RunnableImpl@bcda2d
82         synchronized (this) {
83             //判断是否还有票
84             if(ticket>0){
85                 //提高安全问题出现的概率,增加一个sleep
86                 try {
87                     Thread.sleep(10);
88                 } catch (InterruptedException e) {
89                     e.printStackTrace();
90                 }
91                 
92                 //进行卖票
93                 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
94                 ticket--;
95             }
96         }
97     }
98 }
多线程 同步方法 synchronized

 

  方式三:使用lock 锁

    Lock 是接口,  ReentrantLock 是Lock 的实现类

    API

      

    调用

      1、创建ReentrantLock 对象

      2、在可能产生安全问题代码前调用 lock() 方法,获得锁

      3、调用unlock()方法,解锁

    

  ReentrantLock rl = new ReentrantLock();
  //获得锁
  rl.LOCK
  可能会产生线程安全问题的代码
  Rl.unlock

  示例:

 1 /*
 2  * 开启3个线程,同时卖100张票
 3  */
 4 public class Demo01PayTicket {
 5     public static void main(String[] args) {
 6         //创建接口的实现类对象
 7         RunnableImpl r = new RunnableImpl();
 8         //创建线程对象
 9         Thread t0 = new Thread(r);
10         Thread t1 = new Thread(r);
11         Thread t2 = new Thread(r);
12         //开启多线程
13         t0.start();
14         t1.start();
15         t2.start();
16     }
17 }
18 
19 /*
20  * 发现程序出现了安全问题:卖出了重复的票和不存在的票
21  * 
22  * 多线程安全问题的第三种解决方案:使用Lock锁
23  * java.util.concurrent.locks.Lock接口
24  * Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
25  * JDK1.5之后出现的新特性
26  * 
27  * 实现步骤:
28  *     1.在成员位置创建一个ReentrantLock对象
29  *     2.在访问了共享数据的代码前,调用lock方法,获取锁对象
30  *     3.在访问了共享数据的代码后,调用unlock方法,释放锁对象
31  * 
32  */
33 public class RunnableImpl implements Runnable{
34     
35     //定义一个共享的票源
36     private int ticket = 100;
37     //1.在成员位置创建一个ReentrantLock对象
38     Lock l = new ReentrantLock();
39     
40     @Override
41     public void run() {
42         //让卖票重复执行
43         while(true){
44             //2.在访问了共享数据的代码前,调用lock方法,获取锁对象
45             l.lock();
46             try {
47                 //可能会出现安全问题的代码
48                 //判断是否还有票
49                 if(ticket>0){
50                     //提高安全问题出现的概率,增加一个sleep
51                     Thread.sleep(10);
52                     //进行卖票
53                     System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
54                     ticket--;
55                 }
56                 
57             } catch (Exception e) {
58                 //异常的处理逻辑
59                 System.out.println(e);
60             } finally {
61                 //一定会执行的代码,资源释放
62                 //3.在访问了共享数据的代码后,调用unlock方法,释放锁对象
63                 l.unlock();//无论是否异常,都会释放掉锁对象
64             }
65         }
66         
67     }
68 }
多线程 lock 锁 示例

 

多线程线程图

  线程的五种状态:

    新建状态-->运行状态-->死亡(结束)状态

                        阻塞状态

          冻结状态(休眠/无限等待)

    新建 :刚创建出来的线程,即 new Thread();

    阻塞 :没有抢到CPU,在等待CPU调用

    运行 :调用run 方法,在运行状态

    死亡 :方法结束,调用完成

    休眠 :调用sleep() 方法,进入休眠状态

                          sleep 是Thread 的一个函数

                          sleep 指占用CPU 不工作,其他线程无法进入。即:sleep不会让出系统资源;

    无限等待 : 调用wait() 方法,未被唤醒

                          wait 是object 的一个函数,需要调用notify() 来唤醒

                          wait 指不占用CPU 不工作,其他线程可以进入。即:wait是进入线程等待池中等待,让出系统资源。

 

posted on 2018-11-28 11:35  大角牛  阅读(206)  评论(0编辑  收藏  举报

导航