多线程(1)

 


1、进程、线程、多线程

1.1.什么是进程?

电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中的QQ、酷狗播放器、电脑管家等等。在这里插入图片描述

1.2.什么是线程?

进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。

那什么是多线程?提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多线程。

所谓串行,其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子:当我们下载多个文件时,在串行中它是按照一定的顺序去进行下载的,也就是说,必须等下载完A之后才能开始下载B,它们在时间上是不可能发生重叠的。
在这里插入图片描述
并行:下载多个文件,开启多条线程,多个文件同时进行下载,这里是严格意义上的,在同一时刻发生的,并行在时间上是重叠的。
在这里插入图片描述
了解了这两个概念之后,我们再来说说什么是多线程。举个例子,我们打开腾讯管家,腾讯管家本身就是一个程序,也就是说它就是一个进程,它里面有很多的功能,我们可以看下图,能查杀病毒、清理垃圾、电脑加速等众多功能。

1.3.多线程

按照单线程来说,无论你想要清理垃圾、还是要病毒查杀,那么你必须先做完其中的一件事,才能做下一件事,这里面是有一个执行顺序的。

如果是多线程的话,我们其实在清理垃圾的时候,还可以进行查杀病毒、电脑加速等等其他的操作,这个是严格意义上的同一时刻发生的,没有执行上的先后顺序。

 以上就是,一个进程运行时产生了多个线程。

2、什么是线程安全?

当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
既然是线程安全问题,那么毫无疑问,所有的隐患都是在多个线程访问的情况下产生的,也就是我们要确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行,我们看一下下面的代码。

1 Integer count = 0;
2 public void getCount() {
3        count ++;
4        System.out.println(count);
5  }

很简单的一段代码,下面我们就来统计一下这个方法的访问次数,多个线程同时访问会不会出现什么问题,我开启的3条线程,每个线程循环10次,得到以下结果:
在这里插入图片描述

我们可以看到,这里出现了两个26,出现这种情况显然表明这个方法根本就不是线程安全的,出现这种问题的原因有很多。

最常见的一种,就是我们A线程在进入方法后,拿到了count的值,刚把这个值读取出来,还没有改变count的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的count值是一样的。

那么由此我们可以了解到,这确实不是一个线程安全的类,因为他们都需要操作这个共享的变量。其实要对线程安全问题给出一个明确的定义,还是蛮复杂的,我们根据我们这个程序来总结下什么是线程安全。

当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。

搞清楚了什么是线程安全,接下来我们看看Java中确保线程安全最常用的两种方式。先来看段代码。

1 public void threadMethod(int j) {
2 
3     int i = 1;
4 
5     j = j + i;
6 }

大家觉得这段代码是线程安全的吗?

毫无疑问,它绝对是线程安全的,我们来分析一下,为什么它是线程安全的?

我们可以看到这段代码是没有任何状态的,就是说我们这段代码,不包含任何的作用域,也没有去引用其他类中的域进行引用,它所执行的作用范围与执行结果只存在它这条线程的局部变量中,并且只能由正在执行的线程进行访问。当前线程的访问,不会对另一个访问同一个方法的线程造成任何的影响。

两个线程同时访问这个方法,因为没有共享的数据,所以他们之间的行为,并不会影响其他线程的操作和结果,所以说无状态的对象,也是线程安全的

添加一个状态呢?

如果我们给这段代码添加一个状态,添加一个count,来记录这个方法并命中的次数,每请求一次count+1,那么这个时候这个线程还是安全的吗?

 1 public class ThreadDemo {
 2 
 3    int count = 0; // 记录方法的命中次数
 4 
 5    public void threadMethod(int j) {
 6 
 7        count++ ;
 8 
 9        int i = 1;
10 
11        j = j + i;
12    }
13 }

明显已经不是了,单线程运行起来确实是没有任何问题的,但是当出现多条线程并发访问这个方法的时候,问题就出现了,我们先来分析下count+1这个操作。

进入这个方法之后首先要读取count的值,然后修改count的值,最后才把这把值赋值给count,总共包含了三步过程:“读取”一>“修改”一>“赋值”,既然这个过程是分步的,那么我们先来看下面这张图,看看你能不能看出问题:
在这里插入图片描述
可以发现,count的值并不是正确的结果,当线程A读取到count的值,但是还没有进行修改的时候,线程B已经进来了,然后线程B读取到的还是count为1的值,正因为如此所以我们的count值已经出现了偏差,那么这样的程序放在我们的代码中,是存在很多的隐患的。

3.实现多线程方式 

1.继承Thread类 

2.实现Runnable接口

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

3.1.继承Thread类

实现步骤
  1.    定义一个类MyThread继承Thread类
  2. 在MyThread类中重写run()方法
  3.  创建MyThread类的对象
  4. 启动线程
代码演示:

 

3.2.实现Runnable接口

相比继承Thread类,实现Runnable接口的好处:
  • 避免了Java单继承的局限性
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现

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

public class Demo7 {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ExecutorService threadPool = Executors.newSingleThreadExecutor();

        //启动线程
        Future<String> future = threadPool.submit(new myCallable());

        try{
            System.out.println("waiting the thread to finish,,,");
             //使用Future监视目标线程调用call()方法的情况,当前的线程会一直阻塞,直到call()方法结束返回结果。
            System.out.println("获取监听线程返回值: "+future.get());
        }catch(Exception e){
            e.printStackTrace();
        }

    }

}
    /**
     * callable 比 runnable 强大的地方是:可以返回结果值
     * @return String
     * @others:接口是 Executor 框架的功能类;
     * */
    class myCallable implements Callable<String> {

        @Override
        public String call() throws Exception {
            System.out.println("callable body...");
            Thread.sleep(2000);
            return "Hello World!!";
        }
    }

4、如何确保线程安全?

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

4.1.synchronized

synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。

public class ThreadDemo {

   int count = 0; // 记录方法的命中次数

   public synchronized void threadMethod(int j) {

       count++ ;

       int i = 1;

       j = j + i;
   }
}

同步代码块:

 1 /**
 2  * 多线程卖票:synchronized
 3  */
 4 public class Demo10 {
 5     public static void main(String[] args) {
 6         ticket t = new ticket();
 7 
 8         Thread t1 = new Thread(t, "线程1");
 9         Thread t2 = new Thread(t, "线程2");
10         Thread t3 = new Thread(t, "线程3");
11 
12         t1.start();
13         t2.start();
14         t3.start();
15 
16     }
17 
18     static class ticket implements Runnable {
19         private int ticket = 10;
20         //创建锁
21         Object lock = new Object();
22 
23         @Override
24         public void run() {
25             String name = Thread.currentThread().getName();
26             while (true) {
27                 //同步代码块
28                 synchronized (lock){
29                     try {
30                         sell(name);
31                     } catch (InterruptedException e) {
32                         e.printStackTrace();
33                     }
34                     if (ticket < 0) {
35                         break;
36                     }
37                 }
38             }
39         }
40 
41         private void sell(String name) throws InterruptedException {
42             Thread.sleep(1000);
43             if (ticket > 0) {
44                 System.out.println(name + "——" + ticket);
45                 ticket--;
46             }
47         }
48     }
49 }
View Code
同步方法:
 1 public class Demo9 {
 2     public static void main(String[] args) {
 3         ticket t = new ticket();
 4 
 5         Thread t1 = new Thread(t, "线程1");
 6         Thread t2 = new Thread(t, "线程2");
 7         Thread t3 = new Thread(t, "线程3");
 8 
 9         t1.start();
10         t2.start();
11         t3.start();
12     }
13 
14     static class ticket implements Runnable{
15         private int ticket =10;
16         Lock lock = new ReentrantLock();
17 
18         @Override
19         public void run() {
20             String name = Thread.currentThread().getName();
21             while(true){
22                 try {
23                     if ("线程1".equals(name)){
24                         synchronized (lock){//线程1获得lock锁
25                             sell(name);
26                         }
27                     }else{
28                         sell(name);
29                     }
30                 } catch (InterruptedException e) {
31                     e.printStackTrace();
32                 }
33                 if (ticket < 0){
34                     break;
35                 }
36 
37             }
38         }
39         //synchronized:同步方法
40         private synchronized void  sell(String name) throws InterruptedException {//线程2获取到this锁
41             Thread.sleep(1000);
42              synchronized (lock){
43                  if (ticket >0){
44                      System.out.println(name +"——"+ ticket);
45                      ticket --;
46                  }
47              }
48         }
49     }
50 }
View Code

这样就可以确保我们的线程同步了,同时这里需要注意一个大家平时忽略的问题,首先synchronized锁的是括号里的对象,而不是代码,其次,对于非静态的synchronized方法,锁的是对象本身也就是this。

当synchronized锁住一个对象之后,别的线程如果想要获取锁对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则一直处于等待状态。

注意点:虽然加synchronized关键字,可以让我们的线程变得安全,但是我们在用的时候,也要注意缩小synchronized的使用范围,如果随意使用时很影响程序的性能,别的对象想拿到锁,结果你没用锁还一直把锁占用,这样就有点浪费资源。

4.2.lock

先来说说它跟synchronized有什么区别吧,Lock是在Java1.6被引入进来的,Lock的引入让锁有了可操作性,什么意思?就是我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。我们先来看下一般是如何使用的:

 1 private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子类
 2 
 3    private void method(Thread thread){
 4        lock.lock(); // 获取锁对象
 5        try {
 6            System.out.println("线程名:"+thread.getName() + "获得了锁");
 7            // Thread.sleep(2000);
 8        }catch(Exception e){
 9            e.printStackTrace();
10        } finally {
11            System.out.println("线程名:"+thread.getName() + "释放了锁");
12            lock.unlock(); // 释放锁对象
13        }
14    }

进入方法我们首先要获取到锁,然后去执行我们业务代码,这里跟synchronized不同的是,Lock获取的所对象需要我们亲自去进行释放,为了防止我们代码出现异常,所以我们的释放锁操作放在finally中,因为finally中的代码无论如何都是会执行的。

写个主方法,开启两个线程测试一下我们的程序是否正常:

 1 public static void main(String[] args) {
 2        LockTest lockTest = new LockTest();
 3 
 4        // 线程1
 5        Thread t1 = new Thread(new Runnable() {
 6 
 7            @Override
 8            public void run() {
 9                // Thread.currentThread()  返回当前线程的引用
10                lockTest.method(Thread.currentThread());
11            }
12        }, "t1");
13 
14        // 线程2
15        Thread t2 = new Thread(new Runnable() {
16 
17            @Override
18            public void run() {
19                lockTest.method(Thread.currentThread());
20            }
21        }, "t2");
22 
23        t1.start();
24        t2.start();
25    }

结果
在这里插入图片描述
可以看出我们的执行,是没有任何问题的。

卖票案例:

 1 /**
 2  * 多线程卖票
 3  */
 4 public class Demo8 {
 5     public static void main(String[] args) {
 6         ticket t = new ticket();
 7 
 8         Thread t1 = new Thread(t, "线程1");
 9         Thread t2 = new Thread(t, "线程2");
10         Thread t3 = new Thread(t, "线程3");
11 
12         t1.start();
13         t2.start();
14         t3.start();
15 
16     }
17 
18     static class ticket implements Runnable{
19         ReentrantLock lock = new ReentrantLock();
20         private int ticket =10;
21 
22         @Override
23         public void run() {
24             String name = Thread.currentThread().getName();
25             while(true){
26                 //加锁
27                 lock.lock();
28                 try {
29                     sell(name);
30                 } catch (InterruptedException e) {
31                     e.printStackTrace();
32                 }
33                 if (ticket < 0){
34                     break;
35                 }
36                 //释放锁
37                 lock.unlock();
38             }
39         }
40 
41         private void sell(String name) throws InterruptedException {
42             Thread.sleep(1000);
43             if (ticket >0){
44                 System.out.println(name +"——"+ ticket);
45                 ticket --;
46             }
47         }
48     }
49 }
View Code

4.3.死锁

多线程死锁:同步中嵌套同步,导致锁无法释放。
死锁解决办法:不要在同步中嵌套同步

 1 **
 2  * 死锁
 3  */
 4 public class Demo9 {
 5     public static void main(String[] args) {
 6         ticket t = new ticket();
 7 
 8         Thread t1 = new Thread(t, "线程1");
 9         Thread t2 = new Thread(t, "线程2");
10         Thread t3 = new Thread(t, "线程3");
11 
12         t1.start();
13         t2.start();
14         t3.start();
15     }
16 
17     static class ticket implements Runnable{
18         private int ticket =10;
19         Lock lock = new ReentrantLock();
20 
21         @Override
22         public void run() {
23             String name = Thread.currentThread().getName();
24             while(true){
25                 try {
26                     if ("线程1".equals(name)){
27                         synchronized (lock){//线程1获得lock锁
28                             sell(name);
29                         }
30                     }else{
31                         sell(name);
32                     }
33                 } catch (InterruptedException e) {
34                     e.printStackTrace();
35                 }
36                 if (ticket < 0){
37                     break;
38                 }
39 
40             }
41         }
42         //synchronized:同步方法
43         private synchronized void  sell(String name) throws InterruptedException {//线程2获取到this锁
44             Thread.sleep(1000);
45              synchronized (lock){
46                  if (ticket >0){
47                      System.out.println(name +"——"+ ticket);
48                      ticket --;
49                  }
50              }
51         }
52     }
53 }
View Code

5.线程的生命周期与状态

 

 

5.1.多线程的几种状态

    • 新建状态(New):
      用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。
    • 就绪状态(Runnable):
      当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
    • 运行状态(Running):
      处于这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。
    • 阻塞状态(Blocked):
      阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。
      • 阻塞状态可分为以下3种:
        • 位于对象等待池中的阻塞状态(Blocked in object’s wait pool):
          当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。
        • 位于对象锁池中的阻塞状态(Blocked in object’s lock pool):
          当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。
        • 其他阻塞状态(Otherwise Blocked):
          当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。
    • 死亡状态(Dead):
      当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。

查看Thread源码,能够看到java的线程有六种状态:

 NEW:线程刚被创建,但是并未启动。

RUNNABLE(可运行):

线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。


BLOCKED(锁阻塞):
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked态;当该线程持有锁时,该线程将变成Runnable状态。

WAITING(无限等待):
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。

TIMED_WAITING(计时等待):
waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleepObject.wait

TERMINATED(被终止):
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
 

5.2.线程控制

 jion方法:

 1 public class Demo12 {
 2     public static void main(String[] args) {
 3         ThreadJoin tj1 = new ThreadJoin();
 4         ThreadJoin tj2 = new ThreadJoin();
 5         ThreadJoin tj3 = new ThreadJoin();
 6         tj1.setName("康熙");
 7         tj2.setName("四阿哥");
 8         tj3.setName("八阿哥");
 9         tj1.start();
10         try {
11             //等线程tj1执行完成之后,其余线程方可执行
12             tj1.join();
13         } catch (InterruptedException e) {
14             e.printStackTrace();
15         }
16         tj2.start();
17         tj3.start();
18     }
19     public static class ThreadJoin extends Thread {
20         @Override
21         public void run() {
22             for (int i = 0; i < 5; i++) {
23                 System.out.println(getName() + ":" + i);
24             }
25         }
26     }
27 }
View Code

setDemon方法:

 1 public class Demo2 {
 2     public static void main(String[] args) {
 3         Thread t1 = new Thread(new demo());
 4         //Thread t2 = new Thread(new demo());
 5         
 6         //设置t2为守护线程
 7         //t2.setDaemon(true);
 8         //设置t1为守护线程
 9         t1.setDaemon(true);
10         t1.start();
11         //t2.start();
12     }
13     static class demo extends Thread{
14         public void run(){
15             String name = Thread.currentThread().getName();
16             for (int i = 0;i <4;i++){
17                 try {
18                     Thread.sleep(1000);
19                 } catch (InterruptedException e) {
20                     e.printStackTrace();
21                 }
22                 System.out.println(name +"的内容是"+i);
23             }
24         }
25     }
26 }
View Code

5.3.wait()notify()

wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态状态。
wait 方法会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
notify 方法会通知某个正在等待这个对象的控制权的线程继续运行。
notifyAll 方法会通知所有正在等待这个对象的控制权的线程继续运行。
注意:一定要在线程同步中使用,并且是同一个锁的资源

 1 /**
 2  * wait和notify方法例子,一个人进站出站:
 3  */
 4 public class Demo13 {
 5     public static void main(String[] args) {
 6         State state = new State();
 7         InThread inThread = new InThread(state);
 8         OutThread outThread = new OutThread(state);
 9         Thread in = new Thread(inThread);
10         Thread out = new Thread(outThread);
11         in.start();
12         out.start();
13     }
14 
15     // 控制状态
16     static class State {
17         //状态标识
18         public String flag = "车站内";
19     }
20 
21     static class InThread implements Runnable {
22         private State state;
23         public InThread(State state) {
24             this.state = state;
25         }
26         public void run() {
27             while (true) {
28                 synchronized (state) {
29                     if ("车站内".equals(state.flag)) {
30                         try {
31                             // 如果在车站内,就不用进站,等待,释放锁
32                             state.wait();
33                         } catch (Exception e) {
34                         }
35                     }
36 
37                 }
38                 System.out.println("进站");
39                 state.flag = "车站内";
40                 // 唤醒state等待的线程
41                 state.notify();
42             }
43         }
44     }
45 
46     static class OutThread implements Runnable {
47         private State state;
48 
49         public OutThread(State state) {
50             this.state = state;
51         }
52         public void run() {
53             while (true) {
54                 synchronized (state) {
55                     if ("车站外".equals(state.flag)) {
56                         try {
57                             //就不用出站了,等待,释放锁
58                             state.wait();
59                         } catch (Exception e) {
60                         }
61                     }
62                     System.out.println("出站");
63                     state.flag = "车站外";
64                     // 唤醒state等待的线程
65                     state.notify();
66                 }
67             }
68         }
69     }
70 }
View Code

5.4.waitsleep区别

  •  对于sleep()方法,首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的
  • sleep 控状态依然保持者,当指定的时间到了又会自动恢复运行状态。wait()是把控制权交出去,然后进入等待此对象的等待锁定池处于等待状态,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
  • 在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁 。

5.5.线程停止

结束线程有以下三种方法:
1)设置退出标志,使线程正常退出。
2)使用interrupt()方法中断线程。
3)使用stop方法强行终止线程(不推荐使用Thread.stop, 这种终止线程运行的方法已经被废弃,使用它们是极端不安全的!) 

(1)使用退出标志
一般run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为truefalse来控制while循环是否退出,代码示例: 

 1 public class Demo8Exit {
 2 
 3     public static boolean exit = true;
 4 
 5     public static void main(String[] args) throws InterruptedException {
 6         Thread t = new Thread(new Runnable() {
 7             public void run() {
 8                 while (exit) {
 9                     try {
10                         System.out.println("线程执行!");
11                         Thread.sleep(100l);
12                     } catch (InterruptedException e) {
13                         e.printStackTrace();
14                     }
15                 }
16             }
17         });
18         t.start();
19 
20         Thread.sleep(1000l);
21         exit = false;
22         System.out.println("退出标识位设置成功");
23     }
24 }
View Code

2)使用interrupt()方法中断线程

 1 public class Demo9Interrupt {
 2 
 3     public static boolean exit = true;
 4 
 5     public static void main(String[] args) throws InterruptedException {
 6         Thread t = new Thread(new Runnable() {
 7             public void run() {
 8                 while (exit) {
 9                     try {
10                         System.out.println("线程执行!");
11 
12                         //判断线程的中断标志来退出循环
13                         if (Thread.currentThread().isInterrupted()) {
14                             break;
15                         }
16 
17                         Thread.sleep(100l);
18                     } catch (InterruptedException e) {
19                         e.printStackTrace();
20                         //线程处于阻塞状态,当调用线程的interrupt()方法时,
21                         //会抛出InterruptException异常,跳出循环
22                         break;
23                     }
24                 }
25             }
26         });
27         t.start();
28 
29         Thread.sleep(1000l);
30         //中断线程
31         t.interrupt();
32         System.out.println("线程中断了");
33     }
34 }
View Code

6.线程优先级

6.1 优先级priority

现今操作系统基本采用分时的形式调度运行的线程,线程分配得到时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。 

 1 public class Demo10Priorityt {
 2 
 3     public static void main(String[] args) {
 4         PrioritytThread prioritytThread = new PrioritytThread();
 5 
 6         // 如果8核CPU处理3线程,无论优先级高低,每个线程都是单独一个CPU执行,就无法体现优先级
 7         // 开启10个线程,让8个CPU处理,这里线程就需要竞争CPU资源,优先级高的能分配更多的CPU资源
 8         for (int i = 0; i < 3; i++) {
 9             Thread t = new Thread(prioritytThread, "线程" + i);
10             if (i == 1) {
11                 t.setPriority(4);
12             }
13             if (i == 2) {
14                 t.setPriority(8);
15             }
16             t.setDaemon(true);
17             t.start();
18         }
19 
20         try {
21             Thread.sleep(1000l);
22         } catch (InterruptedException e) {
23             e.printStackTrace();
24         }
25 
26         System.out.println("线程1总计:" + PrioritytThread.count1);
27         System.out.println("线程2总计:" + PrioritytThread.count2);
28     }
29 
30     static class PrioritytThread implements Runnable {
31         public static Integer count1 = 0;
32         public static Integer count2 = 0;
33 
34         public void run() {
35             while (true) {
36                 if ("线程1".equals(Thread.currentThread().getName())) {
37                     count1++;
38                 }
39                 if ("线程2".equals(Thread.currentThread().getName())) {
40                     count2++;
41                 }
42                 if (Thread.currentThread().isInterrupted()) {
43                     break;
44                 }
45             }
46         }
47     }
48 }
View Code

6.2.join()方法

两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。6.3.yield方法 

6.3.yield方法

yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

7.线程的几个主要概念

在多线程编程时,你需要了解以下几个概念:

  • 线程同步
  • 线程间通信
  • 线程死锁
  • 线程控制:挂起、停止和恢复

 8.多线程并发的3个特性

多线程并发开发中,要知道什么是多线程的原子性,可见性和有序性,以避免相关的问题产生。
8.1 原子性

原子性: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。1000元之后,操作突然中止。这样就会导致账户A虽然减去了1000元,但是账户B没有收到这个转过来的1000元。
所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。
8.2 可见性

可见性: 当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即
看得到修改的值
举个简单的例子,看下面这段代码:

//线程1执行的代码
int i = 0;
i = 10;
//线程2执行的代码
j = i;

当线程1执行 int i = 0 这句时, i 的初始值0加载到内存中,然后再执行 i =10 ,那么在内存中 i 的值变为10了。
如果当线程1执行到 int i = 0 这句时,此时线程2执行 j = i,它读取 i 的值并加载到内存中,注意此时内存当中i的值是0,那么就会使得 j 的值也为0,而不是10。
这就是可见性问题,线程1对变量 i 修改了之后,线程2没有立即看到线程1修改的值

8.3.有序性

有序性: 程序执行的顺序按照代码的先后顺序执行

要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。 

原文地址:https://www.cnblogs.com/aaaazzzz/p/12783137.html