线程

基本概念

进程: 进程就是正在运行的应用程序。 进程了负责了内存空间划分。
线程: 一个进程中的 代码是由线程去执行的,线程也就是进程中一个执行路径。
多线程: 一个进程中有多个线程可以同时执行任务。

注意:任何一个java程序中至少存在两个线程:main线程和垃圾回收线程。

多线程的好处于弊端
多线程 的好处:
    1. 解决一个进程中可以同时执行多个任务的问题。
    2. 提高了资源利用率。
多线程的弊端:
    1. 增加了cpu的负担。
    2. 降低了一个进程中线程 的执行概率。
    3. 出现了线程 安全问题。
    4. 会引发死锁现象。

线程的声明周期

创建线程的方式

方式一 : 继承Thread类
        1. 自定义一个类继承Thread类。
        2. 重写Thread类的run方法,把自定义线程的任务代码写在run方法上。
        3. 创建Thread的子类对象,并且调用start方法启动一个线程。

注意:千万不要直接调用run方法,调用start方法的时候线程就会开启,线程一旦开启就会执行run方法中代码,如果直接调用run方法,那么就 相当于调用了一个普通的方法而已。

代码示例:

 1 //模拟QQ边聊天边视频的场景
 2 class TalkThread extends Thread{
 3     @Override
 4     public void run() {
 5         while (true) {
 6             System.out.println("正在聊天!!!!");
 7         }
 8     }
 9 }
10 
11 class VideoThread extends Thread{
12     @Override
13     public void run() {
14         while (true) {
15             System.out.println("正在视频!!!!");
16         }
17     }
18 }
19 
20 public class TestThread {
21     public static void main(String[] args) {
22         TalkThread talkThread = new TalkThread();
23         talkThread.start();
24         VideoThread videoThread = new VideoThread();
25         videoThread.start();
26     }
View Code

方式二:实现Runnable接口
    1. 自定义一个类实现Runnable接口。
    2. 实现Runnable接口 的run方法,把自定义线程的任务定义在run方法上。
    3. 创建Runnable实现类对象。
    4. 创建Thread类的对象,并且把Runnable实现类的对象作为实参传递。
    5. 调用Thread对象的start方法开启一个线程。

注意:Runnable实现类的对象并 不是一个线程对象,只不过是实现了Runnable接口 的对象而已。只有是Thread或者是Thread的子类才是线程 对象。

 1 //模拟QQ边聊天边视频的场景
 2 class Talk implements Runnable{
 3     public void run() {
 4         while (true) {
 5             System.out.println("正在聊天!!!!");
 6         }
 7     }
 8 }
 9 
10 class Video implements Runnable{
11     public void run() {
12         while (true) {
13             System.out.println("正在视频!!!!");
14         }
15     }
16 }
17 
18 public class TestThread {
19     public static void main(String[] args) {
20         Talk talk = new Talk();
21         Thread talkThread = new Thread(talk);
22         talkThread.start();
23         Video video = new Video();
24         Thread videoThread = new Thread(video);
25         videoThread.start();
26     }
27 }
View Code

常用方法

  Thread(String name)     初始化线程的名字
      setName(String name)    设置线程对象名
  getName()             返回线程的名字
  sleep()                 线程睡眠指定的毫秒数。 静态的方法, 那个线程执行了sleep方法代码那么就是那个线程睡眠。
  currentThread()      返回当前的线程对象,该方法是一个静态的方法, 注意: 哪个线程执行了currentThread()代码就返回那个线程的对象。         getPriority()             返回当前线程对象的优先级   默认线程的优先级是5
  setPriority(int newPriority) 设置线程的优先级    虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。

安全问题

出现线程安全问题的根本原因:
    1. 存在两个或者两个以上 的线程对象,而且线程之间共享着一个资源。
    2. 有多个语句操作了共享资源。

线程安全问题的解决方案:sun提供了线程同步机制让我们解决这类问题的。

方式一:同步代码块

  同步代码块的格式:
                synchronized(锁对象){
                    需要被同步的代码...
                }

  同步代码块要注意事项:
        1. 任意的一个对象都可以做为锁对象。
        2. 在同步代码块中调用了sleep方法并不是释放锁对象的。
        3. 只有真正存在线程安全问题的时候才使用同步代码块,否则会降低效率的。
        4. 多线程操作的锁对象必须是唯一共享的。否则无效。

代码示例如下:

 1 //模拟3个窗口同时在售50张 票 。
 2 class SellTacketThread extends Thread{
 3     static int num = 50;
 4     public SellTacketThread(String name) {
 5         super(name);
 6     }
 7     @Override
 8     public void run() {
 9         while (true) {
10             synchronized ("同步锁") {
11                 if (num>0) {
12                     System.out.println(Thread.currentThread().getName()+"售出了弟"+num+"张票");
13                     num--;
14                 }else{
15                     System.out.println("售罄了..");
16                     break;
17                 }
18             }
19         }
20     }
21 }
22 
23 public class TestThread {
24     public static void main(String[] args) {
25         SellTacketThread seller1 = new SellTacketThread("窗口1");
26         SellTacketThread seller2 = new SellTacketThread("窗口2");
27         SellTacketThread seller3 = new SellTacketThread("窗口3");
28         seller1.start();
29         seller2.start();
30         seller3.start();
31     }
32 }
View Code

方式二:同步函数

  所谓同步函数就是使用synchronized修饰一个函数。

同步函数要注意的事项 :
        1. 如果是一个非静态的同步函数的锁对象是this对象,如果是静态的同步函数的锁 对象是当前函数所属的类的字节码文件(class对象)。
        2. 同步函数的锁对象是固定的,不能由随意指定的。

代码示例:

 1 //模拟3个窗口同时在售50张 票 。
 2 class SellTacketThread extends Thread{
 3     static int num = 50;
 4     public SellTacketThread(String name) {
 5         super(name);
 6     }
 7     @Override
 8     public synchronized void run() {
 9         sellTacket();
10     }
11     public synchronized void sellTacket(){
12         while (true) {
13             synchronized ("同步锁") {
14                 if (num>0) {
15                     System.out.println(Thread.currentThread().getName()+"售出了弟"+num+"张票");
16                     num--;
17                 }else{
18                     System.out.println("售罄了..");
19                     break;
20                 }
21             }
22         }
23     }
24 }
25 
26 public class TestThread {
27     public static void main(String[] args) {
28         SellTacketThread seller1 = new SellTacketThread("窗口1");
29         SellTacketThread seller2 = new SellTacketThread("窗口2");
30         SellTacketThread seller3 = new SellTacketThread("窗口3");
31         seller1.start();
32         seller2.start();
33         seller3.start();
34     }
35 }
View Code

推荐使用: 同步代码块。
        原因:
            1. 同步代码块的锁对象可以由我们随意指定,方便控制。同步函数的锁对象是固定 的,不能由我们来指定。
            2. 同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数的所有代码都被同步了。

死锁问题

java中同步机制解决了线程安全问题,但是也同时引发死锁现象。
死锁现象出现 的根本原因:
    1. 存在两个或者两个以上的线程。
    2. 存在两个或者两个以上的共享资源。
死锁现象的解决方案: 没有方案。只能尽量避免发生而已。

代码示例:

 1 class DeadLock extends Thread{
 2     
 3     public DeadLock(String name){
 4         super(name);
 5     }
 6     
 7     public void run() {
 8         if("张三".equals(Thread.currentThread().getName())){
 9             synchronized ("遥控器") {
10                 System.out.println("张三拿到了遥控器,准备 去拿电池!!");
11                 synchronized ("电池") {
12                     System.out.println("张三拿到了遥控器与电池了,开着空调爽歪歪的吹着...");
13                 }
14             }
15         }else if("李四".equals(Thread.currentThread().getName())){
16             synchronized ("电池") { 
17                 System.out.println("狗娃拿到了电池,准备去拿遥控器!!");
18                 synchronized ("遥控器") {
19                     System.out.println("狗娃拿到了遥控器与电池了,开着空调爽歪歪的吹着...");
20                 }
21             }
22         }    
23     }
24 }
25 
26 public class Demo2 {
27 
28     public static void main(String[] args) {
29         DeadLock thread1 = new DeadLock("张三");
30         DeadLock thread2 = new DeadLock("李四");
31         //开启线程
32         thread1.start();
33         thread2.start();
34     }
35 }
View Code

线程通信

线程通讯: 一个线程完成了自己的任务时,要通知另外一个线程去完成另外一个任务.

相关函数:

  wait():  等待   如果线程执行了wait方法,那么该线程会进入等待的状态,等待状态下的线程必须要被其他线程调用notify方法才能唤醒。
  notify(): 唤醒    唤醒线程池等待线程其中的一个。
  notifyAll() : 唤醒线程池所有等待线程。

wait与notify方法要注意的事项:
    1. wait方法与notify方法是属于Object对象 的。
    2. wait方法与notify方法必须要在同步代码块或者是同步函数中才能 使用。
    3. wait方法与notify方法必需要由锁对象调用。

看一个经典的例子:生产者和消费者

代码如下:

 1 //产品类
 2 class Product{
 3     String name;  //名字
 4     double price;  //价格
 5     boolean flag = false; //产品是否生产完毕的标识,默认情况是没有生产完成。
 6 }
 7 
 8 //生产者
 9 class Producer extends Thread{
10     Product  p ;      //产品
11     public Producer(Product p) {
12         this.p  = p ;
13     }
14     @Override
15     public void run() {
16         int i = 0 ; 
17         while(true){
18          synchronized (p) {
19             if(p.flag==false){
20                  if(i%2==0){
21                      p.name = "苹果";
22                      p.price = 6.5;
23                  }else{
24                      p.name="香蕉";
25                      p.price = 2.0;
26                  }
27                  System.out.println("生产者生产出了:"+ p.name+" 价格是:"+ p.price);
28                  p.flag = true;
29                  i++;
30                  p.notify(); //唤醒消费者去消费
31             }else{
32                 //已经生产 完毕,等待消费者先去消费
33                 try {
34                     p.wait();   //生产者等待
35                 } catch (InterruptedException e) {
36                     e.printStackTrace();
37                 }
38             }             
39         }    
40       }    
41     }
42 }
43 
44 
45 //消费者
46 class Customer extends Thread{
47     Product p; 
48     public  Customer(Product p) {
49         this.p = p;
50     }
51     
52     @Override
53     public void run() {
54         while(true){
55             synchronized (p) {    
56                 if(p.flag==true){  //产品已经生产完毕
57                     System.out.println("消费者消费了"+p.name+" 价格:"+ p.price);
58                     p.flag = false; 
59                     p.notify(); // 唤醒生产者去生产
60                 }else{
61                     //产品还没有生产,应该 等待生产者先生产。
62                     try {
63                         p.wait(); //消费者也等待了...
64                     } catch (InterruptedException e) {
65                         e.printStackTrace();
66                     }
67                 }
68             }
69         }    
70     }
71 }
72 
73 public class Demo5 {
74     
75     public static void main(String[] args) {
76         Product p = new Product();  //产品
77         //创建生产对象
78         Producer producer = new Producer(p);
79         //创建消费者
80         Customer customer = new Customer(p);
81         //调用start方法开启线程
82         producer.start();
83         customer.start();
84     }
85 }
View Code

线程的停止

 线程的停止:
     1. 停止一个线程我们一般都会通过一个变量去控制的。
     2. 如果需要停止一个处于等待状态下的线程,那么我们需要通过变量配合notify方法或者interrupt()来使用。

代码如下:

 1 public class Demo6 extends Thread {
 2     
 3     boolean flag = true;
 4     
 5     public Demo6(String name){
 6         super(name);
 7     }
 8     
 9     
10     @Override
11     public synchronized void run() {
12         int i = 0 ;
13         while(flag){
14             try {
15                 this.wait(); //狗娃等待..
16             
17             } catch (InterruptedException e) {
18                 System.out.println("接收到了异常了....");
19             }
20             System.out.println(Thread.currentThread().getName()+":"+i);
21             i++;
22         }
23     }
24     public static void main(String[] args) {
25         Demo6 d = new Demo6("狗娃");
26         d.setPriority(10);
27         d.start();
28         for(int i = 0 ; i<100 ; i++){
29             System.out.println(Thread.currentThread().getName()+":"+i);
30             if(i==80){
31                 d.flag = false;
32                 d.interrupt(); //把线程的等待状态强制清除,被清除状态的线程会接收到一个InterruptedException。 
33                 /*synchronized (d) {                    
34                     d.notify();
35                 }*/
36             }
37         }
38     }
39 
40 }
View Code

 守护线程(后台线程)

 一个线程默认都不是守护线程。需要使用setDaemon(boolean b)来设置。

注意:在一个进程中如果只剩下了守护线程,那么守护线程也会死亡。

 代码如下:

 1 public class Demo7 extends Thread {
 2     
 3     public Demo7(String name){
 4         super(name);
 5     }
 6     
 7     @Override
 8     public void run() {
 9         for(int i = 1 ; i<=100 ; i++){
10             System.out.println("更新包目前下载"+i+"%");
11             if(i==100){
12                 System.out.println("更新包下载完毕,准备安装..");
13             }
14             try {
15                 Thread.sleep(100);
16             } catch (InterruptedException e) {
17                 e.printStackTrace();
18             }
19         }
20     }
21     
22     public static void main(String[] args) {
23          Demo7 d = new Demo7("后台线程");
24          d.setDaemon(true); //setDaemon() 设置线程是否为守护线程,true为守护线程, false为非守护线程。
25         // System.out.println("是守护线程吗?"+ d.isDaemon()); //判断线程是否为守护线程。
26          d.start();
27          
28          for(int i = 1 ; i<=100 ; i++){
29              System.out.println(Thread.currentThread().getName()+":"+i);
30          }
31          
32     }
33 
34 }
View Code

join方法

  在现有线程的基础上加入新的线程,并且现有线程必须让步于新的线程先完成任务, 才能继续执行。

代码示例如下:

 1 //老妈
 2 class  Mon extends Thread{
 3     
 4     public void run() {
 5         System.out.println("妈妈洗菜");
 6         System.out.println("妈妈切菜");
 7         System.out.println("妈妈准备炒菜,发现没有酱油了..");
 8         //叫儿子去打酱油
 9         Son s= new Son();
10         s.start();
11         try {
12             s.join();  //加入。 一个线程如果执行join语句,那么就有新的线程加入,执行该语句的线程必须要让步给新加入的线程先完成任务,然后才能继续执行。
13         } catch (InterruptedException e) {
14             e.printStackTrace();
15         }
16         System.out.println("妈妈继续炒菜");
17         System.out.println("全家一起吃饭..");        
18     }
19 } 
20 
21 class Son extends Thread{
22     
23     @Override
24     public void run() {
25         System.out.println("儿子下楼..");
26         try {
27             Thread.sleep(1000);
28         } catch (InterruptedException e) {
29             e.printStackTrace();
30         }
31         System.out.println("儿子一直往前走");
32         System.out.println("儿子打完酱油了");
33         System.out.println("上楼,把酱油给老妈");
34     }
35 }
36 
37 public class Demo8 {
38     
39     public static void main(String[] args) {
40         Mon m = new Mon();
41         m.start();
42     }
43 }
View Code
原文地址:https://www.cnblogs.com/nicker/p/6225407.html