并发编程学习(一)
基本概念:
synchronized上加static和不加的区别就是有static是类锁,不加就是对象锁。
线程安全:当多个线程访问某一个类(对象或方法)时,这个类始终能表现出正确的行为,那么这个类就是一个线程安全的。
synchronized:可以在任何对象及方法上加锁,而加锁的这段代码称为"互斥区"或"临界区"。
线程安全:
public class MyThread extends Thread{ private int count = 5; public void run(){ count--; System.out.println(this.currentThread().getName()+" count = "+count); } public static void main(String[] args) { MyThread myThread = new MyThread(); Thread t1 = new Thread(myThread,"t1"); Thread t2 = new Thread(myThread,"t2"); Thread t3 = new Thread(myThread,"t3"); Thread t4 = new Thread(myThread,"t4"); Thread t5 = new Thread(myThread,"t5"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } }
输出结果:
t1 count = 4
t4 count = 2
t3 count = 1
t2 count = 3
t5 count = 0
可以看出并没有按照我们程序的顺序调用,要实现线程安全那就要加synchronized;
public synchronized void run(){ count--; System.out.println(this.currentThread().getName()+" count = "+count); }
有时候我们会调用同一对象内的两个方法,一个加锁,一个未加锁,未加锁的不会受加锁的影响。
以下程序会出现脏读:
public class MyObject { private String name = "test"; private String pwd = "123"; public synchronized void setValue(String name,String pwd){ this.name = name; try{ Thread.sleep(2000);//睡眠2秒,但name值已经设置过了 } catch (InterruptedException e) { e.printStackTrace(); } this.pwd = pwd; System.out.println("set结果name="+name+",pwd="+pwd); } public void getValue(){ System.out.println("get结果name="+name+",pwd="+pwd); } public static void main(String[] args) throws InterruptedException { MyObject myObject = new MyObject();//同一对象 Thread t1 = new Thread(new Runnable() {//主线程 @Override public void run() { myObject.setValue("wt","456"); } }); t1.start();//调set方法 Thread.sleep(1000);//休眠一秒调get方法 myObject.getValue(); } }
执行结果:
get结果name=wt,pwd=123
set结果name=wt,pwd=456
原因:调set方法时休眠了两秒,但name值已经设置过了,get方法没加锁,所以取出来是只设置name的值。
public synchronized void getValue(){//get方法加锁输出结果就不会出现脏读
System.out.println("get结果name="+name+",pwd="+pwd);
}
Volatile关键字:
作用:就是强制线程到主内存里去读取变量,而不去线程工作内存区里去读取,从而实现了多个线程间的变量可见,也就是满足线程安全的可见性。
线程可以执行的操作有use(使用)、assign赋值、load装载、store存储、锁定、解锁。
主内存的操作有:read,write,lock,unlock每个操作都是原子性的。
volatile一般用于只针对多个线程可见的变量操作,并不能代替synchronzied的同步功能,它不具有原子性,要实现原子 性建议使用atomic类的系统对象,
public class RunThread extends Thread { private boolean isRunning = true; private void setRunning(boolean isRunning){ this.isRunning = isRunning; } public void run(){ System.out.println("进入run方法"); while (isRunning == true){ // System.out.println("这个很耗时,打开可能会取到isRunning"); } System.out.println("线程停止"); } public static void main(String[] args) throws InterruptedException { RunThread rt = new RunThread(); rt.start(); Thread.sleep(3000); rt.setRunning(false); System.out.println("isrunning设置为false"); Thread.sleep(1000); System.out.println("isRunning:"+rt.isRunning); } }
输出结果:
进入run方法
isrunning设置为false
isRunning:false
程序仍然处于运行中
加入volatile后:private volatile boolean isRunning = true;
执行结果:
进入run方法
isrunning设置为false
线程停止
isRunning:false
出现上面的结果是因为,每个线程都有自己的一个内存区域(这样运行更快),在程序运行时,把需要的数据装载到自己的工作内存,而主内存中的数据仍然存在,我们上面程序修改isRunning为false,只是修改了主内存中的数据,线程的工作内存并没有修改,所以会出现主程序走完了,线程还在执行。
当isRunning被volatile修饰时,变量改变时会强制线程执行引擎去主内存中读取。