volatile与Java内存模型
Java的内存模型都是围绕着原子性,可见性,有序性展开的。为了确保这三个特性,Java使用了一些关键字来告诉虚拟机这些特殊的操作需要特别注意。volatile就是其中之一。
当你使用volatile关键字来声明一个变量时,虚拟机会告诉编译器,这个变量极不稳定,很容易改变,需要特别注意。我们通过一个示例来看下:
package JAVA线程方法解析;
/**
* Author select you from me
* func 测试volatile关键字
*/
public class VolatileUse {
public static long t = 0;
public static class ChangeT implements Runnable{
private long to;
public ChangeT(long to){
this.to=to;
}
@Override
public void run() {
while(true){
VolatileUse.t=to;
Thread.yield();
}
}
}
public static class ReadT implements Runnable{
@Override
public void run() {
while(true){
long tmp = VolatileUse.t;
if(tmp!=111L&&tmp!=-999L&&tmp!=333L&&tmp!=-444L){
System.out.println(tmp);
}
Thread.yield();
}
}
}
public static void main(String[] args){
new Thread(new ChangeT(111L)).start();
new Thread(new ChangeT(-999L)).start();
new Thread(new ChangeT(333L)).start();
new Thread(new ChangeT(-444L)).start();
new Thread(new ReadT()).start();
}
}
上边代码如果在32位JAVA虚拟机上运行,将产生下边的输出:
-4294967185
4294966297
4294966852
原因是因为long类型是64位,在32位虚拟机上操作可能产生串位的情况,那么怎么做呢?
public volatile static long t = 0; //将变量声明为volatile能保证操作的原子性。
但是volatile不能替代锁,无法保证复合操作的原子性。比如下面的一个例子:
package JAVA线程方法解析;
/**
* Author select you from me
* func 测试volatile关键字
*/
public class VolatileUseTwo{
static volatile int i=0;
public static class Plustask implements Runnable{
@Override
public void run() {
for(int k=0;k<1000;k++){
i++;
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread[] threads = new Thread[10];
for(int i =0;i<10;i++){
threads[i]=new Thread(new Plustask());
threads[i].start();
//threads[i].join();//如果把这个join有序去掉,那么这个i值会一直小于10000
}
System.out.print(i);
}
}
- 线程组
在一个系统中,如果线程很多,而且功能分配比较明确,建议将相同功能的线程放到线程组里。下面来看一下线程组的使用:
package JAVA线程方法解析;
/**
* Author select you from me
* func 线程组的使用
*/
public class ThreadGroupUse implements Runnable{
public static void main(String[]args){
ThreadGroup threadGroup = new ThreadGroup("PrintGroup");//定义线程组的名字
Thread thread1 = new Thread(threadGroup,new ThreadGroupUse(),"thread1");
Thread thread2 = new Thread(threadGroup,new ThreadGroupUse(),"thread2");
thread1.start();
thread2.start();
System.out.println(threadGroup.activeCount());//获得线程的总数,不确定的。
threadGroup.list();//线程组中所有的线程信息
}
@Override
public void run() {
String goupAndName = Thread.currentThread().getThreadGroup().getName()
+"--"+Thread.currentThread().getName();
while(true) {
System.out.println("I am" + goupAndName);
try {
Thread.sleep(3000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 守护线程(Daemon)
守护线程是一种特殊的线程,它是系统的守护者,比如垃圾回收线程,JIT线程就可以理解为守护线程,而我们建立的线程被称为用户线程,当系统中只有守护线程时,虚拟机会自然退出,看下面的例子:
package JAVA线程方法解析;
/**
* Author select you from me
* func 守护线程
*/
public class DaemonUse {
public static class DaemonT extends Thread{
@Override
public void run() {
while(true){
System.out.println("I am alive");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException{
Thread t = new DaemonT();
t.setDaemon(true);
//设置守护线程,必须在start()方法之前,否则会设置失败。并被认为是用户线程一直向下执行。
t.start();
Thread.sleep(1000);
}
}
- 线程优先级
线程可以有自己的优先级,优先级高的线程在竞争资源的时候更有优势。但也只是大概率问题。
Java中使用1-10表示线程的优先级。数字越大优先级越高。
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
我们简单举个例子:
Thread t1 = new HighPriority();
Thread t2 = new LowPriority();
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
一般情况下,线程1会比线程2先执行完。
- 线程同步
关键字synchronized的作用是实现线程的同步,他的工作是对同步的代码加锁,使得每一次只有一个线程进入同步块,从而保证线程的安全性。
synchronized有多种用法:
(1)指定对象加锁:对给定对象加锁,进入同步代码前要获得指定对象的锁。
(2)直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
(3)直接作用于静态方法:相当于对当前类进行加锁,进入同步代码前要获得当前类的锁。
我们举一个指定对象加锁的正确例子和反例,大家理解下什么叫指定对象加锁。
package JAVA线程方法解析;
/**
* Author select you from me
* func 线程安全 错误示范一:新建了两个线程,每个线程是不同的实例方法,所以线程不安全
*/
public class SynchronizedUse implements Runnable {
static int i=0;
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<10000;j++){
increase();
}
}
public static void main(String []args) throws InterruptedException{
Thread t1 = new Thread(new SynchronizedUse());
Thread t2 = new Thread(new SynchronizedUse());
t1.start();
//t1.join();
t2.start();
//t2.join();
Thread.sleep(2000);
System.out.println(i);
}
}
正确的修改方式:
package JAVA线程方法解析;
/**
* Author select you from me
* func 线程安全 第一种修改方式
*/
public class SynchronizedUseCrect1 implements Runnable {
static int i=0;
public static synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<10000;j++){
increase();
}
}
public static void main(String []args) throws InterruptedException{
Thread t1 = new Thread(new SynchronizedUseCrect1());
Thread t2 = new Thread(new SynchronizedUseCrect1());
t1.start();
//t1.join();
t2.start();
//t2.join();
Thread.sleep(2000);
System.out.println(i);
}
}
package JAVA线程方法解析;
/**
* Author select you from me
* func 线程安全 第二种修改方式
*/
public class SynchronizedUseCrect2 implements Runnable {
static SynchronizedUseCrect2 synchronizedUseCrect2 = new SynchronizedUseCrect2();
static int i=0;
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<10000;j++){
increase();
}
}
public static void main(String []args) throws InterruptedException{
Thread t1 = new Thread(synchronizedUseCrect2);
Thread t2 = new Thread(synchronizedUseCrect2);
t1.start();
//t1.join();
t2.start();
//t2.join();
Thread.sleep(2000);
System.out.println(i);
}
}
- 使用Vector()代替ArrayList 使用CurrentHashMap()代替HashMap()
第二章结束。
作者:select you from me
链接:https://mp.csdn.net/mdeditor/96293004
来源:CSDN
转载请联系作者获得授权并注明出处。