基础知识-Java多线程(volatile 与 synchronized)基础

0.概念

什么是线程,什么是进程:

进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同。

线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

进程是正在运行的程序的实例,或者:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。

Java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(还有一种是实现Callable接口,并与Future、线程池结合使用)


1.扩展java.lang.Thread类

class Thread1 extends Thread{  
    private String name;  
    public Thread1(String name) {  
       this.name=name;  
    }  
    public void run() {  
        for (int i = 0; i < 5; i++) {  
            System.out.println(name + "运行  :  " + i);  
            try {  
                sleep((int) Math.random() * 10);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
         
    }  
}  
public class Main {  
  
    public static void main(String[] args) {  
        Thread1 mTh1=new Thread1("A");  
        Thread1 mTh2=new Thread1("B");  
        mTh1.start();  
        mTh2.start();  
  
    }  
  
}  

start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。

实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。


2.实现java.lang.Runnable接口

class MyFirstRunnable implements Runnable{
        private int i=0;
        @Override
        public void run() {
                // TODO Auto-generated method stub
                for(i=0;i<10;i++){
                        System.out.println(Thread.currentThread().getName()+" "+ i);
                }
        }
}

实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。


3.使用Callable 和Future 接口创建线程

Callbale: 创建Callable接口的实现类并实现clall() 方法;

并使用FutureTask  类来包装callable实现类的对象,并以此FutureTask对象作为Thread对象的Target来创建线程。

class MyCallable implements Callable<Integer> {
        private int i=0;        
        @Override
        public Integer call() throws Exception {
                int sum=0;
                for(i=0;i<10;i++){
                        System.out.println(Thread.currentThread().getName()+" "+i);
                        sum++;
                }
                return sum;
        }
}
 
线程具体实现的时候:
public static void main(String[] args) {                
                Callable<Integer> myCallabel= new MyCallable();                
                FutureTask<Integer> ft=new  FutureTask<Integer>(myCallabel);                
                System.out.println(Thread.currentThread().getName()+" ");
                Thread th= new Thread(ft);
                th.start();     
try { 
 int sum = ft.get(); //取得新创建的线程中的call()方法返回的                    System.out.println("sum = " + sum);             
} catch (InterruptedException e) {            
  e.printStackTrace();          
  } catch (ExecutionException e) {
       e.printStackTrace(); 
}



4.Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

5.synchronized

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种: 
    1). 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 
    2). 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
    3). 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 

    4). 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。


当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。

给代码加上synchornized (同步锁),可以修饰:类,代码块,静态方法,方法,可以保证代码只有一个线程访问


去掉synchornized 就会发现,运行哪个线程就不受我们控制了。

指定要给某个对象加锁



在AccountOperator 类中的run方法里,我们用synchronized 给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。


如果没有加synchornized ,上图中红框中的结果肯定就不是我们想要的了。


6.volatile 

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

volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。



volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。
volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性。

也就是说,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。




参考资料:

https://www.zhihu.com/question/25532384

http://blog.csdn.net/soulfeeling/article/details/72123887

https://www.cnblogs.com/yjd_hycf_space/p/7526608.html

http://blog.csdn.net/luoweifu/article/details/46613015

http://blog.csdn.net/xilove102/article/details/52437581

https://www.cnblogs.com/sunrunzhi/p/3930297.html




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值