Java多线程编程-Thread synchronized使用
我们在进行多线程开发的时候,会出现线程安全问题。非线程安全就是数据出现不一致导致的,对同一个对象中的实例变量进行并发访问时发生。就是取到的数据是被修改过的。
线程主要通过共享对字段和对象引用字段所引用的访问来进行通信。 这种通信方式非常有效,但是却可能导致两种错误:线程干扰和内存一致性错误。 防止这些错误所需的工具是同步。
但是,同步会引入线程争用,当两个或多个线程尝试同时访问同一资源并使Java运行时更慢地执行一个或多个线程,甚至挂起它们的执行时,就会发生线程争用。 饥饿和活锁是线程争用的形式。
线程安全的变量(方法内的变量)
非线程安全只是针对实例变量,为了验证方法内的变量是线程安全的,做一下实验验证一下:
对象类:
public class ThreadMethodVariableObj {
public void addCount(String threadName) {
try {
int count = 0;
if (threadName.equalsIgnoreCase("thread01")) {
count = 10;
System.out.println("thread01 set count end");
Thread.sleep(3000);
} else {
count = 100;
System.out.println("thread02 set count end");
}
System.out.println("thread name:" + threadName + " ,set count value:" + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程1:
public class ThreadMethodVariableT1 extends Thread {
private ThreadMethodVariableObj threadMethodVariableObj;
public ThreadMethodVariableT1(ThreadMethodVariableObj threadMethodVariableObj, String threadName) {
this.threadMethodVariableObj = threadMethodVariableObj;
this.setName(threadName);
}
@Override
public void run() {
threadMethodVariableObj.addCount(Thread.currentThread().getName());
}
}
线程2:
public class ThreadMethodVariableT2 extends Thread {
private ThreadMethodVariableObj threadMethodVariableObj;
public ThreadMethodVariableT2(ThreadMethodVariableObj threadMethodVariableObj, String threadName) {
this.threadMethodVariableObj = threadMethodVariableObj;
this.setName(threadName);
}
@Override
public void run() {
threadMethodVariableObj.addCount(Thread.currentThread().getName());
}
}
运行类:
public class ThreadMethodVariableMain {
public static void main(String[] args) {
ThreadMethodVariableObj threadMethodVariableObj = new ThreadMethodVariableObj();
ThreadMethodVariableT1 threadMethodVariableT1 = new ThreadMethodVariableT1(threadMethodVariableObj, "thread01");
threadMethodVariableT1.start();
ThreadMethodVariableT2 threadMethodVariableT2 = new ThreadMethodVariableT2(threadMethodVariableObj, "thread02");
threadMethodVariableT2.start();
}
}
运行结果:
运行是各个线程的值不会影响其他的线程中的count值,为什么会这样?我们可以通过java内存模型进行分析,在java内存中,有一部分内存的分配是线程私有的。而局部变量是保存在本地方法栈中,是线程私有的。所以不会有线程安全问题,可是实例变量就不一样了,我们来验证一下:
实例对象类:
public class ThreadInstanceVariableObj {
private int count = 0;
public void addCount(String threadName) {
try {
if (threadName.equalsIgnoreCase("thread01")) {
count = 10;
System.out.println("thread01 set count end");
Thread.sleep(3000);
} else {
count = 100;
System.out.println("thread02 set count end");
}
System.out.println("thread name:" + threadName + " ,set count value:" + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程1:
public class ThreadInstanceVariableT1 extends Thread {
private ThreadInstanceVariableObj threadInstanceVariableObj;
public ThreadInstanceVariableT1(ThreadInstanceVariableObj threadInstanceVariableObj, String threadName) {
this.threadInstanceVariableObj = threadInstanceVariableObj;
this.setName(threadName);
}
@Override
public void run() {
threadInstanceVariableObj.addCount(Thread.currentThread().getName());
}
}
线程2:
public class ThreadInstanceVariableT2 extends Thread {
private ThreadInstanceVariableObj threadInstanceVariableObj;
public ThreadInstanceVariableT2(ThreadInstanceVariableObj threadInstanceVariableObj, String threadName) {
this.threadInstanceVariableObj = threadInstanceVariableObj;
this.setName(threadName);
}
@Override
public void run() {
threadInstanceVariableObj.addCount(Thread.currentThread().getName());
}
}
运行类:
public class ThreadInstanceVariableMain {
public static void main(String[] args) {
ThreadInstanceVariableObj threadInstanceVariableObj = new ThreadInstanceVariableObj();
ThreadInstanceVariableT1 threadInstanceVariableT1 = new ThreadInstanceVariableT1(threadInstanceVariableObj, "thread01");
threadInstanceVariableT1.start();
ThreadInstanceVariableT2 threadInstanceVariableT2 = new ThreadInstanceVariableT2(threadInstanceVariableObj, "thread02");
threadInstanceVariableT2.start();
}
}
运行结果:
线程1和线程2的值一样,线程1的值已经被线程2修改为了100,所有线程1获取的值是不对的.我们只需要在实例类ThreadInstanceVariableObj 中的addCount方法中加上synchronized就可以解决,代码如下:
//添加同步的操作
public synchronized void addCount(String threadName) {
try {
if (threadName.equalsIgnoreCase("thread01")) {
count = 10;
System.out.println("thread01 set count end");
Thread.sleep(3000);
} else {
count = 100;
System.out.println("thread02 set count end");
}
System.out.println("thread name:" + threadName + " ,set count value:" + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
看到的数据现在正常了,没有被污染。看看运行是线程1先运行完,再运行线程2的。如果在web端去访问的话,就会有请求阻塞的问题,要等一个个请求处理完,这样会影响性能,但是可以用其他方法解决。
我们怎么将上面的同步改为异步的形式呢?可以定义多个ThreadInstanceVariableObj 对象进行,我们来看看
运行代码:
public class ThreadInstanceVariableMain {
public static void main(String[] args) {
ThreadInstanceVariableObj threadInstanceVariableObj = new ThreadInstanceVariableObj();
ThreadInstanceVariableObj threadInstanceVariableObj2 = new ThreadInstanceVariableObj();
ThreadInstanceVariableT1 threadInstanceVariableT1 = new ThreadInstanceVariableT1(threadInstanceVariableObj, "thread01");
threadInstanceVariableT1.start();
ThreadInstanceVariableT2 threadInstanceVariableT2 = new ThreadInstanceVariableT2(threadInstanceVariableObj2, "thread02");
threadInstanceVariableT2.start();
}
}
运行结果:
运行结果和上面的运行结果对比可以看出现在是使用异步的当时进行的,这样为什么可以实现呢?其实就是synchronized取得的锁都是对象锁,而不是将一段代码或者方法当作锁,哪个线程先取得这个锁其他线程就只有等待,但是多个线程访问多个对象,则会在JVM中创建多个锁。
为了验证锁的是对象,我们进行如下现在:
不进行同步操作的时候:
public class ThreadObject {
public void printMethod() {
try {
System.out.println("current thread name:" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("thread is end:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行代码:
public class ThreadObjectMain {
public static void main(String[] args) {
ThreadObject threadObject = new ThreadObject();
ThreadObjectT1 threadObjectT1 = new ThreadObjectT1(threadObject, "thread01");
ThreadObjectT2 threadObjectT2 = new ThreadObjectT2(threadObject, "thread02");
threadObjectT1.start();
threadObjectT2.start();
}
}
运行结果:
进行同步操作的结果:
public class ThreadObject {
public synchronized void printMethod() {
try {
System.out.println("current thread name:" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("thread is end:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
调用synchronized声明的方法,必须要排队进行。
那现在我们多个线程同步调用不同的方法,一个方法声明同步,一个方法不声明同步看看结果
添加一个方法:printMethod2()
public void printMethod2() {
try {
System.out.println("current thread name (printMethod2):" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("thread is end (printMethod2):" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
更改线程2调用的方法,试试:
public class ThreadObjectT2 extends Thread {
private ThreadObject threadObject;
public ThreadObjectT2(ThreadObject threadObject, String threadName) {
this.threadObject = threadObject;
this.setName(threadName);
}
@Override
public void run() {
threadObject.printMethod2();
}
}
运行结果为:
由结果可以看出,当一个线程调用了同步方法,另一个线程调用非同步的方法时,线程1会只有object的锁,但是线程2可以异步调用非同步方法的。
现在全部转为同步方法,其他的不变看看:
public synchronized void printMethod2() {
try {
System.out.println("current thread name (printMethod2):" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("thread is end (printMethod2):" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果为:
我们将method2改为同步的时候,多线程调用不同的方法也是同步进行的。由此可以得出相应结论:
1.当线程1调用对象的同步方法时,会获取object对象的锁,但是线程2可以异步调用对象的非同步方法
2.当线程1调用了对象的同步方法时,会获取object对象的锁,线程2调用对象的另外的同步方法时,也需要等线程1释放锁才能再进行执行,需要等待。