线程间的共享
三个关键字:Synchronized,ThreadLocal,volatile的使用实例
1.Synchronized内置锁
1)类锁:每个类的Class对象
代码示例:
类锁的方法和使用类锁的线程
解析:在类锁的方法里用线程的休眠代表平常的业务工作(用static关键字修饰)
在类锁的线程只是做简单的输出和调用类锁的方法
//类锁的方法
private static synchronized void synClass(){
try {
Thread.sleep(1000);
System.out.println("synClass is going...");
Thread.sleep(1000);
System.out.println("synClass ended...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//使用类锁的线程
private static class SynClass extends Thread{
@Override
public void run() {
System.out.println("TestClass is running");
synClass();
}
}
2)对象锁: 每个对象 (new出来的实例)
代码示例:
对象锁的方法和使用对象锁的线程
解析:在对象锁的方法里用线程的休眠和输出语句代表平常的业务工作
在对象锁的线程里SynClzAndIns是包含这些方法的实体类,先要new出这个实例对象,然后调用相应的对象锁的方法
//对象锁的方法
private synchronized void instance(){
try {
Thread.sleep(3000);
System.out.println("synInstance is going..."+this.toString());
Thread.sleep(3000);
System.out.println("synInstance ended..."+this.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//使用对象锁的线程
private static class InstanceSyn implements Runnable{
private SynClzAndInst synClzAndInst;
public InstanceSyn(SynClzAndInst synClzAndInst) {
this.synClzAndInst = synClzAndInst;
}
@Override
public void run() {
System.out.println("TestInstance is running..."+synClzAndInst);
synClzAndInst.instance();
}
}
具体代码块(SynClzAndInst的类中包含有1个使用类锁的线程和2个使用对象锁的线程,1个使用类锁的方法和2个使用对象锁的方法)
package 线程间的共享;
/**
* @author lenovo
*/
public class SynClzAndInst {
//使用类锁的线程
private static class SynClass extends Thread{
@Override
public void run() {
System.out.println("TestClass is running");
synClass();
}
}
//使用对象锁的线程
private static class InstanceSyn implements Runnable{
private SynClzAndInst synClzAndInst;
public InstanceSyn(SynClzAndInst synClzAndInst) {
this.synClzAndInst = synClzAndInst;
}
@Override
public void run() {
System.out.println("TestInstance is running..."+synClzAndInst);
synClzAndInst.instance();
}
}
//使用对象锁的线程
private static class Instance2Syn implements Runnable{
private SynClzAndInst synClzAndInst;
public Instance2Syn(SynClzAndInst synClzAndInst) {
this.synClzAndInst = synClzAndInst;
}
@Override
public void run() {
System.out.println("TestInstance2 is running..."+synClzAndInst);
synClzAndInst.instance2();
}
}
//对象锁的方法
private synchronized void instance(){
try {
Thread.sleep(3000);
System.out.println("synInstance is going..."+this.toString());
Thread.sleep(3000);
System.out.println("synInstance ended..."+this.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//对象锁的方法
private synchronized void instance2(){
try {
Thread.sleep(3000);
System.out.println("synInstance2 is going..."+this.toString());
Thread.sleep(3000);
System.out.println("synInstance2 ended..."+this.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//类锁的方法
private static synchronized void synClass(){
try {
Thread.sleep(1000);
System.out.println("synClass is going...");
Thread.sleep(1000);
System.out.println("synClass ended...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SynClzAndInst synClzAndInst=new SynClzAndInst();
Thread t1=new Thread(new InstanceSyn(synClzAndInst));
SynClzAndInst synClzAndInst2=new SynClzAndInst();
Thread t2=new Thread(new Instance2Syn(synClzAndInst2));
// SynClass t2=new SynClass();
t1.start();
t2.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
终上所述:
无论是使用类锁还是对象锁,线程之间都是共享的(因为他们锁的都是不同对象),他们可以并发运行。在实际的开发中,我们只有new出一个锁去锁两个不同的对象,这是他们才会产生资源的竞争
2.ThreadLocal(线程变量)
1)它的底层实现是Map,主要应用在线程池的连接里面
2)三个常用的方法: protected T initialValue() //初始化值
ThreadLocal.get() //获取值
ThreadLocal.set() //设置值
ThreadLocal代码使用实例:
业务需求: 开启多个线程,线程里的工作是将线程变量的初值取出后进行更改,并写回,观察这几个线程的变量是否相互影响。
新建一个ThreadLocal的类并覆初值
其中:initialValue()是覆初始值
static ThreadLocal<Integer> threadLocal= new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
新建一个线程数组,开启对个线程
/**
* 运行3个线程
*/
public static void StartThreadArray(){
Thread[] runs=new Thread[3];
for(int i=0;i<runs.length;i++){
runs[i]=new Thread(new TestThread(i));
}
for (int i=0;i<runs.length;i++){
runs[i].start();
}
}
线程里具体业务操作:在各个线程中分别取出ThreadLocal的初值,并与各自线程id号进行加法运算,并写会。考虑最终得到的线程结果值是否会相互影响。
/**
* 类说明:测试线程,线程的工作是将ThreadLocal的变量变化,并写回,
* 看线程之间是否相互影响
*/
public static class TestThread implements Runnable{
int id;
public TestThread(int id){
this.id=id;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start");
Integer s=threadLocal.get(); //获得变量的值
s=s+id;
threadLocal.set(s);
System.out.println(Thread.currentThread().getName()+":"+
threadLocal.get());
}
}
完整代码
package 线程间的共享;
/**
* @author lenovo
* 类说明:使用类ThreadLocal
*/
public class UseThreadLocal {
static ThreadLocal<Integer> threadLocal= new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
/**
* 运行3个线程
*/
public static void StartThreadArray(){
Thread[] runs=new Thread[3];
for(int i=0;i<runs.length;i++){
runs[i]=new Thread(new TestThread(i));
}
for (int i=0;i<runs.length;i++){
runs[i].start();
}
}
/**
* 类说明:测试线程,线程的工作是将ThreadLocal的变量变化,并写回,
* 看线程之间是否相互影响
*/
public static class TestThread implements Runnable{
int id;
public TestThread(int id){
this.id=id;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start");
Integer s=threadLocal.get(); //获得变量的值
s=s+id;
threadLocal.set(s);
System.out.println(Thread.currentThread().getName()+":"+
threadLocal.get());
}
}
public static void main(String[] args) {
StartThreadArray();
}
}
运行结果
结果分析:threadLocal的初值为1,Thread-0,Thread-1,Thread-2的id号分别为0,1,2;
由结果可知,ThradLocal虽然只有一个实例对象。可是在线程运行时,它被看做每个线程独一份的变量,它在线程间并不会影响。所以在线程0,线程1,线程2获得的初值都是1,然后对他们的id号进行加法运算,则结果的值分别是1,2,3。
3.关键字volatile:
1)最轻量级的同步机制,可是线程不安全,不能保证原子性
2)应用场景: 只有一个线程写,多个线程读(它能保证变量的可见性,不能保证操作的原子性)(原子性:要么一起执行完成,要么都不执行)
解析:变量的可见性:保证了变量在线程的可见性,所有线程访问由volatile修饰的变量,都必须从主存中读取后操作,并在工作内存修改后立即写回主存,保证了其他线程的可见性
不能保证操作的原子性:“原子操作(atomic operation)是不需要synchronized”,这是多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束。(也就是说多个线程运行时,可能由于调度机制的不一致,导致结果的千变万化)
代码示例
package 线程间的共享;
/**
* @author lenovo
* 类说明:volatile无法提供操作的原子性
*/
public class VolatileUnsafe {
private static class VolatileVar implements Runnable{
private volatile int a=0;
@Override
public void run() {
String threadName=Thread.currentThread().getName();
a=a+1;
System.out.println(threadName+":======"+a);
try {
Thread.sleep(100);
a=a+1;
System.out.println(threadName+":======"+a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
VolatileVar v=new VolatileVar();
Thread t1=new Thread(v);
Thread t2=new Thread(v);
Thread t3=new Thread(v);
Thread t4=new Thread(v);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果
上面两个图是运行同一段代码产生的不同结果,我们最直观的感受就是结果的多样性与结果的不可预见性。显然这是我们编写代码时,最不愿见到的。(我们设计改程序的初衷是为了进行各线程的累加操作,若它能保证原子性那么线程0的两次变量值应该为1,2;线程1的两次变量值应该为3,4…依次类推。可显然上面的结果和我们预期的不一样,这就是因为volatile修饰的变量不能保证操作的原子性,当线程0运行时,它对变量操作一次的时候,其他线程都有机会拿到该变量,导致线程0无法按照预期地完成值的第二次更改,依次类推,各线程拿到该变量的可能性由操作系统调度决定,结果这也将导致值的多样化)