Java线程安全的计数器

   一、多线程以及线程安全   
       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
代码:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
* 功能:线程安全的计数器,5个线程同时跑,计数到1000,输出线程名和计数
* @author Yolanda
*
*/
public class MySafeThread implements Runnable{

     private static AtomicInteger count = new AtomicInteger(0);//线程安全的计数变量
     private int threadCount = 0;//线程编号
     private static int num = 1;

     /**
     * 功能:计数
     */
     public static void calc(){
          while((count.get())<1000)
          {
               count.incrementAndGet();//自增1,返回更新值
              System.out.println("正在运行是线程" + Thread.currentThread().getName() + ":" + count);         
          }
     }
     
     /**
     * 功能:线程运行方法,每次只能一个线程访问
     */
     public synchronized void run() {
          while(true)
          {
               try {
                    Thread.sleep(1);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }     
               MySafeThread.calc();
          }
     }
     
     public static void main(String[] args) {          
          //创建五个线程实例并启动
          for (int i = 1; i < 6; i++) {     
               Thread mySafeThread = new Thread(new MySafeThread());
               mySafeThread.start();
          }
     }     
}




结果:

 

正在运行是线程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

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

看上面的结果其实是有一点不对的,前面的几个计数发生了重复,这是由于锁的位置不正确导致的。所以对这个程序重写了一下。

package com.yolanda.fun.thread;


import java.util.concurrent.atomic.AtomicInteger;


public class MySafeCalcThread1 implements Runnable {
	
	private static AtomicInteger count = new AtomicInteger(0);
	
    public synchronized static void calc() {
    	if ((count.get()) < 1000) {
    		int c = count.incrementAndGet();// 自增1,返回更新值
    		System.out.println("正在运行是线程" + Thread.currentThread().getName() + ":" + c);
    	}
			
	}
	
	public void run() {
		 while(true)
         {
			 MySafeCalcThread1.calc();
			 try {
				 Thread.sleep(0);
			 } catch (InterruptedException e) {
				 // TODO Auto-generated catch block
				 e.printStackTrace();
			 }	 
         }
		
	}


	public static void main(String[] args) {
		for (int i = 0; i < 6; i++) {
			MySafeCalcThread1 thread = new MySafeCalcThread1();
			Thread t = new Thread(thread);
			t.start();
			
		}


	}




}

然后结果就对啦,就没有重复计数的问题啦。


结果:

正在运行是线程Thread-0:1
正在运行是线程Thread-1:2
正在运行是线程Thread-0:4
正在运行是线程Thread-2:3
正在运行是线程Thread-2:7
正在运行是线程Thread-0:6
正在运行是线程Thread-0:9
正在运行是线程Thread-1:5
正在运行是线程Thread-0:12
正在运行是线程Thread-5:11
正在运行是线程Thread-3:10
正在运行是线程Thread-2:8

.......

正在运行是线程Thread-4:996
正在运行是线程Thread-4:997
正在运行是线程Thread-4:998
正在运行是线程Thread-4:999
正在运行是线程Thread-4:1000
正在运行是线程Thread-1:743
正在运行是线程Thread-3:742
正在运行是线程Thread-0:935
正在运行是线程Thread-5:884
正在运行是线程Thread-2:747


还有一种写法是这样子的~

package com.yolanda.fun.thread;


import java.util.concurrent.atomic.AtomicInteger;


public class MySafeCalcThread2 implements Runnable {
	
	private static AtomicInteger count = new AtomicInteger(0);
	
	public static void calc() {
		
		while ((count.get()) < 1000) {
			int c = 0;
			synchronized (count) {
				if ((count.get()) < 1000) {
					c = count.incrementAndGet();// 自增1,返回更新值
				}
			}
			System.out.println("正在运行是线程" + Thread.currentThread().getName() + ":" + c);
		}




	}
	
	public void run() {
		 while(true)
         {
			 MySafeCalcThread2.calc();
			 try {
				 Thread.sleep(0);
			 } catch (InterruptedException e) {
				 // TODO Auto-generated catch block
				 e.printStackTrace();
			 }	 
         }
		
	}


	public static void main(String[] args) {
		for (int i = 0; i < 6; i++) {
			MySafeCalcThread2 thread = new MySafeCalcThread2();
			Thread t = new Thread(thread);
			t.start();
			
		}


	}




}



结果:

正在运行是线程Thread-0:1
正在运行是线程Thread-3:2
正在运行是线程Thread-4:3
正在运行是线程Thread-5:4
正在运行是线程Thread-0:5
正在运行是线程Thread-0:6
正在运行是线程Thread-2:7
正在运行是线程Thread-2:8
正在运行是线程Thread-1:9
正在运行是线程Thread-2:10

........

正在运行是线程Thread-2:990
正在运行是线程Thread-2:991
正在运行是线程Thread-2:992
正在运行是线程Thread-1:993
正在运行是线程Thread-2:994
正在运行是线程Thread-0:995
正在运行是线程Thread-5:996
正在运行是线程Thread-5:997
正在运行是线程Thread-5:998
正在运行是线程Thread-5:999
正在运行是线程Thread-5:1000


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

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

第一种:继承Thread类

package com.yolanda.fun.thread;

public class MyThread extends Thread {
	
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("线程" + Thread.currentThread().getName() + "在运行");
		}
	}

	public static void main(String[] args) {
		MyThread thread = new MyThread();
		thread.start();
		
		for (int i = 0; i < 10; i++) {
			System.out.println("线程" + Thread.currentThread().getName() + "在运行");// 线程main
		}

	}

}



结果:

线程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来启动


package com.yolanda.fun.thread;

public class MyRunnable implements Runnable{

	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("线程" + Thread.currentThread().getName() + "在运行");
		}
	}
	
	public static void main(String[] args) {
		MyRunnable thread = new MyRunnable();
		Thread t = new Thread(thread);
		t.start();
		
		for (int i = 0; i < 10; i++) {
			System.out.println("线程" + Thread.currentThread().getName() + "在运行");
		}
	}

}


结果:

线程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()方法执行结束后,线程即处于终止状态。处于终止状态的线程不具备继续运行的能力。


 

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值