Java线程安全的计数器

来源:互联网 发布:爱奇艺视频下载软件 编辑:程序博客网 时间:2024/06/06 19:19


一、多线程以及线程安全   
       java线程安全就是指控制多个线程对某个资源的有序访问或修改。这涉及两个很重要的概念:java的内存模型和java的线程同步机制。
      1.java的内存模型
要解决两个主要的问题:可见性和有序性
可见性: 多个线程之间是不能互相传递数据通信的,它们之间的沟通只能通过共享变量来进行
      2.线程同步
       由于同一进程内的多个线程共享内存空间,在Java中,就是共享实例,当多个线程试图同时修改某个实例的内容时,就会造成冲突,因此,线程必须实现共享互斥,使多线程同步。
       最简单的同步是将一个方法标记为synchronized,对同一个实例来说,任一时刻只能有一个synchronized方法在执行。当一个方法正在执行某个synchronized方法时,其他线程如果想要执行这个实例的任意一个synchronized方法,都必须等待当前执行synchronized方法的线程退出此方法后,才能依次执行。
       但是,非synchronized方法不受影响,不管当前有没有执行synchronized方法,非synchronized方法都可以被多个线程同时执行。
       synchronized和volatile关键字的区别:
       (1)synchronized关键字保证了多个线程对于同步块是互斥的,synchronized作为一种同步手段,解决java多线程的执行有序性和内存可见性,而volatile关键字只解决多线程的内存可见性问题;
       (2)volatile关键字是java提供的一种同步手段,只不过它是轻量级。volatile只能保证多线程的内存可见性,不能保证多线程的执行有序性。而最彻底的同步要保证有序性和可见性,例如synchronized。
       任何被volatile修饰的变量,都不拷贝副本到工作内存,任何修改都及时写在主存。因此对于Valatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是有序的。
      3.基本知识
JAVA多线程实现方式:继承Thread类、实现Runnable接口
使用哪一种:http://www.cnblogs.com/rollenholt/archive/2011/08/28/2156357.html
最好是后一种
二、实例代码
功能:编写一个线程安全的计数器,5个线程同时跑,计数到1000,输出线程名和计数直到1000
代码:
[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:18px;"><span style="font-size:18px;">import java.util.concurrent.CountDownLatch;  
  2. import java.util.concurrent.atomic.AtomicInteger;  
  3.   
  4. /** 
  5. * 功能:线程安全的计数器,5个线程同时跑,计数到1000,输出线程名和计数 
  6. * @author Yolanda 
  7. * 
  8. */  
  9. public class MySafeThread implements Runnable{  
  10.   
  11.      private static AtomicInteger count = new AtomicInteger(0);//线程安全的计数变量  
  12.      private int threadCount = 0;//线程编号  
  13.      private static int num = 1;  
  14.   
  15.      /** 
  16.      * 功能:计数 
  17.      */  
  18.      public static void calc(){  
  19.           while((count.get())<1000)  
  20.           {  
  21.                count.incrementAndGet();//自增1,返回更新值  
  22.               System.out.println("正在运行是线程" + Thread.currentThread().getName() + ":" + count);           
  23.           }  
  24.      }  
  25.        
  26.      /** 
  27.      * 功能:线程运行方法,每次只能一个线程访问 
  28.      */  
  29.      public synchronized void run() {  
  30.           while(true)  
  31.           {  
  32.                try {  
  33.                     Thread.sleep(1);  
  34.                } catch (InterruptedException e) {  
  35.                     e.printStackTrace();  
  36.                }       
  37.                MySafeThread.calc();  
  38.           }  
  39.      }  
  40.        
  41.      public static void main(String[] args) {            
  42.           //创建五个线程实例并启动  
  43.           for (int i = 1; i < 6; i++) {       
  44.                Thread mySafeThread = new Thread(new MySafeThread());  
  45.                mySafeThread.start();  
  46.           }  
  47.      }       
  48. }</span></span>  


结果:

 

正在运行是线程Thread-1:2

正在运行是线程Thread-4:4

正在运行是线程Thread-0:4

正在运行是线程Thread-3:2

正在运行是线程Thread-3:9

正在运行是线程Thread-3:10

正在运行是线程Thread-0:8

正在运行是线程Thread-4:7

正在运行是线程Thread-4:13

正在运行是线程Thread-4:14

正在运行是线程Thread-4:15

正在运行是线程Thread-4:16

正在运行是线程Thread-4:17

正在运行是线程Thread-4:18

正在运行是线程Thread-4:19

正在运行是线程Thread-4:20

......

正在运行是线程Thread-4:988

正在运行是线程Thread-4:989

正在运行是线程Thread-4:990

正在运行是线程Thread-4:991

正在运行是线程Thread-4:992

正在运行是线程Thread-4:993

正在运行是线程Thread-4:994

正在运行是线程Thread-4:995

正在运行是线程Thread-4:996

正在运行是线程Thread-4:997

正在运行是线程Thread-4:998

正在运行是线程Thread-4:999

正在运行是线程Thread-4:1000

正在运行是线程Thread-2:399

正在运行是线程Thread-1:398

正在运行是线程Thread-0:700

正在运行是线程Thread-3:402



=====================================我是更新线=====================================

在此处补充一下JAVA多线程实现的两种方式

第一种:继承Thread类

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:18px;">package com.yolanda.fun.thread;  
  2.   
  3. public class MyThread extends Thread {  
  4.       
  5.     public void run() {  
  6.         for (int i = 0; i < 10; i++) {  
  7.             System.out.println("线程" + Thread.currentThread().getName() + "在运行");  
  8.         }  
  9.     }  
  10.   
  11.     public static void main(String[] args) {  
  12.         MyThread thread = new MyThread();  
  13.         thread.start();  
  14.           
  15.         for (int i = 0; i < 10; i++) {  
  16.             System.out.println("线程" + Thread.currentThread().getName() + "在运行");// 线程main  
  17.         }  
  18.   
  19.     }  
  20.   
  21. }  
  22. </span>  

结果:

线程main在运行
线程Thread-0在运行
线程main在运行
线程main在运行
线程Thread-0在运行
线程Thread-0在运行
线程main在运行
线程Thread-0在运行
线程main在运行
线程Thread-0在运行
线程Thread-0在运行
线程Thread-0在运行
线程Thread-0在运行
线程Thread-0在运行
线程Thread-0在运行
线程main在运行
线程main在运行
线程main在运行
线程main在运行
线程main在运行


结果分析:这里可以看到main线程和Thread-0线程交替运行。

所谓的多线程,指的是两个线程的代码可以同时运行,而不必一个线程需要等待另一个线程内的代码执行完才可以运行。对于单核CPU来说,是无法做到真正的多线程的,每个时间点上,CPU都会执行特定的代码,由于CPU执行代码时间很快,所以两个线程的代码交替执行看起来像是同时执行的一样。那具体执行某段代码多少时间,就和分时机制系统有关了。分时系统把CPU时间划分为多个时间片,操作系统以时间片为单位片为单位各个线程的代码,越好的CPU分出的时间片越小。所以看不到明显效果也很正常,一个线程打印5句话本来就很快,可能在分出的时间片内就执行完成了。所以,最简单的解决办法就是把for循环的值调大一点就可以了(也可以在for循环里加Thread.sleep方法)。


第二种:实现Runnable接口。和继承自Thread类差不多,不过实现Runnable后,还是要通过一个Thread来启动

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:18px;">package com.yolanda.fun.thread;  
  2.   
  3. public class MyRunnable implements Runnable{  
  4.   
  5.     public void run() {  
  6.         for (int i = 0; i < 10; i++) {  
  7.             System.out.println("线程" + Thread.currentThread().getName() + "在运行");  
  8.         }  
  9.     }  
  10.       
  11.     public static void main(String[] args) {  
  12.         MyRunnable thread = new MyRunnable();  
  13.         Thread t = new Thread(thread);  
  14.         t.start();  
  15.           
  16.         for (int i = 0; i < 10; i++) {  
  17.             System.out.println("线程" + Thread.currentThread().getName() + "在运行");  
  18.         }  
  19.     }  
  20.   
  21. }  
  22. </span>  

结果:

线程main在运行
线程Thread-0在运行
线程main在运行
线程Thread-0在运行
线程main在运行
线程Thread-0在运行
线程main在运行
线程main在运行
线程main在运行
线程main在运行
线程main在运行
线程main在运行
线程main在运行
线程Thread-0在运行
线程Thread-0在运行
线程Thread-0在运行
线程Thread-0在运行
线程Thread-0在运行
线程Thread-0在运行
线程Thread-0在运行


两个线程也是交替运行。


其实Thread类也是实现的Runnable接口。


两种实现方式对比的关键就在于extends和implements的对比,当然是后者好。因为第一,继承只能但继承,实现可以多实现;第二,实现的方式对比继承的方式,也有利于减小程序之间的耦合。

因此,多线程的实现几乎都是使用的Runnable接口的方式。


线程状态

虚拟机中的线程状态有六种,定义在Thread.State中:


1、新建状态NEW
new了但是没有启动的线程的状态。比如"Thread t = new Thread()",t就是一个处于NEW状态的线程

2、可运行状态RUNNABLE
new出来线程,调用start()方法即处于RUNNABLE状态了。处于RUNNABLE状态的线程可能正在Java虚拟机中运行,也可能正在等待处理器的资源,因为一个线程必须获得CPU的资源后,才可以运行其run()方法中的内容,否则排队等待

3、阻塞BLOCKED
如果某一线程正在等待监视器锁,以便进入一个同步的块/方法,那么这个线程的状态就是阻塞BLOCKED

4、等待WAITING
某一线程因为调用不带超时的Object的wait()方法、不带超时的Thread的join()方法、LockSupport的park()方法,就会处于等待WAITING状态

5、超时等待TIMED_WAITING
某一线程因为调用带有指定正等待时间的Object的wait()方法、Thread的join()方法、Thread的sleep()方法、LockSupport的parkNanos()方法、LockSupport的parkUntil()方法,就会处于超时等待TIMED_WAITING状态

6、终止状态TERMINATED
线程调用终止或者run()方法执行结束后,线程即处于终止状态。处于终止状态的线程不具备继续运行的能力。


0 0