同步、异步、多线程

1、首先明确一点,对于单核CPU,任意一个时刻只有一个线程在运行。那么既然这样,多线程还有什么意义呢? 举例来说,现在只有一个人,要做好几个任务。单线程就是,任务一个一个地做,必须做完一个任务后,再去做另一个任务。多线程就是一会做这个任务,一会做那个任务,每个任务做一会,不停的切换。显然,最后把所有的任务做完,多线程必定比单线程更耗费时间。为什么?因为,多线程要在不同的任务之间切换,切换肯定是要耗费时间的。那么问题来了,既然多线程比单线程更耗费时间,为什么还要多线程? 单线程有一个致命的问题,就是一个线程运行的整个过程中,其他线程必须等待,不能响应用户的命令,用户体验太差,好像电脑死机一样。假如单线程,你能想象一下,用户在听歌的时候不能写文档,这种体验也太差了。多线程的时候,单核CPU一会做这个任务,一会做那个任务,切换的时间是毫秒级的,用户完全感觉不出来。从而给用户照成错觉,感觉这些任务并行的运行。

2、同步的使用场景:多个线程同时访问一块数据,也叫共享区。对于多个线程同时访问一块数据的时候,必须使用同步,否则可能会出现不安全的情况。比如数据库中的脏读。但是,多个线程同时访问一块数据,有一种情况不需要同步技术,那就是原子操作,也就是说操作系统在底层保证了操作要么全部做完,要么不做。

3、异步的使用场景:只有一个线程访问当前的数据。比如,观察者模式,没有共享区,主题发生变化,通知观察者更新,主题继续做自己的事情,不需要等待观察者更新完成后再工作。

Java自带的Future多线程模式

在Java5后,提供了大量处理多线程的接口,以前只是简单的使用其线程池,最近发现Future模式也有。

只贴出了部分代码:

定义池:

  1. private static final ExecutorService worker = Executors.newFixedThreadPool(N);// 线程池
  2.     private static List<Future<?>> futureList = new ArrayList<Future<?>>();// 工作中的线程

使用线程池:

  1. futureList.add(worker.submit(this));

结束线程:

  1. // 结束线程池中的线程执行(中断)
  2.     public static void cancel() {
  3.         for (Future<?> f : futureList) {
  4.             f.cancel(true);
  5.         }
  6.     }

注:Future为线程的执行结果票据,当使用Callable方式执行时可以得到线程的执行结果f.get(),同时也可以控制某线程的结束和执行状态。当使用Runnable方式执行时,得到结果是空,但也可以对线程进行控制。

补充:
应该是调用了线程的中断方法Thread.currentThread().interrupt();但并不像stop方法那样立即结束掉子线程,而是改变了中断的信号量Thread.interrupted(),在阻塞的线程会抛出InterruptedException异常,但是在非阻塞的条件下子线程会继续执行,需要在循环中自己判断信号量来抛出异常。

原文链接:http://sunnymoon.iteye.com/blog/1260604

Java多线程同步之static、volatile、synchronized用处比较

程序1:
public class VolatileText extends Thread
{
public static volatile int a = 0;
public static int b = 0;
public static volatile int c = 0;

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
VolatileText vt[] = new VolatileText[1000];
for(int i=0;i<vt.length;i++)
{
vt[i] = new VolatileText();
//vt[i].start();
}
for(int i=0;i<vt.length;i++)
{
vt[i].start();
}
for(int i=0;i<vt.length;i++)
{
try {
vt[i].join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

System.out.println(
“VolatileText.a = ” + VolatileText.a + “\n”
+ “VolatileText.b = ” + VolatileText.b + “\n”
+ “VolatileText.c = ” + VolatileText.c);
}

public void run()
{
inc();
synInc();
synVolInc();
}

public void inc()
{
for(int i=0;i<10;i++)
{
a++;
try {
sleep(3);
} catch (InterruptedException e) {

e.printStackTrace();
}
}
}

public synchronized void synInc()
{
for(int i=0;i<10;i++)
{
b++;
try {
sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void synVolInc()
{
for(int i=0;i<10;i++)
{
c++;
try {
sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

程序结果:

VolatileText.a = 9734
VolatileText.b = 9374
VolatileText.c = 9077

程序1中三个静态变量a,b,c,其中两个使用volatile修饰。三个函数其中两个用synchronized修饰。但是1000个线程得到的累加结果都不是预想中的10000(如果叠加结果能保证每次都是10000说明多线程同步成功,否则多线程没有同步)。这是为啥来?不是说好的使用synchronized可以实现同步的么???????为啥来!

关键点在于static关键词的使用。 在本例中三个synchronized的函数都没有使用static修饰,也就是说对“对象”的该函数是实现了同步操作,而多个对象之间调用他们各自的函数自然互不影响… 因此a,b,c都没有得到预想中线程同步的结果。

———————————————————————————————————–

程序2:

public class VolatileText extends Thread
{
public static volatile int a = 0;
public static int b = 0;
public static volatile int c = 0;

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
VolatileText vt[] = new VolatileText[1000];
for(int i=0;i<vt.length;i++)
{
vt[i] = new VolatileText();
//vt[i].start();
}
for(int i=0;i<vt.length;i++)
{
vt[i].start();
}
for(int i=0;i<vt.length;i++)
{
try {
vt[i].join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

System.out.println(
“VolatileText.a = ” + VolatileText.a + “\n”
+ “VolatileText.b = ” + VolatileText.b + “\n”
+ “VolatileText.c = ” + VolatileText.c);
}

public void run()
{
inc();
synInc();
synVolInc();
}

public static void inc()
{
for(int i=0;i<10;i++)
{
a++;
try {
sleep(3);
} catch (InterruptedException e) {

e.printStackTrace();
}
}
}

public static synchronized void synInc()
{
for(int i=0;i<10;i++)
{
b++;
try {
sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static synchronized void synVolInc()
{
for(int i=0;i<10;i++)
{
c++;
try {
sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

程序结果:

VolatileText.a = 9926
VolatileText.b = 10000
VolatileText.c = 10000

可以看到使用了static synchronized修饰的函数能够确保多个线程的调用能够实现同步,正如上篇文中分析的那样!

那么如果像上面分析的这样的话,不用static的synchronized有啥用呢?

——————————————————————————————————————-

程序3: 不用static的synchronized函数的同步与否比较
public class StaticTest_2
{

public static volatile int a = 0;
public static int b = 0;
public static volatile int c = 0;
public static int d = 0;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub

}

public void inc()
{
for(int i=0;i<10;i++)
{
a++;
try {
Thread.sleep(3);
} catch (InterruptedException e) {

e.printStackTrace();
}
}
}

public synchronized void synInc()
{
for(int i=0;i<10;i++)
{
b++;
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void synVolInc()
{
for(int i=0;i<10;i++)
{
c++;
try {
Thread.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void incWithSynOrVol()
{
for(int i=0;i<10;i++)
{
d++;
try {
Thread.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

######
public class StaticTest_1 extends Thread
{

public StaticTest_2 instance = null;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
StaticTest_2 s2 = new StaticTest_2();
StaticTest_1 s1[] = new StaticTest_1[100];
for(int i=0;i<s1.length;i++)
{
s1[i] = new StaticTest_1();
s1[i].instance = s2;
}
for(int i=0;i<s1.length;i++)
{
s1[i].start();
}
for(int i=0;i<s1.length;i++)
{
try {
s1[i].join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(
“StaticTest_2.a = ” + StaticTest_2.a + “\n”
+ “StaticTest_2.b = ” + StaticTest_2.b + “\n”
+ “StaticTest_2.c = ” + StaticTest_2.c+”\n”
+ “StaticTest_2.d = ” + StaticTest_2.d);

}
@Override
public void run()
{
instance.inc();
instance.synInc();
instance.synVolInc();
instance.incWithSynOrVol();
}

}

多次运行输出:

1.

StaticTest_2.a = 999
StaticTest_2.b = 1000
StaticTest_2.c = 1000
StaticTest_2.d = 1000

2.

StaticTest_2.a = 999
StaticTest_2.b = 1000
StaticTest_2.c = 1000
StaticTest_2.d = 999

擦,运行了十好几次终于有一次d的值不是1000了。

b,c的值肯定是1000,因为他们使用synchronized同步了。d没有使用synchronized同步所以不能保证每次都得到1000, a虽然使用了volatile,但是需要同步的操作等式右侧有其自身,所以根据上篇文章的分析不符合使用volatile同步的要求,也不能保证每次都得到1000.

这个例子里面虽然synchronized的函数(对b,c的操作)没有使用static修饰,也能保证线程安全的,即线程同步。这是因为在调用的时候是同一对象调用这些synchronized的函数。

 

Java中的volatile和synchronized||Java多线程同步

Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。

我们知道,在Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步。

这在JVM 1.2之前,Java的内存模型实现总是从主存读取变量,是不需要进行特别的注意的。而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用变得非常重要。

在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。

要解决这个问题,只需要像在本程序中的这样,把该变量声明为volatile(不稳定的)即可,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。

这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。

而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。

使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。

由于使用屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

————————————————————————————————————

上面这段说volatile可以在一定程度上实现synchronized的线程同步

—————————————————————————————————————-

在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。

Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制。

 

synchronized

同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用

synchronized 修饰的方法 或者 代码块。

 

volatile

用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。

 

下面看一个例子,我们实现一个计数器,每次线程启动的时候,会调用计数器inc方法,对计数器进行加一

 

执行环境——jdk版本:jdk1.6.0_31 ,内存 :3G   cpu:x86 2.4G

运行结果:Counter.count=992

运行结果还是没有我们期望的1000,下面我们分析一下原因

 

在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,

线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存

变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图

描述这写交互

 

java volatile1

 

 

read and load 从主存复制变量到当前工作内存
use and assign  执行代码,改变共享变量值
store and write 用工作内存数据刷新主存相关内容

其中use and assign 可以多次出现

但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样

对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的

例如假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值

在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6

线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6

导致两个线程及时用volatile关键字修改之后,还是会存在并发的情况。

上边例子没有考虑到等待线程全部结束,下面例子是替代品

public class Counter {

public volatile static int count = 0;
public static int threadCnt = 0;

public static void inc() {

//这里延迟1毫秒,使得结果明显
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}

count++;
}
public synchronized static void threadOver()
{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
threadCnt++;
}

public static void main(String[] args) {

//同时启动1000个线程,去进行i++计算,看看实际结果

for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
Counter.threadOver();
}
}).start();
}

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//这里每次运行的值都有可能不同,可能为1000
System.out.println(“运行结果:Counter.count=” + Counter.count+”\n线程结束:”+Counter.threadCnt);
}
}

输出:

运行结果:Counter.count=998
线程结束:1000

也就是volatile关键字没有保证Counter.inc()是原子操作,否则Counter.count也应该是1000.

———————————————————————————————————————

上边这段说:volatile不能替代synchronized来实现多线程同步

———————————————————————————————————————-

volatile关键字相信了解Java多线程的读者都很清楚它的作用。volatile关键字用于声明简单类型变量,如int、float、boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。但这有一定的限制。例如,下面的例子中的n就不是原子级别的:

  1. package mythread;
  2. public class JoinThread extends Thread
  3. {
  4.     public static volatile int n = 0;
  5.     public void run()
  6.     {
  7.         for (int i = 0; i < 10; i++)
  8.             try
  9.         {
  10.                 n = n + 1;
  11.                 sleep(3); // 为了使运行结果更随机,延迟3毫秒
  12.             }
  13.             catch (Exception e)
  14.             {
  15.             }
  16.     }
  17.     public static void main(String[] args) throws Exception
  18.     {
  19.         Thread threads[] = new Thread[100];
  20.         for (int i = 0; i < threads.length; i++)
  21.             // 建立100个线程
  22.             threads[i] = new JoinThread();
  23.         for (int i = 0; i < threads.length; i++)
  24.             // 运行刚才建立的100个线程
  25.             threads[i].start();
  26.         for (int i = 0; i < threads.length; i++)
  27.             // 100个线程都执行完后继续
  28.             threads[i].join();
  29.         System.out.println(“n=” + JoinThread.n);
  30.     }
  31. }

如果对n的操作是原子级别的,最后输出的结果应该为n=1000,而在执行上面积代码时,很多时侯输出的n都小于1000,这说明n=n+1不是原子级别的操作。原因是声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:

n = n + 1;
n++;

如果要想使这种情况变成原子操作,需要使用synchronized关键字,如上的代码可以改成如下的形式:

  1. package mythread;
  2. public class JoinThread extends Thread
  3. {
  4.     public static int n = 0;
  5.     public static synchronized void inc()
  6.     {
  7.         n++;
  8.     }
  9.     public void run()
  10.     {
  11.         for (int i = 0; i < 10; i++)
  12.             try
  13.             {
  14.                 inc(); // n = n + 1 改成了 inc();
  15.                 sleep(3); // 为了使运行结果更随机,延迟3毫秒
  16.             }
  17.             catch (Exception e)
  18.             {
  19.             }
  20.     }
  21.     public static void main(String[] args) throws Exception
  22.     {
  23.         Thread threads[] = new Thread[100];
  24.         for (int i = 0; i < threads.length; i++)
  25.             // 建立100个线程
  26.             threads[i] = new JoinThread();
  27.         for (int i = 0; i < threads.length; i++)
  28.             // 运行刚才建立的100个线程
  29.             threads[i].start();
  30.         for (int i = 0; i < threads.length; i++)
  31.             // 100个线程都执行完后继续
  32.             threads[i].join();
  33.         System.out.println(“n=” + JoinThread.n);
  34.     }
  35. }

上面的代码将n=n+1改成了inc(),其中inc方法使用了synchronized关键字进行方法同步。因此,在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原来操作,当变量的值由自身的上一个决定时,如n=n+1、n++等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile。

———————————————————————————————————

这段说:在进行值的自身叠加等与自己有关系的操作时,volatile不好使必须用synchronized,而当处理的是n = m+1等与等式右边没有自身的操作时,volatile可以替代synchronized进行多线程同步。。

———————————————————————————————————-

 

java Future用法和意义一句话击破 [转]

在并发编程时,一般使用runnable,然后扔给线程池完事,这种情况下不需要线程的结果。
所以run的返回值是void类型。

如果是一个多线程协作程序,比如菲波拉切数列,1,1,2,3,5,8…使用多线程来计算。
但后者需要前者的结果,就需要用callable接口了。
callable用法和runnable一样,只不过调用的是call方法,该方法有一个泛型返回值类型,你可以任意指定。

线程是属于异步计算模型,所以你不可能直接从别的线程中得到函数返回值。
这时候,Future就出场了。Futrue可以监视目标线程调用call的情况,当你调用Future的get()方法以获得结果时,当前线程就开始阻塞,直接call方法结束返回结果。

下面三段简单的代码可以很简明的揭示这个意思:

runnable接口实现的没有返回值的并发编程。

callable实现的存在返回值的并发编程。(call的返回值String受泛型的影响)

同样是callable,使用Future获取返回值。