当前位置:文档之家› 多线程总结

多线程总结

多线程总结
多线程总结

最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣。已经拟好了提纲,大概分为这几个主题: java线程安全,java垃圾收集,java并发包详细介绍,java profile和jvm性能调优。慢慢写吧。本人jameswxx原创文章,转载请注明出处,我费了很多心血,多谢了。关于java线程安全,网上有很多资料,我只想从自己的角度总结对这方面的考虑,有时候写东西是很痛苦的,知道一些东西,想用文字说清楚,却不是那么容易。我认为要认识 java线程安全,必须了解两个主要的点:java的内存模型,java的线程同步机制。特别是内存模型,java的线程同步机制很大程度上都是基于内存模型而设定的。从暂时写得比较仓促,后面会慢慢补充完善。

浅谈java内存模型

不同的平台,内存模型是不一样的,但是jvm的内存模型规范是统一的。java的多线程并发问题最终都会反映在java的内存模型上,所谓线程安全无非要控制多个线程对某个资源的有序访问或修改。java的内存模型,要解决两个主要的问题:可见性和有序性。我们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内存的。JVM定义了自己的内存模型,屏蔽了底层平台内存管理细节,对于java开发人员,要解决的是在jvm内存模型的基础上,如何解决多线程的可见性和有序性。

那么,何谓可见性?多个线程之间是不能互相传递数据通信的,它们之间的沟通只能通过共享变量来进行。Java内存模型(JMM)规定了jvm有主内存,主内存是多个线程共享的。当new一个对象的时候,也是被分配在主内存中,每个线程都有自己的工作内存,工作内存存储了主存的某些对象的副本,当然线程的工作内存大小是有限制的。当线程操作某个对象时,执行顺序如下:

(1) 从主存复制变量到当前工作内存 (read and load)

(2) 执行代码,改变共享变量值 (use and assign)

(3) 用工作内存数据刷新主存相关内容 (store and write) JVM规范定义了线程对主存的操作指令:read,load,use,assign,store,write。当一个共享便变量在多个线程的工作内存中都有副本时,如果一个线程修改了这个共享变量,那么其他线程应该能够看到这个被修改后的值,这就是多线程的可见性问题。

那么,什么是有序性呢?线程在引用变量时不能直接从主内存中引用,如果线程工作内存中没有该变量,则会从主内存中拷贝一个副本到工作内存中,这个过程为read-load,完成后线程会引用该副本。当同一线程再度引用该字段时,有可能重新从主存中获取变量副本(read-load-use),也有可能直接引用原来的副本 (use),也就是说 read,load,use顺序可以由JVM实现系统决定。

线程不能直接为主存中中字段赋值,它会将值指定给工作内存中的变量副本(assign),完成后这个变量副本会同步到主存储区(store- write),至于何时同步过去,根据JVM实现系统决定.有该字段,则会从主内存中将该字段赋值到工作内存中,这个过程为read-load,完成后线程会引用该变量副本,当同一线程多次重复对字段赋值时,比如:

for(int i=0;i<10;i++)

a++;

线程有可能只对工作内存中的副本进行赋值,只到最后一次赋值后才同步到主存储区,所以assign,store,weite顺序可以由JVM实现系统决定。假设有一个共享变量x,线程a执行x=x+1。从上面的描述中可以知道x=x+1并不是一个原子操作,它的执行过程如下:

1 从主存中读取变量x副本到工作内存

2 给x加1

3 将x加1后的值写回主存

如果另外一个线程b执行x=x-1,执行过程如下:

1 从主存中读取变量x副本到工作内存

2 给x减1

3 将x减1后的值写回主存

那么显然,最终的x的值是不可靠的。假设x现在为10,线程a加1,线程b减1,从表面上看,似乎最终x还是为10,但是多线程情况下会有这种情况发生:

1:线程a从主存读取x副本到工作内存,工作内存中x值为10

2:线程b从主存读取x副本到工作内存,工作内存中x值为10

3:线程a将工作内存中x加1,工作内存中x值为11

4:线程a将x提交主存中,主存中x为11

5:线程b将工作内存中x值减1,工作内存中x值为9

6:线程b将x提交到中主存中,主存中x为9

同样,x有可能为11,如果x是一个银行账户,线程a存款,线程b扣款,显然这样是有严重问题的,要解决这个问题,必须保证线程a和线程b是有序执行的,并且每个线程执行的加1或减1是一个原子操作。看看下面代码:

Java代码

1.public class Account {

2.

3. private int balance;

4.

5. public Account(int balance) {

6. this.balance = balance;

7. }

8.

9. public int getBalance() {

10. return balance;

11. }

12.

13. public void add(int num) {

14. balance = balance + num;

15. }

16.

17. public void withdraw(int num) {

18. balance = balance - num;

19. }

20.

21. public static void main(String[] args) throws InterruptedException {

22. Account account = new Account(1000);

23. Thread a = new Thread(new AddThread(account, 20), "add");

24. Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");

25. a.start();

26. b.start();

27. a.join();

28. b.join();

29. System.out.println(account.getBalance());

30. }

31.

32. static class AddThread implements Runnable {

33. Account account;

34. int amount;

35.

36. public AddThread(Account account, int amount) {

37. this.account = account;

38. this.amount = amount;

39. }

40.

41. public void run() {

42. for (int i = 0; i < 200000; i++) {

43. account.add(amount);

44. }

45. }

46. }

47.

48. static class WithdrawThread implements Runnable {

49. Account account;

50. int amount;

51.

52. public WithdrawThread(Account account, int amount) {

53. this.account = account;

54. this.amount = amount;

55. }

56.

57. public void run() {

58. for (int i = 0; i < 100000; i++) {

59. account.withdraw(amount);

60. }

61. }

62. }

63.}

Java代码

1.public class Account {

2.

3. private int balance;

4.

5. public Account(int balance) {

6. this.balance = balance;

7. }

8.

9. public int getBalance() {

10. return balance;

11. }

12.

13. public void add(int num) {

14. balance = balance + num;

15. }

16.

17. public void withdraw(int num) {

18. balance = balance - num;

19. }

20.

21. public static void main(String[] args) throws InterruptedException {

22. Account account = new Account(1000);

23. Thread a = new Thread(new AddThread(account, 20), "add");

24. Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");

25. a.start();

26. b.start();

27. a.join();

28. b.join();

29. System.out.println(account.getBalance());

30. }

31.

32. static class AddThread implements Runnable {

33. Account account;

34. int amount;

35.

36. public AddThread(Account account, int amount) {

37. this.account = account;

38. this.amount = amount;

39. }

40.

41. public void run() {

42. for (int i = 0; i < 200000; i++) {

43. account.add(amount);

44. }

45. }

46. }

47.

48. static class WithdrawThread implements Runnable {

49. Account account;

50. int amount;

51.

52. public WithdrawThread(Account account, int amount) {

53. this.account = account;

54. this.amount = amount;

55. }

56.

57. public void run() {

58. for (int i = 0; i < 100000; i++) {

59. account.withdraw(amount);

60. }

61. }

62. }

63.}

public class Account {

private int balance;

public Account(int balance) {

this.balance = balance;

}

public int getBalance() {

return balance;

}

public void add(int num) {

balance = balance + num;

}

public void withdraw(int num) {

balance = balance - num;

}

public static void main(String[] args) throws InterruptedException {

Account account = new Account(1000);

Thread a = new Thread(new AddThread(account, 20), "add");

Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");

a.start();

b.start();

a.join();

b.join();

System.out.println(account.getBalance());

}

static class AddThread implements Runnable {

Account account;

int amount;

public AddThread(Account account, int amount) {

this.account = account;

this.amount = amount;

}

public void run() {

for (int i = 0; i < 200000; i++) {

account.add(amount);

}

}

}

static class WithdrawThread implements Runnable {

Account account;

int amount;

public WithdrawThread(Account account, int amount) {

this.account = account;

this.amount = amount;

}

public void run() {

for (int i = 0; i < 100000; i++) {

account.withdraw(amount);

}

}

}

}

第一次执行结果为10200,第二次执行结果为1060,每次执行的结果都是不确定的,因为线程的执行顺序是不可预见的。这是java同步产生的根源,synchronized关键字保证了多个线程对于同步块是互斥的,synchronized作为一种同步手段,解决java多线程的执行有序性和内存可见性,而volatile关键字只解决多线程的内存可见性问题。后面将会详细介绍。

synchronized关键字

上面说了,java用synchronized关键字做为多线程并发环境的执行有序性的保证手段之一。当一段代码会修改共享变量,这一段代码成为互斥区或临界区,为了保证共享变量的正确性,synchronized标示了临界区。典型的用法如下:

synchronized(锁){

临界区代码

}

为了保证银行账户的安全,可以操作账户的方法如下:

刚才不是说了synchronized的用法是这样的吗:

synchronized(锁) {

临界区代码

}

那么对于public synchronized void add(int num)这种情况,意味着什么呢?其实这种情况,锁就是这个方法所在的对象。同理,如果方法是public static synchronized void add(int num),那么锁就是这个方法所在的class。

理论上,每个对象都可以做为锁,但一个对象做为锁时,应该被多个线程共享,这样才显得有意义,在并发环境下,一个没有共享的对象作为锁是没有意义的。假如有这样的代码:

Java代码

1.public class ThreadTest{

2. public void test(){

1. Object lock=new Object();

2. synchronized (lock){

3. //do something

4. }

5. }

6.}

Java代码

1.public class ThreadTest{

2. public void test(){

3. Object lock=new Object();

4. synchronized (lock){

5. //do something

6. }

7. }

8.}

public class ThreadTest{

public void test(){

Object lock=new Object();

synchronized (lock){

//do something

}

}

}

lock变量作为一个锁存在根本没有意义,因为它根本不是共享对象,每个线程进来都会执行Object lock=new Object();每个线程都有自己的lock,根本不存在锁竞争。

每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个被线程被唤醒 (notify)后,才会进入到就绪队列,等待cpu的调度。当一开始线程a第一次执行account.add方法时,jvm会检查锁对象account 的就绪队列是否已经有线程在等待,如果有则表明account的锁已经被占用了,由于是第一次运行,account的就绪队列为空,所以线程a获得了锁,执行account.add 方法。如果恰好在这个时候,线程b要执行account.withdraw方法,因为线程a已经获得了锁还没有释放,所以线程 b要进入account的就绪队列,等到得到锁后才可以执行。

一个线程执行临界区代码过程如下:

1 获得同步锁

2 清空工作内存

3 从主存拷贝变量副本到工作内存

4 对这些变量计算

5 将变量从工作内存写回到主存

6 释放锁

可见,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。

生产者/消费者模式

生产者/消费者模式其实是一种很经典的线程同步模型,很多时候,并不是光保证多个线程对某共享资源操作的互斥性就够了,往往多个线程之间都是有协作的。

假设有这样一种情况,有一个桌子,桌子上面有一个盘子,盘子里只能放一颗鸡蛋,A专门往盘子里放鸡蛋,如果盘子里有鸡蛋,则一直等到盘子里没鸡蛋,B专门从盘子里拿鸡蛋,如果盘子里没鸡蛋,则等待直到盘子里有鸡蛋。其实盘子就是一个互斥区,每次往盘子放鸡蛋应该都是互斥的,A的等待其实就是主动放弃锁,B 等待时还要提醒A放鸡蛋。

如何让线程主动释放锁

很简单,调用锁的wait()方法就好。wait方法是从Object来的,所以任意对象都有这个方法。看这个代码片段:

Java代码

1.Object lock=new Object();//声明了一个对象作为锁

2. synchronized (lock) {

3. balance = balance - num;

4. //这里放弃了同步锁,好不容易得到,又放弃了

5. lock.wait();

6.}

Java代码

1.Object lock=new Object();//声明了一个对象作为锁

2. synchronized (lock) {

3. balance = balance - num;

4. //这里放弃了同步锁,好不容易得到,又放弃了

5. lock.wait();

6.}

Object lock=new Object();//声明了一个对象作为锁

synchronized (lock) {

balance = balance - num;

//这里放弃了同步锁,好不容易得到,又放弃了

lock.wait();

}

如果一个线程获得了锁lock,进入了同步块,执行lock.wait(),那么这个线程会进入到lock 的阻塞队列。如果调用 lock.notify()则会通知阻塞队列的某个线程进入就绪队列。

声明一个盘子,只能放一个鸡蛋

Java代码

1.package com.jameswxx.synctest;

2.public class Plate{

1. List eggs=new ArrayList();

2. public synchronized Object getEgg(){

3. if(eggs.size()==0){

4. try{

5. wait();

6. }catch(InterruptedException e){

7. }

8. }

9.

10. Object egg=eggs.get(0);

11. eggs.clear();//清空盘子

12. notify();//唤醒阻塞队列的某线程到就绪队列

13. return egg;

14.}

15.

16. public synchronized void putEgg(Object egg){

17. If(eggs.size()>0){

18. try{

19. wait();

20. }catch(InterruptedException e){

21. }

22. }

23. eggs.add(egg);//往盘子里放鸡蛋

24. notify();//唤醒阻塞队列的某线程到就绪队列

25. }

26.}

Java代码

1.package com.jameswxx.synctest;

2.public class Plate{

3. List eggs=new ArrayList();

4. public synchronized Object getEgg(){

5. if(eggs.size()==0){

6. try{

7. wait();

8. }catch(InterruptedException e){

9. }

10. }

11.

12. Object egg=eggs.get(0);

13. eggs.clear();//清空盘子

14. notify();//唤醒阻塞队列的某线程到就绪队列

15. return egg;

16.}

17.

18. public synchronized void putEgg(Object egg){

19. If(eggs.size()>0){

20. try{

21. wait();

22. }catch(InterruptedException e){

23. }

24. }

25. eggs.add(egg);//往盘子里放鸡蛋

26. notify();//唤醒阻塞队列的某线程到就绪队列

27. }

28.}

package com.jameswxx.synctest;

public class Plate{

List eggs=new ArrayList();

public synchronized Object getEgg(){

if(eggs.size()==0){

try{

wait();

}catch(InterruptedException e){

}

}

Object egg=eggs.get(0);

eggs.clear();//清空盘子

notify();//唤醒阻塞队列的某线程到就绪队列

return egg;

}

public synchronized void putEgg(Object egg){

If(eggs.size()>0){

try{

wait();

}catch(InterruptedException e){

}

}

eggs.add(egg);//往盘子里放鸡蛋

notify();//唤醒阻塞队列的某线程到就绪队列

}

}

声明一个Plate对象为plate,被线程A和线程B共享,A专门放鸡蛋,B专门拿鸡蛋。假设1 开始,A调用plate.putEgg方法,此时eggs.size()为0,因此顺利将鸡蛋放到盘子,还执行了notify()方法,唤醒锁的阻塞队列的线程,此时阻塞队列还没有线程。

2 又有一个A线程对象调用plate.putEgg方法,此时eggs.size()不为0,调用wait()方法,自己进入了锁对象的阻塞队列。

3 此时,来了一个B线程对象,调用plate.getEgg方法,eggs.size()不为0,顺利的拿到了一个鸡蛋,还执行了notify()方法,唤醒锁的阻塞队列的线程,此时阻塞队列有一个A线程对象,唤醒后,它进入到就绪队列,就绪队列也就它一个,因此马上得到锁,开始往盘子里放鸡蛋,此时盘子是空的,因此放鸡蛋成功。

4 假设接着来了线程A,就重复2;假设来料线程B,就重复3。

整个过程都保证了放鸡蛋,拿鸡蛋,放鸡蛋,拿鸡蛋。

volatile关键字

volatile是java提供的一种同步手段,只不过它是轻量级的同步,为什么这么说,因为volatile只能保证多线程的内存可见性,不能保证多线程的执行有序性。而最彻底的同步要保证有序性和可见性,例如synchronized。任何被volatile修饰的变量,都不拷贝副本到工作内存,任何修改都及时写在主存。因此对于Valatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是有序的。什么意思呢?假如有这样的

代码:

Java代码

1.public class VolatileTest{

2. public volatile int a;

3. public void add(int count){

4. a=a+count;

5. }

6.}

Java代码

1.public class VolatileTest{

2. public volatile int a;

3. public void add(int count){

4. a=a+count;

5. }

6.}

public class VolatileTest{

public volatile int a;

public void add(int count){

a=a+count;

}

}

当一个VolatileTest对象被多个线程共享,a的值不一定是正确的,因为a=a+count 包含了好几步操作,而此时多个线程的执行是无序的,因为没有任何机制来保证多个线程的执行有序性和原子性。volatile存在的意义是,任何线程对a的修改,都会马上被其他线程读取到,因为直接操作主存,没有线程对工作内存和主存的同步。所以,volatile的使用场景是有限的,在有限的一些情形下可以使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

1)对变量的写操作不依赖于当前值。

2)该变量没有包含在具有其他变量的不变式中

Volatile只保证了可见性,所以Volatile适合直接赋值的场景,如

Java代码

1.public class VolatileTest{

2. public volatile int a;

1. public void setA(int a){

2. this.a=a;

3. }

4.}

Java代码

1.public class VolatileTest{

2. public volatile int a;

3. public void setA(int a){

4. this.a=a;

5. }

6.}

public class VolatileTest{

public volatile int a;

public void setA(int a){

this.a=a;

}

}

在没有volatile声明时,多线程环境下,a的最终值不一定是正确的,因为this.a=a;涉及到给a赋值和将a同步回主存的步骤,这个顺序可能被打乱。如果用volatile声明了,读取主存副本到工作内存和同步a到主存的步骤,相当于是一个原子操作。所以简单来说,volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。这是一种很简单的同步场景,这时候使用volatile的开销将会非常小。

Java代码

1.public synchronized void add(int num) {

2. balance = balance + num;

3.}

4.public synchronized void withdraw(int num) {

5. balance = balance - num;

6.}

Java代码

1.public synchronized void add(int num) {

2. balance = balance + num;

3.}

4.public synchronized void withdraw(int num) {

5. balance = balance - num;

6.}

Java多线程和输入输出流

班级:13科技2班学号:201324131225 姓名:许耿宁 Java多线程和输入输出流 一、实验目的: 1.熟悉利用Thread类建立多线程方法。 2.熟悉利用Thread接口建立多线程方法。 3.熟悉Java的文件读写机制,练习输入输出流的使用。 二、实验内容: 1.阅读下列程序,分析并上机检验其功能。 public class DelayRunnable implements Runnable{ private static int count=0; private int no; private int delay; public DelayRunnable(){ count++; no=count; } public void run(){ try{ for (int i=0;i<10;i++){ delay=(int)(Math.random()*5000); Thread.sleep(delay); System.out.println("Thread "+no+" with a delay "+delay); } }catch(InterruptedException e){} } } class MyRunnable{ public static void main(String args[]){ DelayRunnable r1 = new DelayRunnable();

DelayRunnable r2 = new DelayRunnable(); Thread thread1=new Thread(r1); Thread thread2=new Thread(r2); thread1.start(); thread2.start(); try{ Thread.sleep(1000); }catch(InterruptedException e){ System.out.println("Thread wrong"); } } } 2.将上列程序利用Runnable接口改写,并上机检验。 3.创建简单的程序ThreeThread.java,该程序将创建三个线程,每个线程应当显示它所运行的时间(可以考虑使用Date类或Calendar类)。 4.键盘输入10个整数,从小到大进行排序。 5.接收键盘输入的字符串,用FileInputStream类将字符串写入文件,用 FileOutputStream类读出文件内容显示在屏幕上。 6.将一个文本文件的内容按行读出,每读出一行就顺序加上行号,并写入到另一个文件中。 三、实验要求: 1.通过实验掌握Thread 、Runnable使用方法; 2.程序必须能够实现多线程; 3.程序必须能够完成题目要求; 4.通过实验掌握文件输入输出流的使用方法; 5.程序必须能够从键盘接收字符串并保存在文件中; 6.程序必须能够读出文件内容显示在屏幕上; 7.写出实验报告。 四、实验代码及截图: 第一题: 在编译器上运行程序得到截图所示结果:

多核与多线程技术的区别到底在哪里

多核与多线程技术的区别到底在哪里? 【导读】:毫无疑问的,“多核”、“多线程”此二词已快成为当今处理器架构设计中的两大显学,如同历史战国时代以“儒”、“墨”两大派的显学,只不过当年两大治世思想学派是争得你死我亡,而多核、多线程则是相互兼容并蓄,今日几乎任何处理器都朝同时具有多核多线程的路线发展迈进。毫无疑问的,“多核”、“多线程”此二词已快成为当今处理器架构设计中的两大显学,如同历史战国时代以“儒”、“墨”两大派的显学,只不过当年两大治世思想学派是争得你死我亡,而多核、多线程则是相互兼容并蓄,今日几乎任何处理器都朝同时具有多核多线程的路线发展迈进。 虽然两词到处可见,但可有人知此二者的实际差异?在执行设计时又是以何者为重?到底是该多核优先还是多线程提前?关于此似乎大家都想进一步了解,本文以下试图对此进行个中差异的解说,并尽可能在不涉及实际复杂细节的情形下,让各位对两者的机制观念与差别性有所理解。 行程早于线程 若依据信息技术的发展历程,在软件程序执行时的再细分、再切割的小型化单位上,先是有行程(Process),之后才有线程(Thread),线程的单位比行程更小,一个行程内可以有多个线程,在一个行程下的各线程,都是共享同一个行程所建立的内存寻址资源及内存管理机制,包括执行权阶、内存空间、堆栈位置等,除此之外各个线程自身仅拥有少许因为执行之需的变量自属性,其余都依据与遵行行程所设立的规定。 相对的,程序与程序之间所用的就是不同的内存设定,包括分页、分段等起始地址的不同,执行权阶的不同,堆栈深度的不同等,一颗处理器若执行了A行程后要改去执行B行程,对此必须进行内存管理组态的搬迁、变更,而这个搬迁若是在处理器内还好,若是在高速缓存甚至是系统主存储器时,此种切换、转移程序对执行效能的损伤就非常大,因为完成搬迁、切换程序的相同时间,处理器早就可以执行数十到上千个指令。 两种路线的加速思维 所以,想避免此种切换的效率损耗,可以从两种角度去思考,第一种思考就是扩大到整体运算系统的层面来解决,在一部计算机内设计、配置更多颗的处理器,然后由同一个操作系统同时掌控及管理多颗处理器,并将要执行的程序的各个程序,一个程序喂(也称:发派)给一颗处理器去执行,如此多颗同时执行,每颗处理器执行一个程序,如此就可以加快整体的执行效率。 当然!这种加速方式必须有一个先决条件,即是操作系统在编译时就必须能管控、发挥及运用多行程技术,倘若以单行程的系统组态来编译,那么操作系统就无法管控服务器内一颗以上的处理器,如此就不用去谈论由操作系统负责让应用程序的程序进行同时的多颗同时性的执行派送。 即便操作系统支持多程序,而应用程序若依旧只支持单程序,那情形一样是白搭,操作

4:一个经典的多线程同步问题汇总

一个经典的多线程同步问题 程序描述: 主线程启动10个子线程并将表示子线程序号的变量地址作为参数传递给子线程。子线程接收参数 -> sleep(50) -> 全局变量++ -> sleep(0) -> 输出参数和全局变量。 要求: 1.子线程输出的线程序号不能重复。 2.全局变量的输出必须递增。 下面画了个简单的示意图: 分析下这个问题的考察点,主要考察点有二个: 1.主线程创建子线程并传入一个指向变量地址的指针作参数,由于线程启动须要花费一定的时间,所以在子线程根据这个指针访问并保存数据前,主线程应等待子线程保存完毕后才能改动该参数并启动下一个线程。这涉及到主线程与子线程之间的同步。 2.子线程之间会互斥的改动和输出全局变量。要求全局变量的输出必须递增。这涉及到各子线程间的互斥。 下面列出这个程序的基本框架,可以在此代码基础上进行修改和验证。 //经典线程同步互斥问题 #include #include #include long g_nNum; //全局资源 unsigned int__stdcall Fun(void *pPM); //线程函数 const int THREAD_NUM = 10; //子线程个数 int main() { g_nNum = 0;

HANDLE handle[THREAD_NUM]; int i = 0; while (i < THREAD_NUM) { handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL); i++;//等子线程接收到参数时主线程可能改变了这个i的值} //保证子线程已全部运行结束 WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE); return 0; } unsigned int__stdcall Fun(void *pPM) { //由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来int nThreadNum = *(int *)pPM; //子线程获取参数 Sleep(50);//some work should to do g_nNum++; //处理全局资源 Sleep(0);//some work should to do printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum); return 0; } 运行结果:

JAVA线程程序设计(小时钟)实验报告(附完整代码)

线程程序设计 一、课题内容和要求 内容:设计和编写一个编写一个指针式时钟程序,应用线程实现时钟的走动。 要求:本实验旨在通过实验,培养学生将JAVA 线程的相关知识点(包括线程调度,线程同步等)有机结合并加以综合应用,在实验中设计多线程程序的能力。 二、设计思路分析 class Clock:一个指针式时钟的主类 class Layout: 添加窗口和时钟组件 class ClockPaint:定义时钟组件 三、概要设计 public class Clock extends JFrame { public static void main(String[] s) ; } class Layout extends JFrame { public Layout(); } class ClockPaint extends JPanel implements Runnable { int x, y, r; int h, m, s; double rad = Math.PI / 180; public ClockPaint(int x, int y, int r); public void paint(Graphics g); public void run(); } 时钟的绘制:

运行时钟: 四、详细设计 import java.awt.*; import javax.swing.*; import java.util.*; public class Clock extends JFrame { public static void main(String[] s) { new Layout(); } } class Layout extends JFrame {// 添加窗口和时钟组件public Layout() { ClockPaint cp = new ClockPaint(20, 20, 70); add(cp);

java线程学习总结

java线程学习总结1(java thread培训总结1) 1.线程中一些基本术语和概念 (2) 1.1线程的几个状态 (2) 1.2 Daemon线程 (2) 1.3锁的定义 (2) 1.4死锁 (2) 1.5.Java对象关于锁的几个方法 (3) 1.6锁对象(实例方法的锁) (3) 1.7类锁 (4) 1.8.线程安全方法与线程不安全方法 (4) 1.9类锁和实例锁混合使用 (4) 1.10锁的粒度问题 (4) 1.11.读写锁 (5) 1.12 volatile (5) 2.线程之间的通讯 (5) 2.1屏障 (6) 2.2.锁工具类 (6) 2.3.条件变量 (6) 3. Java线程调度 (7) 3.1 Java优先级 (7) 3.2. 绿色线程 (7) 3.3 本地线程 (7) 3.4 Windows本地线程 (7) 3.5线程优先级倒置与继承 (8) 3.6循环调度 (8) 4.线程池 (8) 5工作队列 (9) 6.参考资料 (10)

1.线程中一些基本术语和概念 1.1线程的几个状态 初始化状态 就绪状态 运行状态 阻塞状态 终止状态 1.2 Daemon线程 Daemon线程区别一般线程之处是:主程序一旦结束,Daemon线程就会结束。 1.3锁的定义 为了协调多个并发运行的线程使用共享资源才引入了锁的概念。 1.4死锁 任何多线程应用程序都有死锁风险。当一组线程中的每一个都在等待一个只 有该组中另一个线程才能引起的事件时,我们就说这组线程死锁了。换一个说法就是一组线程中的每一个成员都在等待别的成员占有的资源时候,就可以说这组线程进入了死锁。死锁的最简单情形是:线程 A 持有对象X 的独占锁,并且在等待对象Y 的锁,而线程 B 持有对象Y 的独占锁,却在等待对象X 的锁。除非有某种方法来打破对锁的等待(Java 锁定不支持这种方法),否则死锁的线程将永远等下去。

多线程总结

最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣。已经拟好了提纲,大概分为这几个主题: java线程安全,java垃圾收集,java并发包详细介绍,java profile和jvm性能调优。慢慢写吧。本人jameswxx原创文章,转载请注明出处,我费了很多心血,多谢了。关于java线程安全,网上有很多资料,我只想从自己的角度总结对这方面的考虑,有时候写东西是很痛苦的,知道一些东西,想用文字说清楚,却不是那么容易。我认为要认识 java线程安全,必须了解两个主要的点:java的内存模型,java的线程同步机制。特别是内存模型,java的线程同步机制很大程度上都是基于内存模型而设定的。从暂时写得比较仓促,后面会慢慢补充完善。 浅谈java内存模型 不同的平台,内存模型是不一样的,但是jvm的内存模型规范是统一的。java的多线程并发问题最终都会反映在java的内存模型上,所谓线程安全无非要控制多个线程对某个资源的有序访问或修改。java的内存模型,要解决两个主要的问题:可见性和有序性。我们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内存的。JVM定义了自己的内存模型,屏蔽了底层平台内存管理细节,对于java开发人员,要解决的是在jvm内存模型的基础上,如何解决多线程的可见性和有序性。 那么,何谓可见性?多个线程之间是不能互相传递数据通信的,它们之间的沟通只能通过共享变量来进行。Java内存模型(JMM)规定了jvm有主内存,主内存是多个线程共享的。当new一个对象的时候,也是被分配在主内存中,每个线程都有自己的工作内存,工作内存存储了主存的某些对象的副本,当然线程的工作内存大小是有限制的。当线程操作某个对象时,执行顺序如下: (1) 从主存复制变量到当前工作内存 (read and load) (2) 执行代码,改变共享变量值 (use and assign) (3) 用工作内存数据刷新主存相关内容 (store and write) JVM规范定义了线程对主存的操作指令:read,load,use,assign,store,write。当一个共享便变量在多个线程的工作内存中都有副本时,如果一个线程修改了这个共享变量,那么其他线程应该能够看到这个被修改后的值,这就是多线程的可见性问题。 那么,什么是有序性呢?线程在引用变量时不能直接从主内存中引用,如果线程工作内存中没有该变量,则会从主内存中拷贝一个副本到工作内存中,这个过程为read-load,完成后线程会引用该副本。当同一线程再度引用该字段时,有可能重新从主存中获取变量副本(read-load-use),也有可能直接引用原来的副本 (use),也就是说 read,load,use顺序可以由JVM实现系统决定。 线程不能直接为主存中中字段赋值,它会将值指定给工作内存中的变量副本(assign),完成后这个变量副本会同步到主存储区(store- write),至于何时同步过去,根据JVM实现系统决定.有该字段,则会从主内存中将该字段赋值到工作内存中,这个过程为read-load,完成后线程会引用该变量副本,当同一线程多次重复对字段赋值时,比如: for(int i=0;i<10;i++) a++; 线程有可能只对工作内存中的副本进行赋值,只到最后一次赋值后才同步到主存储区,所以assign,store,weite顺序可以由JVM实现系统决定。假设有一个共享变量x,线程a执行x=x+1。从上面的描述中可以知道x=x+1并不是一个原子操作,它的执行过程如下:

多核CPU面临的挑战与机遇

多核CPU面临的挑战与机遇 ——如何发挥多核CPU的性能 09计算机科学与技术一班 2009118231 樊如霞

多核CPU面临的挑战与机遇 ——如何发挥多核CPU的性能取代过去的单一中央处理器,计算机目前正在步入多核时代。尽管这项技术对我们而言并不是新鲜事物,但这是这种类型的体系架构首次大规模运用于商用个人电脑和服务器市场。这场变革将影响到每位计算机用户。多核技术的触角已经深入到服务器,笔记本电脑甚至游戏机控制台领域。从最终用户的角度来看,这种变革的影响是潜移默化的。程序设计者们发现要实现多核设计的性能也是一项充满挑战的艰巨任务,特别是现在还没有一劳永逸的办法和自动化技术能适应多核系统上运行的现行软件。 多核CPU就是基板上集成有多个单核CPU,早期PD双核需要北桥来控制分配任务,核心之间存在抢二级缓存的情况,后期酷睿自己集成了任务分配系统,再搭配操作系统就能真正同时开工,2个核心同时处理2“份”任务,速度快了,万一1个核心死机,起码另一个U还可以继续处理关机、关闭软件等任务。 与单核处理器相比,多核处理器在体系结构、软件、功耗和安全性设计等方面面临着巨大的挑战,但也蕴含着巨大的潜能。 CMP和SMT一样,致力于发掘计算的粗粒度并行性。CMP可以看做是随着大规模集成电路技术的发展,在芯片容量足够大时,就可以将大规模并行处理机结构中的SMP (对称多处理机)或DSM(分布共享处理机)节点集成到同一芯片内,各个处理器并行执行不同的线程或进程。在基于SMP结构的单芯片多处理机中,处理器之间通过片外Cache或者是片外的共享存储器来进行通信。而基于DSM结构的单芯片多处理器中,处理器间通过连接分布式存储器的片内高速交叉开关网络进行通信。 由于SMP和DSM已经是非常成熟的技术了,CMP结构设计比较容易,只是后端设计和芯片制造工艺的要求较高而已。正因为这样,CMP成为了最先被应用于商用CPU 的“未来”高性能处理器结构。 虽然多核能利用集成度提高带来的诸多好处,让芯片的性能成倍地增加,但很明显的是原来系统级的一些问题便引入到了处理器内部。

JAVA重点知识总结

CoreJava部分 1简述下java基本数据类型及所占位数,java基本数据类型:4类8种 整数类型:byte(1byte),short(2byte),int(4byte),long(8byte) 浮点类型:float(4byte),double(8byte) 字符类型:char(2byte) 逻辑类型:boolean(false/true1byte) 2说出5个启动时异常 ------RunTimeException ------NullPointerException ------ArrayIndexOutOfBoundsException ------ClassCastException ------NumberFormatException 3HashMap和HashTable的区别: 1HashMap允许空键值对,HashTable不允许 2HashMap不是线程安全的,HashTable是 3HashMap直接实现Map接口,HashTable继承Dictionary类 4.ArrayList,Vector,LinkedList存储性能和区别 它们都实现了List接口 ArrayList和Vector都是基于数组实现的 LinkedList基于双向循环链表(查找效率低,添加删除容易) ArrayList不是线程安全的而Vector是线程安全的,所有速度上ArrayList高于Vector 5.Collection和Collections的区别 Collection是集合类的上级接口,继承与他的接口主要有Set和List Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作。 6List、Map、Set三个接口,存取元素时,各有什么特点? List以特定次序来持有元素,可有重复元素。 Set无法持有重复元素,内部排序 Map保存key-value值,value可多值。 7final,finally,finalize的区别 Final用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承Finally是异常处理语句结构的一部分,表示总是执行 Finalize是Object类的一个方法,在垃圾收集时的其他资源回收,例如关闭文件等。8Overload和Override的区别。Overload的方法是否可以改变返回值的类型? 方法的重写Override和重载Overload是Java多态的不同表现。 重写Overriding是父类与子类之间多态的一种表现,方法名,参数列表返回值类型都得与父类的方法一致。 重载Overloading是一种类中多态的一种表现。重载的方法是可以改变返回值类型的。9用一句话总结一下冒泡排序 依次比较相邻的两个数,将小数放在前面,大数放在后面。 10实现线程安全的两种方式 1)synchronized方法:通过在方法声明加入synchronized关键字来声明synchronized方法

java学习整体总结

CoreJava部分 1 简述下java基本数据类型及所占位数,java基本数据类型:4类8种 整数类型:byte(1byte),short(2byte),int(4byte),long(8byte) 浮点类型:float(4byte),double(8byte) 字符类型:char(2byte) 逻辑类型:boolean(false/true 1byte) 2 说出5个启动时异常 RunTimeException ------NullPointerException ------ArrayIndexOutOfBoundsException ------ClassCastException ------NumberFormatException 3 HashMap 和HashTable的区别: 1HashMap 允许空键值对,HashTable不允许 2HashMap不是线程安全的,HashTable是 3HashMap直接实现Map接口,HashTable继承Dictionary类 4. ArrayList,Vector,LinkedList存储性能和区别 它们都实现了List接口 ArrayList和Vector都是基于数组实现的 LinkedList基于双向循环链表(查找效率低,添加删除容易) ArrayList不是线程安全的而Vector是线程安全的,所有速度上 ArrayList高于Vector 5. Collection和Collections的区别 Collection是集合类的上级接口,继承与他的接口主要有Set和List Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作。 6 List、Map、Set三个接口,存取元素时,各有什么特点? List以特定次序来持有元素,可有重复元素。 Set 无法持有重复元素,内部排序 Map保存key-value值,value可多值。 7 final,finally,finalize的区别 Final用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承 Finally 是异常处理语句结构的一部分,表示总是执行 Finalize 是Object类的一个方法,在垃圾收集时的其他资源回收,例如关闭文件等。 8 Overload和Override的区别。Overload的方法是否可以改变返回值的

浅谈多核CPU、多线程与并行计算

0.前言 最近发觉自己博客转帖的太多,于是决定自己写一个原创的。笔者用过MPI 和C#线程池,参加过比赛,有所感受,将近一年来,对多线程编程兴趣一直不减,一直有所关注,决定写篇文章,算是对知识的总结吧。有说的不对的地方,欢迎各位大哥们指正:) 1.CPU发展趋势 核心数目依旧会越来越多,依据摩尔定律,由于单个核心性能提升有着严重的瓶颈问题,普通的桌面PC有望在2017年末2018年初达到24核心(或者16核32线程),我们如何来面对这突如其来的核心数目的增加?编程也要与时俱进。笔者斗胆预测,CPU各个核心之间的片内总线将会采用4路组相连:),因为全相连太过复杂,单总线又不够给力。而且应该是非对称多核处理器,可能其中会混杂几个DSP处理器或流处理器。 2.多线程与并行计算的区别 (1)多线程的作用不只是用作并行计算,他还有很多很有益的作用。 还在单核时代,多线程就有很广泛的应用,这时候多线程大多用于降低阻塞(意思是类似于 while(1) { if(flag==1) break;

sleep(1); } 这样的代码)带来的CPU资源闲置,注意这里没有浪费CPU资源,去掉sleep(1)就是纯浪费了。 阻塞在什么时候发生呢?一般是等待IO操作(磁盘,数据库,网络等等)。此时如果单线程,CPU会干转不干实事(与本程序无关的事情都算不干实事,因为执行其他程序对我来说没意义),效率低下(针对这个程序而言),例如一个IO操作要耗时10毫秒,CPU就会被阻塞接近10毫秒,这是何等的浪费啊!要知道CPU是数着纳秒过日子的。 所以这种耗时的IO操作就用一个线程Thread去代为执行,创建这个线程的函数(代码)部分不会被IO操作阻塞,继续干这个程序中其他的事情,而不是干等待(或者去执行其他程序)。 同样在这个单核时代,多线程的这个消除阻塞的作用还可以叫做“并发”,这和并行是有着本质的不同的。并发是“伪并行”,看似并行,而实际上还是一个CPU在执行一切事物,只是切换的太快,我们没法察觉罢了。例如基于UI 的程序(俗话说就是图形界面),如果你点一个按钮触发的事件需要执行10秒钟,那么这个程序就会假死,因为程序在忙着执行,没空搭理用户的其他操作;而如果你把这个按钮触发的函数赋给一个线程,然后启动线程去执行,那么程序就不会假死,继续响应用户的其他操作。但是,随之而来的就是线程的互斥和同步、死锁等问题,详细见有关文献。 现在是多核时代了,这种线程的互斥和同步问题是更加严峻的,单核时代大都算并发,多核时代真的就大为不同,为什么呢?具体细节请参考有关文献。我

java并发编程艺术总结.

1、并发编程的挑战 上下文切换:CPU通过时间片分配算法来循环执行任务,在切换任务的过程中,会保存上一个任务的状态,以便在下次切换回这个任务时,可以再加载这个任务的状态。 减少上下文切换的方法:无锁并发编程、CAS算法、使用最少线程和使用协程 2、Java并发机制的底层实现原理 Java代码编译后java字节码然后加载到JVM然后转化为CUP执行的汇编,java的并发依赖于JVM的实现与CPU的指令。 1. Volatile的应用 可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。 后面还是详细介绍volatile关键字 2. synchronized的实现原理与应用 1) synchronized简介 synchronized在JVM中实现,JVM基于进入与退出Monitor对象来实现方法同步与代码块同步,在同步代码块前后分别形成monitorenter和monitorexit这两个字节码。synchronized的锁存放在java对象头里,在对象头里有关于锁的信息:轻量级锁,重量级锁,偏向锁。(对象头里还包括:GC标记、分代年龄、线程ID、HashCode等。) 2) 锁的介绍 级别从低到高:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,锁能升级不能降级,目的是提高获取锁和释放锁的效率。 偏向锁: 在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。

为了让线程获得锁的代价更低而引入了偏向锁。 当一个线程访问同步块并获取锁(对象)时,会在对象头里记录偏向锁的线程ID。以后该线程进入与退出同步块时不需要进行CAS操作来加锁和解锁。如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。 轻量级锁: 线程通过CAS来获取锁(线程栈帧中有存储锁记录的空间,将Mask Word复制到锁记录中,然后尝试使用CAS将对象头中的Mask Word替换成指向锁记录的指针),如果成功,就获取锁,失败就尝试自旋来获取锁。 重量级锁: 为了避免在轻量级中无用的自旋(比如获取到锁的线程被阻塞住了),JVM可以将锁升级成重量级。当锁处于这个状态时,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程。 锁优点缺点使用场景 偏向锁加锁与解锁不需要 额外的消耗。线程存在竞争时, 会带来额外的锁撤 销的消耗 适用于只有一个 线程访问同步块 轻量级锁竞争的线程不会阻 塞,提高了程序的 响应速度始终得不到锁竞争 的线程,自旋消耗 CPU 追求响应时间, 同步块执行速度 非常快 重量级锁线程竞争不使用自 旋,不会消耗CPU 线程阻塞,响应时 间缓慢 追求吞吐量,同 步块执行时间较 长 3. 原子操作的实现原理 原子:不能被中断的一个或一系列操作。 在java中可以通过锁和循环CAS的方式来实现原子操作。 1) 使用循环CAS实现原子操作 利用处理器提供的CAS指令来实现,自旋CAS现在的基本思路就是循环进

多核多线程期末考试复习原题

多核编程 一、不定项选择(每题4分) 计算机的硬件工艺发展顺序是:(A) A.电子管数字计算机、晶体管数字计算机、集成电路数字计算机、大规模集成电路数字计算机 B.晶体管数字计算机、电子管数字计算机、集成电路数字计算机、大规模集成电路数字计算机 C.电子管数字计算机、集成电路数字计算机、大规模集成电路数字计算机、晶体管数字计算机 D.电子管数字计算机、集成电路数字计算机、晶体管数字计算机、大规模集成电路数字计算机 下面关于Intel 8086芯片于8088芯片的描述,不正确的是:(D) A. 8086是第一个16位的微处理器。 B. 8088是第一个16位的微处理器。 C. 8086每周期能传送或接收16位数据 D. 8088每周期能传送或接收16位数据 针对内存的速度瓶颈,英特尔为80386设计了_______来解决这个速度瓶颈:(B) A. 虚拟86 B. 高速缓存(Cache) C. 浮点运算单元 D. 多媒体扩展指令集 对一个具体的问题做性能优化时,可以同时在这多个层次上考虑可能的优化手段,一般说来:(AB) A. 在越高的层次上进行优化,可能获得的效益越高 B. 在越低的层次上进行优化工作则相对越容易实现 C. 在越高的层次上进行优化,可能获得的效益越低 D. 在越低的层次上进行优化工作则相对越难于实现 VTune性能分析器中的取样功能有哪几种方式?(AC) A. 基于时间取样 B. 随机取样 C. 基于事件取样 D. 线性取样 Intel调优助手能够给我们自动推荐代码改进办法,主要有以下哪些方面?(BCD) A. 算法自动改进 B. 处理器瓶颈以及改进 C. 取样向导增强 D. 超线程 使用-O3编译选项所得的程序,执行效率比使用-O2编译选项所得的程序_______。(D) A. 高 B. 低 C. 一样 D. 不一定 对于函数调用的边际效应,以下表述不正确的是:(B)

走下神坛的内存调试器--定位多线程内存越界问题实践总结

定位多线程内存越界问题实践总结 2013/2/4 杨志丰yangzhifeng83@https://www.doczj.com/doc/4414317525.html, 关键字多线程,内存越界,valgrind,electric-fence,mprotect,libsigsegv,glibc 最近定位了在一个多线程服务器程序(OceanBase MergeServer)中,一个线程非法篡改另一个线程的内存而导致程序core掉的问题。定位这个问题花了整整一周的时间,期间历经曲折,尝试了各种内存调试的办法。往往感觉就要柳暗花明了,却发现又进入了另一个死胡同。最后,使用强大的mprotect+backtrace+libsigsegv等工具成功定位了问题。整个定位过程遇到的问题和解决办法对于多线程内存越界问题都很典型,简单总结一下和大家分享。只对终极组合秘技感兴趣的同学,请直接阅读最后一节,其他的章节写到这里是为了科普。 现象 core是在系统集成测试过程中发现的。服务器程序MergeServer有一个50个工作线程组成的线程池,当使用8个线程的测试程序通过MergeServer读取数据时,后者偶尔会core 掉。用gdb查看core文件,发现core的原因是一个指针的地址非法,当进程访问指针指向的地址时引起了段错误(segment fault)。见下图。 发生越界的指针ptr_位于一个叫做cname_的对象中,而这个对象是一个动态数组field_columns_的第10个元素的成员。如下图。 复现问题 之后,花了2天的时间,终于找到了重现问题的方法。重现多次,可以观察到如下一些现象: 1.随着客户端并发数的加大(从8个线程到16个线程),出core的概率加大; 2.减少服务器端线程池中的线程数(从50个到2个),就不能复现core了。

多核多线程

多核多线程复习资料 1. 什么是CMP 单芯片多处理器(Chip Multiprocessors,简称CMP),将大规模并行处理器中的SMP (对称多处理器)集成到同一芯片内,各个理器并行执行不同的进程。 2 .并行计算的菲林分类 单指令流单数据流(Si ngle In struction stream Si ngle Data stream, SISD) 单指令流多数据流(Single Instruction stream Multiple Data stream, SIMD ) 多指令流单数据流( Multiple Instruction stream Single Data stream, MISD ) 多指令流多数据流( Multiple In struction stream Multiple Data stream, MIMD ) 3?进程和线程的区别 进程是正在被执行的程序,是系统进行资源分配和调度的一个独立单位,是一个动态的概念。线程是程序的有序控制流,是被执行的指令序列。 线程属于进程,线程运行在进程空间内。 4?线程的特点 线程因创建而产生,因调度而处于运行状态,因等待资源或事件而处于阻塞状态。 5?分解模式 1?任务分解---园丁修理草坪2?数据分解---矩阵相乘&园丁修理草坪 3?数据流分解---园丁修理草坪 将一个复杂的过程划分成多个任务,这些任务按照某种顺序执行,这种分解方式成为数据流 分解。 ---要求:理解,分析,举例 总结:任务分解下分工工作,相互配合;数据分解模式下分工明确,互不干扰;数据流分解模式下前一个工作是后一个工作开始的前提。 6 ?理解处理任务之间的数据依赖关系的战略有什么? 变量本地化:最简单的解决方案就是创建,初始化,并使用局部变量 改造变量(把共享变量改造成不共享的变量) 规约:创建线程1和线程2的局部变量suml和sum2,然后suml初始化,并计算循环的奇数迭代,sum2初始化和计算循环的偶数迭代。每个线程都独立计算其迭代。在循环结束时,主线程可以组合规约变量的总和( sum1+ sum2 )。 7 ?常用的同步机制

C++ 多线程编程总结

C++ 多线程编程总结 在开发C++程序时,一般在吞吐量、并发、实时性上有较高的要求。设计C++程序时,总结起来可以从如下几点提高效率: ?l 并发 ?l 异步 ?l 缓存 下面将我平常工作中遇到一些问题例举一二,其设计思想无非以上三点。 1任务队列 1.1 以生产者-消费者模型设计任务队列 生产者-消费者模型是人们非常熟悉的模型,比如在某个服务器程序中,当User数据被逻辑模块修改后,就产生一个更新数据库的任务(produce),投递给IO模块任务队列,IO模块从任务队列中取出任务执行sql操作(consume)。 设计通用的任务队列,示例代码如下: 详细实现可参见 1.2 任务队列使用技巧

注意,此模式下为多任务队列,每个任务队列单线程。 1.2.3 连接池与异步回调 比如逻辑Service模块需要数据库模块异步载入用户数据,并做后续处理计算。而数据库模块拥有一个固定连接数的连接池,当执行SQL的任务到来时,选择一个空闲的连接,执行SQL,并把SQL 通过回调函数传递给逻辑层。其步骤如下: ?n 预先分配好线程池,每个线程创建一个连接到数据库的连接 ?n 为数据库模块创建一个任务队列,所有线程都是这个任务队列的消费者 ?n 逻辑层想数据库模块投递sql执行任务,同时传递一个回调函数来接受sql执行结果 注意,此模式下为单任务队列,每个任务队列多线程。 2. 日志

本文主要讲C++多线程编程,日志系统不是为了提高程序效率,但是在程序调试、运行期排错上,日志是无可替代的工具,相信开发后台程序的朋友都会使用日志。常见的日志使用方式有如下几种: ?n 流式,如logstream << "start servie time[%d]" << time(0) << " app name[%s]" << app_string.c_str() << endl; ?n Printf 格式如:logtrace(LOG_MODULE, "start servie time[%d] app name[%s]", time(0), app_string.c_str()); 二者各有优缺点,流式是线程安全的,printf格式格式化字符串会更直接,但缺点是线程不安全,如果把app_string.c_str() 换成app_string (std::string),编译被通过,但是运行期会crash(如果运气好每次都crash,运气不好偶尔会crash)。我个人钟爱printf风格,可以做如下改进: ?l 增加线程安全,利用C++模板的traits机制,可以实现线程安全。示例: 这样,除了标准类型+std::string 传入其他类型将编译不能通过。这里只列举了一个参数的例子,可以重载该版本支持更多参数,如果你愿意,可以支持9个参数或更多。 ?l 为日志增加颜色,在printf中加入控制字符,可以再屏幕终端上显示颜色,Linux下示例:printf("\033[32;49;1m [DONE] \033[39;49;0m") 更多颜色方案参见: ?l 每个线程启动时,都应该用日志打印该线程负责什么功能。这样,程序跑起来的时候通过top –H – p pid 可以得知那个功能使用cpu的多少。实际上,我的每行日志都会打印线程id,此线程id 非pthread_id,而其实是线程对应的系统分配的进程id号。 3. 性能监控 尽管已经有很多工具可以分析c++程序运行性能,但是其大部分还是运行在程序debug阶段。我们需要一种手段在debug和release阶段都能监控程序,一方面得知程序瓶颈之所在,一方面尽早发现哪些组件在运行期出现了异常。 通常都是使用gettimeofday 来计算某个函数开销,可以精确到微妙。可以利用C++的确定性析构,非常方便的实现获取函数开销的小工具,示例如下 struct profiler{ profiler(const char* func_name){ gettimeofday(&tv, NULL);

Java线程总结

Java线程总结 在论坛上面常常看到初学者对线程的无可奈何,所以总结出了下面一篇文章,希望对一些正在学习使用java线程的初学者有所帮助。 首先要理解线程首先需要了解一些基本的东西,我们现在所使用的大多数操作系统都属于多任务,分时操作系统。正是由于这种操作系统的出现才有了多线程这个概念。我们使用的w indows,linux就属于此列。什么是分时操作系统呢,通俗一点与就是可以同一时间执行多个程序的操作系统,在自己的电脑上面,你是不是一边听歌,一边聊天还一边看网页呢?但实际上,并不上c pu在同时执行这些程序,c pu只是将时间切割为时间片,然后将时间片分配给这些程序,获得时间片的程序开始执行,不等执行完毕,下个程序又获得时间片开始执行,这样多个程序轮流执行一段时间,由于现在c pu 的高速计算能力,给人的感觉就像是多个程序在同时执行一样。 一般可以在同一时间内执行多个程序的操作系统都有进程的概念。一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间,一组系统资源。在进程概念中,每一个进程的内部数据和状态都是完全独立的。因此可以想像创建并执行一个进程的系统开像是比较大的,所以线程出现了。在java中,程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务。多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行。(你可以将前面一句话的程序换成进程,进程是程序的一次执行过程,是系统运行程序的基本单位) 线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈。所以系统在产生一个线程。或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程也被称为轻负荷进程(light-w eight proc ess)。一个进程中可以包含多个线程。 多任务是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程,同进程一样,一个线程也有从创建,运行到消亡的过程,称为线程的生命周期。用线程的状态(state)表明线程处在生命周期的哪个阶段。线程有创建,可运行,运行中,阻塞,死亡五中状态.通过线程的控制与调度可使线程在这几种状态间转化每个程序至少自动拥有一个线程,称为主线程。当程序加载到内存时,启动主线程。 [线程的运行机制以及调度模型] java中多线程就是一个类或一个程序执行或管理多个线程执行任务的能力,每个线程可以独立于其他线程而独立运行,当然也可以和其他线程协同运行,一个类控制着它的所有线程,可以决定哪个线程得到优先级,哪个线程可以访问其他类的资源,哪个线程开始执行,哪个保持休眠状态。 下面是线程的机制图: 线程的状态表示线程正在进行的活动以及在此时间段内所能完成的任务.线程有创建,可运行,运行中,阻塞,死亡五中状态。一个具有生命的线程,总是处于这五种状态之一: 1.创建状态 使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程处于创建状态(new thread) 2.可运行状态

多线程实验报告

宁波工程学院电信学院计算机教研室 实验报告 课程名称: Java 2 实验项目: 多线程实验 指导教师: **** 实验位置: 电信楼机房 姓 名: *** 学 号: **** 班 级: **** 日 期: 一、实验目的 1、掌握多线程编程的特点和工作原理; 2、掌握编写线程程序的方法 3、了解线程的调度和执行过程 4、掌握线程同步机理 二、实验环境 windows 记事本,java jdk 1.60版本,cmd 命令运行窗口 三、实验内容 实验一: 应用Java 中线程的概念写一个Java 程序(包括一个测试线程程序类TestThread ,一个Thread 类的子类PrintThread )。在测试程序中用子类PrintThread 创建2个线程,使得其中一个线程运行时打印10次“线程1正在运行”,另一个线程运行时打印5次“线程2正在运行 源程序: public class A { public static void main(String args[]) {

Test1 A1; Test2 A2; A1=new Test1(); A2=new Test2(); A1.start(); A2.start(); } } class PrintThread extends Thread { } class Test1 extends PrintThread { public void run() { for(int i=1;i<=10;i++) { System.out.println("线程1正在运行!"); } } } class Test2 extends PrintThread { public void run() { for(int i=1;i<=5;i++) { System.out.println("线程2正在运行!"); } } } 运行结果:

文本预览