Java多线程并发编程

Thread和Runnable

  • Runnable接口可以避免继承自Thread类的单继承的局限性。
  • Runnable的代码可以被多个线程(Thread的实例)所共享,适合于多个线程共享资源(其实就是持有同一个runnable实例)的情况。

以火车站买票为例,分别以继承Thread类和实现Runnable接口这两种方式来模拟3个线程卖5张票:

  使用Thread类模拟卖票

 1 class MyThread extends Thread{
 2
 3     private int ticketCount = 5; // 5张票
 4     private String name;        // 窗口(线程的名字)
 5
 6     public MyThread(String name) {
 7         this.name = name;
 8     }
 9
10     @Override
11     public void run() {
12         while (ticketCount > 0) {
13             System.out.println(name + "窗口卖了1张票,剩余票数:" + --ticketCount);
14             try {
15                 Thread.sleep(50);
16             } catch (InterruptedException e) {
17                 e.printStackTrace();
18             }
19         }
20     }
21 }
22
23 public class TicketThreadTest {
24
25     public static void main(String[] args) {
26
27         new MyThread("窗口1").start(); // 创建了3个MyThread,每个线程有自己的独立的资源
28         new MyThread("窗口2").start();
29         new MyThread("窗口3").start();
30     }
31
32 }

  运行结果:

  使用Runnable接口模拟卖票

 1 class MyThread implements Runnable {
 2
 3     private int ticketCount = 5;
 4
 5     @Override
 6     public void run() {
 7         while (ticketCount > 0) {
 8             System.out.println(Thread.currentThread().getName()+"卖了1张票,剩余票数:" + --ticketCount);
 9             try {
10                 Thread.sleep(50);
11             } catch (InterruptedException e) {
12                 e.printStackTrace();
13             }
14         }
15     }
16
17 }
18
19 public class TicketThreadTest {
20
21     public static void main(String[] args) {
22
23         MyThread mt = new MyThread();
24         new Thread(mt,"窗口1").start(); // 3个线程使用的是同一个Runnable实例中的资源
25         new Thread(mt,"窗口2").start();
26         new Thread(mt,"窗口3").start();
27     }
28
29 }

  运行结果:

  之所以出现运行结果是4,2,3,1,0而不是4,3,2,1,0是因为线程将票数减去1之后还没有来得及打印又立即被其他线程抢占的CPU。如果要打印顺序一致,需要采用同步。

  在使用Runnable接口的时候需要注意的是:要想让所有的线程共享资源,传递给Thread的Runnable接口应该是同一个实例,例如以下的代码并不共享资源

1         MyThread mt1 = new MyThread();
2         MyThread mt2 = new MyThread();
3         MyThread mt3 = new MyThread();
4
5         new Thread(mt1,"窗口1").start();
6         new Thread(mt2,"窗口2").start();
7         new Thread(mt3,"窗口3").start();    // 3个线程有各自的资源

线程的生命周期

  • 创建:对应于new。
  • 就绪:调用start()方法。注意:此时线程仅仅是加入了就绪队列,等待获取CPU执行权,具备了运行的条件,但是不一定已经开始运行。
  • 运行:处于就绪状态的线程一旦获取了CPU便进行入了运行态,执行run()方法中的逻辑。
  • 终止:run()方法正常完成或者线程调用stop()方法。
  • 阻塞:一个运行中的线程由于某种原因让出了CPU资源,暂停了自己的执行,例如调用了sleep()、wait()、join()方法。

守护线程

  java中的线程分为2类。

  • 用户线程:运行在前台,执行具体的任务。例如:程序的主线程、连接网络的子线程都是用户线程。
  • 守护线程:运行在后台,为其他前台线程提供服务。一旦所有的用户线程都结束运行,守护线程就会随JVM一起结束工作。例如:数据库连接池中的监测线程,JVM启动后的监测线程、还有最常见的GC线程。

  设置守护线程只需要使用Thread类中提供的setDaemon(true)方法即可。注意:

  • 该方法必须在start()方法之前调用,否则会抛出IllegalThreadStateException。
  • 在守护线程中产生的新线程也会自动成为守护线程。
  • 并不是所有的任务都可以分配给守护线程来执行的,例如:读写操作或者计算逻辑。(因为它结束的时间是不可预知的)

  下面模拟这样一个场景:守护线程在很长的一段时间内不断向文件中写数据,主线程阻塞等待来自键盘的输入,一旦主线程获取到了用户的输入,这时候主线程的阻塞就会解除掉,主线程继续运行直到结束。一旦主线程结束,用户线程就没有了,这时候即使数据还没有写完,守护线程也会随JVM一起结束运行。

 1 import java.io.File;
 2 import java.io.FileOutputStream;
 3 import java.util.Scanner;
 4
 5 class DaemonThread implements Runnable{
 6
 7     @Override
 8     public void run() {
 9         System.out.println("进入守护线程:" + Thread.currentThread().getName());
10
11         try {
12             writeToFile();                                        // 向文件中写数据
13         } catch (Exception e) {
14             e.printStackTrace();
15         }
16
17         System.out.println("退出守护线程:" + Thread.currentThread().getName());
18     }
19
20     /**
21      * 在500秒之内不断向文件中写入内容
22      */
23     private void writeToFile() throws Exception{
24
25         final String LINE_SEPARATOR = System.getProperty("line.separator");
26         FileOutputStream fos = new FileOutputStream(new File("deamon.txt"), true);
27
28         int count = 0;
29         while (count < 500) {
30             fos.write((LINE_SEPARATOR + "word" + count).getBytes());
31             System.out.println("守护线程向文件中写入了world" + count++);
32             Thread.sleep(1000);                        // 每写一次休眠1s,保证线程不那么快结束
33         }
34         fos.close();
35     }
36
37 }
38
39 public class DaemonThreadTest {
40
41     public static void main(String[] args) {
42
43         System.out.println("进入主线程:" + Thread.currentThread().getName());
44
45         DaemonThread daemonThread = new DaemonThread();
46         Thread thread = new Thread(daemonThread);
47         thread.setDaemon(true);                            // 设置为守护线程
48         thread.start();
49
50         Scanner scanner = new Scanner(System.in);
51         scanner.next();                                    // 主线程阻塞,一旦执行了输入,主线程就会解除阻塞,打印出最下面语句,退出运行
52
53         System.out.println("退出主线程:" + Thread.currentThread().getName());
54     }
55
56 }

 

  由以上的演示可以知道:当用户线程停止运行,守护线程也就终止了。守护线程原先的意图是向文件中写入500次,但是我们在写入第7次的时候解除了键盘的阻塞,导致主线程结束(用户线程),守护线程自然而然结束了,仅仅向文件中写入了部分数据。

使用jstack生成线程快照

  

  jstack主要是用来生成JVM当前时刻线程的快照的(threaddump,即:当前进程中所有的线程信息),帮助我们分析出程序问题的原因,如:长时间停顿、CPU占用率过高等。

  找到进程的pid可以使用任务管理器——查看——选择列——勾选PID。

  运行前面的DaemonThreadTest,使用任务管理器查看pid。

  如果在Eclipse中运行,则进行的名字为javaw。记录下该PID在jstack中执行jstack -l pid.即生成了当前时刻的线程快照

可见性

  • 可见性:指的是一个线程对共享变量的修改能够被其他线程及时看到。
  • 共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。

Java内存模型(JMM)

  Java Memory Model描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。

  • 所有的变量都存储在主内存中。
  • 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)。

  JMM中的2条规定:

  • 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写。
  • 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。

  共享变量可见性原理的实现

  线程1对共享变量的修改要想被线程2及时看到,必须经过以下的2个步骤:

  1. 把工作内存1中更新过的共享变量刷新到主内存中。
  2. 将主内存中最新的共享变量的值更新到工作内存2中。

  java在语言层次(并不包括jdk1.5之后引入的java.util.concurrent实现可见性)上提供了2种可见性的实现:

  • synchronized
  • volatile
  • final(一旦赋值,不许更改)

synchronized实现可见性

  synchronized能够实现原子性(同步)和内存可见性。JMM中关于synchronized的2条规定:

  • 线程解锁前,必须把共享变量的最新值刷新到主内存中。——保证了线程退出同步块之后,主内存中的值最新。
  • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁需要是同一把锁)。

  有了以上的2条就能够保证线程解锁前对共享变量的修改在下次加锁时对其他线程是可见的。

线程执行互斥代码的过程

  1. 获得互斥所;
  2. 清空工作内存;
  3. 从主内存中拷贝变量的最新副本到工作内存;
  4. 执行锁内部的代码;
  5. 将更改后的共享变量的值刷新到主内存中;
  6. 释放互斥锁。

指令重排序

  代码的书写顺序与实际执行的顺序不同,指令重排序是编译器或者处理机为了提高程序性能而做的优化。指令重排序主要有3种:

  • 编译器优化重排序(编译器优化,调整代码顺序使其更符合处理机)
  • 指令集并行重排序(处理机优化,例如多核技术)
  • 内存系统重排序(处理机优化,主要针对读写缓存)

  指令重排序可能导致以下的结果:

as-if-serial语义

  无论如何重排序,程序的执行结果应该和代码顺序执行的结果一致。(java编译器、运行时和处理机都会保证Java在单线程下遵守as-if-serial语义)

  例如有以下的程序:

 1 package org.gpf;
 2
 3 public class SynchronizedDemo{
 4
 5     // 共享变量
 6     private boolean ready = false;
 7     private int result = 0;
 8     private int number = 1;
 9
10     /**
11      * 写操作
12      */
13     public void write(){
14         ready = true;                    // 1.1
15         number = 2;                        // 1.2
16     }
17     /** 读操作*/
18     public void read(){
19
20         if (ready)                        // 2.1
21             result = number * 3;        // 2.2
22         System.out.println("result的值是:" + result);
23     }
24
25     private class ReadWriteThread extends Thread{
26
27         private boolean flag;
28
29         /**
30          * 根据构造方法中传入的布尔值确定是读操作还是写操作
31          */
32         public ReadWriteThread(boolean flag) {
33             this.flag = flag;
34         }
35
36         @Override
37         public void run() {
38             if (flag)
39                 write();
40             else
41                 read();
42         }
43
44     }
45
46     public static void main(String[] args) {
47
48         SynchronizedDemo syncDemo = new SynchronizedDemo();
49
50         syncDemo.new ReadWriteThread(true).start();    // 启动线程进行写操作
51         try {
52             Thread.sleep(1000);
53         } catch (InterruptedException e) {
54             e.printStackTrace();
55         }
56         syncDemo.new ReadWriteThread(false).start();// 启动线程进行读操作
57     }
58 }

  假设关键语句的执行顺序是1.1-->2.1-->2.2-->1.2,则result = 3.

  假设关键语句的执行顺序是1.2-->2.1-->2.2-->1.1,则result = 0.

  类似以上的情况有很多,例如2.1和2.2就有可能进行重排序(因为2.1和2.2没有数据依赖关系,只有数据依赖关系才会禁止进行重排序)。2.1和2.2进行重排序后等同于如下的代码:

1 int mid = number * 3;
2 if (ready) {
3     result = mid;
4 }

导致共享变量不可见(线程不安全)的原因

  • 线程的交叉执行;
  • 重排序结合线程交叉执行;
  • 共享变量更新后的值没有在工作内存与主内存之间进行及时更新。

安全的代码应该是下面的:

 1 /**
 2  * 写操作
 3  */
 4 public synchronized void write(){
 5     ready = true;                    // 1.1
 6     number = 2;                      // 1.2
 7 }
 8
 9 /**
10  * 读操作
11  */
12 public synchronized void read(){
13
14     if (ready)                      // 2.1
15         result = number * 3;        // 2.2
16     System.out.println("result的值是:" + result);
17 }

  加入synchronized之所以能过解决内存可见性的原理:

  • synchronized相当于一把锁,保证了锁内部代码的原子性。正是由于原子性保证了线程不可能交叉执行。
  • synchronized保证了共享变量在工作内存和主内存之间进行及时更新。

  之所以出现不加synchronized关键字,打印的result的结果也是6这种情况是因为:synchronized关键字是通过2条JMM规范实现的内存可见性,如果有synchronized关键字就一定能过保证内存可见性,JMM并没有说不加synchronized关键字共享内存就一定不可见——即使不加synchronized关键字,也是有可能会有可见性(并且大多数情况下是这样,主要是编译器做了一些优化,揣摩程序的用途,实现我们想要的结果)。但是尽管如此,最好还是在并发编程的时候我们自己实现可见性。

volatile实现内存可见性

  volatile关键字能够保证volatile变量的可见性,但是不能保证volatile变量复合操作的原子性。

volatile实现内存可见性的原理

  • 深入来说:volatile是通过加入内存屏障和禁止重排序优化来实现的。

    • 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令,它会将CPU写缓冲区的缓存强制刷新到主内存中。
    • 对volatile变量执行读操作时,会在读操作后加入一条load屏障指令,强制使缓冲区的缓存失效。
  • 通俗地讲:volatile变量在每次线程访问时,都强迫从主内存中重读该变量的值,而当该变量变化时,又会强迫线程将最新的值刷新到主内存。这样在任何时刻,不同的线程总能看到该变量的最新值。  

线程写volatile变量的过程

  1. 改变线程工作内存中volatile变量副本的值;
  2. 将改变后的副本的值从工作内存刷新到主内存。

线程读volatile变量的过程

  1. 从主内存中读取volatile变量的最新值到线程的工作内存中;
  2. 从工作内存中读取volatile变量的副本。

volatile不能保证volatile变量复合操作的原子性

1 private volatile int number = 0;
2 number++;

  以上的代码并不是原子操作,以上的number++实际上有3个原子操作:①读取number的值;②将number的值加1;③写入最新的number的值。

 1 public class VolatileDemo {
 2
 3     private volatile int number = 0; //    定义volatile变量,改变对其他线程可见
 4
 5     public int getNumber() {
 6         return number;
 7     }
 8
 9     /**
10      * 该方法完成number的自增操作
11      */
12     public void increase(){
13         try {
14             Thread.sleep(100);
15         } catch (InterruptedException e) {
16             e.printStackTrace();
17         }
18         this.number++;    // 该操作不是原子操作
19     }
20
21     public static void main(String[] args)  {
22
23         final VolatileDemo volDemo = new VolatileDemo();
24
25         // 启动500个线程对number进行自增操作
26         for (int i = 0; i < 500; i++) {
27             new Thread(new Runnable() {
28
29                 @Override
30                 public void run() {
31                     volDemo.increase();
32                 }
33             }).start();
34         }
35
36         // 如果还有子线程在运行,主线程就让出CPU资源,直到所有的子线程全部运行完毕,主线程再继续运行
37         while (Thread.activeCount() > 1) {
38             Thread.yield();
39         }
40
41         System.out.println("number = " + volDemo.getNumber());
42     }
43 }

  如果volatile变量能够保证原子操作,那么500个线程运行结束number的值应该是500,运行以上程序:发现number的值可能不是500而是480~500左右.也就是说volatile并不能保证原子操作。

  程序分析:假设在某一时刻number的值是5

  但是在线程A的工作内存中number = 5.

  此时线程A和主内存中的number的值都是6.——出现了两个线程对number进行增加操作,但是number的值只增加了1,所以会出现以上的number < 500的情况。

解决方案(保证number++的原子性即可):

  • 使用synchronized关键字;
  • 使用JDK1.5提供的java.util.concurrent.locks.ReentrantLock;
  • 使用JDK1.5提供的java.util.concurrent.atomic.AtomicInteger。

解决方案一:使用synchronized关键字(此时就不需要使用volatile变量了)

 1 public class VolatileDemo {
 2
 3     private int number = 0; //    共享变量
 4
 5     public int getNumber() {
 6         return number;
 7     }
 8
 9     /**
10      * 该方法完成number的自增操作
11      */
12     public void increase(){
13
14         try {
15             Thread.sleep(100);
16         } catch (InterruptedException e) {
17             e.printStackTrace();
18         }
19
20         synchronized (this) {
21             this.number++;        // 缩小锁的粒度,直接加在方法上会导致等待时间过长
22         }
23     }
24
25     public static void main(String[] args)  {
26
27         final VolatileDemo volDemo = new VolatileDemo();
28
29         // 启动500个线程对number进行自增操作
30         for (int i = 0; i < 500; i++) {
31             new Thread(new Runnable() {
32
33                 @Override
34                 public void run() {
35                     volDemo.increase();
36                 }
37             }).start();
38         }
39
40         // 如果还有子线程在运行,主线程就让出CPU资源,直到所有的子线程全部运行完毕,主线程再继续运行
41         while (Thread.activeCount() > 1) {
42             Thread.yield();
43         }
44
45         System.out.println("number = " + volDemo.getNumber());
46     }
47 }

  注意:在以上的程序中同步放在number++上而不是放在同步方法上,原因是缩小锁的粒度,避免过长时间的等待。

解决方案二:使用jdk1.5之后的并发包中的可重锁

 1 import java.util.concurrent.locks.Lock;
 2 import java.util.concurrent.locks.ReentrantLock;
 3
 4 public class VolatileDemo {
 5
 6     private int number = 0;                 // 共享变量
 7     private Lock lock = new ReentrantLock();// 可重互斥锁
 8
 9     public int getNumber() {
10         return number;
11     }
12
13     /**
14      * 该方法完成number的自增操作
15      */
16     public void increase(){
17
18         try {
19             Thread.sleep(100);
20         } catch (InterruptedException e) {
21             e.printStackTrace();
22         }
23
24         lock.lock();        // 加锁
25         try {
26             this.number++;    // 将操作放在try中
27         } finally{
28             lock.unlock();    // 在finally中释放锁
29         }
30
31     }
32
33     public static void main(String[] args)  {
34
35         final VolatileDemo volDemo = new VolatileDemo();
36
37         // 启动500个线程对number进行自增操作
38         for (int i = 0; i < 500; i++) {
39             new Thread(new Runnable() {
40
41                 @Override
42                 public void run() {
43                     volDemo.increase();
44                 }
45             }).start();
46         }
47
48         // 如果还有子线程在运行,主线程就让出CPU资源,直到所有的子线程全部运行完毕,主线程再继续运行
49         while (Thread.activeCount() > 1) {
50             Thread.yield();
51         }
52
53         System.out.println("number = " + volDemo.getNumber());
54     }
55 }

  注意:在进行互斥资源的操作时先加锁,将操作放在try中,在finally中释放锁。

解决方案三:使用原子方式更新int的值(AtomicInteger)

 1 import java.util.concurrent.atomic.AtomicInteger;
 2
 3 public class VolatileDemo {
 4
 5     private AtomicInteger atomicInteger = new AtomicInteger(0);    // 共享变量,可以用原子方式更新的 int值
 6     public int getNumber() {
 7         return atomicInteger.get();
 8     }
 9
10     /**
11      * 该方法完成number的自增操作
12      */
13     public void increase(){
14
15         try {
16             Thread.sleep(100);
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20
21         atomicInteger.incrementAndGet();    // 以原子的方式进行自增
22
23     }
24
25     public static void main(String[] args)  {
26
27         final VolatileDemo volDemo = new VolatileDemo();
28
29         // 启动500个线程对number进行自增操作
30         for (int i = 0; i < 500; i++) {
31             new Thread(new Runnable() {
32
33                 @Override
34                 public void run() {
35                     volDemo.increase();
36                 }
37             }).start();
38         }
39
40         // 如果还有子线程在运行,主线程就让出CPU资源,直到所有的子线程全部运行完毕,主线程再继续运行
41         while (Thread.activeCount() > 1) {
42             Thread.yield();
43         }
44
45         System.out.println("number = " + volDemo.getNumber());
46     }
47 }

使用volatile的注意事项

  由于volatile只能保证可见性不能保证原子性,所以要安全使用volatile变量,必须同时满足以下条件:

  1. 对变量的写入操作不依赖当前值(例如:number++count*=5不满足条件,boolean变量满足条件);
  2. 该变量没有包含在具有其他变量的不变式中(例如low<high不满足)。

synchronized与volatile的比较

  • volatile不需要加锁,比synchronized更轻量级,不会阻塞线程;
  • 从内存性角度来讲,volatile读相当于加锁,volatile写相当于解锁

Q&A

  Q1:即使没有保证可见性的措施(没有同步、volatile和final),很多时候共享变量依然能够在主内存和工作内存见得到及时的更新?

  A1:一般只有在短时间内高并发的情况下才会出现变量得不到及时更新的情况,因为CPU在执行时会很快地刷新缓存,所以一般情况下很难看到这种问题。

PS:

  对64位的变量(long或者double)变量的读写可能不是原子操作,因为JMM允许JVM将没有被volatile修饰的64位数据类型的读写操作划分为2次32位的读写操作来进行,可能会导致读取“半个变量”的问题,解决方案是加volatile关键字。

时间: 05-22

Java多线程并发编程的相关文章

对JAVA多线程 并发编程的理解

对JAVA多线程并发编程的理解 Java多线程编程关注的焦点主要是对单一资源的并发访问,本文从Java如何实现支持并发访问的角度,浅析对并发编程的理解,也算是对前段时间所学的一个总结. 线程状态转换 Java语言定义了5中线程状态,在任何一个时间点,一个线程只能有且只有其中一种状态,这5中状态分别是: ?  新建(New):创建后尚未启动的线程处于这种状态 ?  运行(Runable):Runable包括了操作系统线程状态中的Running和Ready,也就是处于此状态的线程可能正在执行,也有可

java多线程并发编程与CPU时钟分配小议

我们先来研究下JAVA的多线程的并发编程和CPU时钟振荡的关系吧 老规矩,先科普 我们的操作系统在DOS以前都是单任务的 什么是单任务呢?就是一次只能做一件事 你复制文件的时候,就不能重命名了 那么现在的操作系统,我一边在这边写BLOG,一边听歌,一边开着QQ,一边…………………… 显然,现在的操作系统都是多任务的操作系统 操作系统对多任务的支持是怎么样的呢? 每打开一个程序,就启动一个进程,为其分配相应空间(主要是运行程序的内存空间) 这其实就支持并发运行了 CPU有个时钟频率,表示每秒能执行

java多线程 并发 编程

转自:http://www.cnblogs.com/luxiaoxun/p/3870265.html 一.多线程的优缺点 多线程的优点: 1)资源利用率更好 2)程序设计在某些情况下更简单 3)程序响应更快 多线程的代价: 1)设计更复杂 虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂.在多线程访问共享数据的时候,这部分代码需要特别的注意.线程之间的交互往往非常复杂.不正确的线程同步产生的错误非常难以被发现,并且重现以修复. 2)上下文切换的开销 当CPU从执行一个线程切

Java 多线程 并发编程 (转)

一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源. 2.Java标准库提供了进程和线程相关的API,进程主要包括表示进程的jav

Java 多线程 并发编程

转自: http://blog.csdn.net/escaflone/article/details/10418651 一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空

多线程并发编程

前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的执行单元线程本身依靠程序进行运行线程是程序中的顺序控制流,只能使用分配给程序的资源和环境 2 进程:执行中的程序一个进程至少包含一个线程 3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程 4 多线程:在一个程序中运行多个任务目的是更好地使用CPU资源 线程的实现 继承Thread类 在j

java 多线程并发问题总结

java 多线程并发主要通过关键字synchronized实现 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 二.然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该ob

《Java虚拟机并发编程》学习笔记

对<Java虚拟机并发编程>这本书真的是相见恨晚.以前对并发编程只是懂个皮毛,这本书让我对并发编程有了一个全新的认识.所以把书上的知识点做下笔记,以便以后复习使用. 并发与并行 仔细说来,并发和并行是两个不同的概念.但随着多核处理器的普及,并发程序的不同的线程往往会被编译器分配到不同处理器核心上,所以说并发编程与并行对于上层程序员来说是一样的. 并发的风险 饥饿 当一个线程等待某个需要运行时间很长或者永远无法完成的时间发发生,那么这个线程就会陷入饥饿状态.通常饥饿也会被叫做活锁. 解决饥饿的方

Java多线程并发技术

Java多线程并发技术 参考文献: http://blog.csdn.net/aboy123/article/details/38307539 http://blog.csdn.net/ghsau/article/category/1707779 http://www.iteye.com/topic/366591 JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式