Java多线程编程核心技术---对象及变量的并发访问(二)

数据类型String的常量池特性

在JVM中具有String常量池缓存的功能。

public class Service {
	public static void print(String str){
		try {
			synchronized (str) {
				while (true) {
					System.out.println(Thread.currentThread().getName());
					Thread.sleep(500);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class ThreadA extends Thread {
	@Override
	public void run() {
		Service.print("AA");
	}
}

public class ThreadB extends Thread {
	@Override
	public void run() {
		Service.print("AA");
	}
}
public class Test {
	public static void main(String[] args) {
		ThreadA a = new ThreadA();
		a.setName("A");
		ThreadB b = new ThreadB();
		b.setName("B");
		a.start();
		b.start();
	}
}

控制台打印结果如下:

...
A
A
A
A
A
A
...

出现这种情况就是因为Sting的两个值都是AA,两个线程持有相同的锁,所以造成线程B不能执行。因此在大多数情况下,同步synchronized代码块都不实用String作为锁对象,而改用其他,比如new Object()实例化一个Object对象,但他并不放入缓存中。


同步synchronized方法无限等待与解决
public class Service {
	synchronized public void methodA(){
		System.out.println("methodA begin...");
		boolean condition = true;
		while (condition) {
			
		}
		System.out.println("methodA end...");
	}
	
	synchronized public void methodB(){
		System.out.println("methodB begin...");
		System.out.println("methodB end...");
	}
}
public class ThreadA extends Thread {
	private Service service;
	
	public ThreadA(Service service) {
		super();
		this.service = service;
	}
	
	@Override
	public void run() {
		service.methodA();
	}
}

public class ThreadB extends Thread {
	private Service service;
	
	public ThreadB(Service service) {
		super();
		this.service = service;
	}
	
	@Override
	public void run() {
		service.methodB();
	}
}
public class Run {
	public static void main(String[] args) {
		Service service = new Service();
		ThreadA a = new ThreadA(service);
		a.setName("A");
		ThreadB b = new ThreadB(service);
		b.setName("B");
		a.start();
		b.start();
	}
}

控制台打印结果如下:

methodA begin...

线程A处于死循环状态,线程B永远无法拿到Service对象锁而一直得不到运行。

对Service对象做如下修改:

public class Service {
	Object object1 = new Object();
	Object object2 = new Object();
	public void methodA() {
		synchronized (object1) {
			System.out.println("methodA begin...");
			boolean condition = true;
			while (condition) {

			}
			System.out.println("methodA end...");
		}
	}

	public void methodB() {
		synchronized (object2) {
			System.out.println("methodB begin...");
			System.out.println("methodB end...");
		}
	}
}

此时控制台打印结果如下:

methodA begin...
methodB begin...
methodB end...

methodA()和methodB()对不同的对象加锁,所以线程A持有的锁不会对线程B造成影响。


多线程的死锁

Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。

public class DeadThread implements Runnable {

	public String username;
	public Object lock1 = new Object();
	public Object lock2 = new Object();
	
	public void setFlag(String username) {
		this.username = username;
	}
	
	@Override
	public void run() {
		if (username.equals("a")) {
			synchronized (lock1) {
				try {
					System.out.println("username=" + username);
					Thread.sleep(3000);
				} catch (Exception e) {
					e.printStackTrace();
				}
				synchronized (lock2) {
					System.out.println("按lock1->lock2代码顺序执行了");
				}
			}
		}
		if (username.equals("b")) {
			synchronized (lock2) {
				try {
					System.out.println("username=" + username);
					Thread.sleep(3000);
				} catch (Exception e) {
					e.printStackTrace();
				}
				synchronized (lock1) {
					System.out.println("按lock2->lock1代码顺序执行了");
				}
			}
		}
	}

	public static void main(String[] args) {
		try {
			DeadThread t1 = new DeadThread();
			t1.setFlag("a");
			Thread thread1 = new Thread(t1);
			thread1.start();
			Thread.sleep(200);
			
			t1.setFlag("b");
			Thread thread2 = new Thread(t1);
			thread2.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

控制台打印结果如下:

username=a
username=b

此时程序不结束,处于死锁状态。

可以使用jps命令查看当前线程的id,然后使用jstack -l id来检查是否存在死锁。


内置类与静态内置类
//内置类
public class PublicClass {
	private String username;
	private String password;
	
	class PrivateClass{
		private String age;
		private String address;
		public String getAge() {
			return age;
		}
		public void setAge(String age) {
			this.age = age;
		}
		public String getAddress() {
			return address;
		}
		public void setAddress(String address) {
			this.address = address;
		}
		public void printPublicProperty() {
			System.out.println(username + "-" + password);
		}
	}
	
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	
	public static void main(String[] args) {
		PublicClass publicClass = new PublicClass();
		publicClass.setUsername("admin");
		publicClass.setPassword("123456");
		System.out.println(publicClass.getUsername() + "-" + publicClass.getPassword());
		
		PrivateClass privateClass = publicClass.new PrivateClass();
		privateClass.setAddress("shanghai");
		privateClass.setAge("25");
		System.out.println(privateClass.getAddress() + "-" + privateClass.getAge());
		privateClass.printPublicProperty();
	}
}

控制台打印结果如下:

admin-123456
shanghai-25
admin-123456
//静态内置类
public class PublicClass {
	static String username;
	static String password;
	
	static class PrivateClass{
		private String age;
		private String address;
		public String getAge() {
			return age;
		}
		public void setAge(String age) {
			this.age = age;
		}
		public String getAddress() {
			return address;
		}
		public void setAddress(String address) {
			this.address = address;
		}
		public void printPublicProperty() {
			System.out.println(username + "-" + password);
		}
	}
	
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	
	public static void main(String[] args) {
		PublicClass publicClass = new PublicClass();
		publicClass.setUsername("admin");
		publicClass.setPassword("123456");
		System.out.println(publicClass.getUsername() + "-" + publicClass.getPassword());
		
		PrivateClass privateClass = new PrivateClass();
		privateClass.setAddress("shanghai");
		privateClass.setAge("25");
		System.out.println(privateClass.getAddress() + "-" + privateClass.getAge());
		privateClass.printPublicProperty();
	}
}

控制台打印结果同上。


内置类与同步-实验1
public class OutClass {
	static class Inner{
		public void method1() {
			synchronized ("其他的锁") {
				for (int i = 0; i < 10; i++) {
					System.out.println(Thread.currentThread().getName() + "i=" + i);
					try {
						Thread.sleep(100);
					} catch (Exception e) {
					}
				}
			}
		}
		
		public synchronized void method2() {
			for (int i = 11; i < 20; i++) {
				System.out.println(Thread.currentThread().getName() + "i=" + i);
				try {
					Thread.sleep(100);
				} catch (Exception e) {
				}				
			}
		}
	}
	
	public static void main(String[] args) {
		final Inner inner = new Inner();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				inner.method1();
			}
		}, "A");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				inner.method2();
			}
		}, "B");
		t1.start();
		t2.start();
	}
}

控制台打印结果如下:

Ai=0
Bi=11
Bi=12
Ai=1
Bi=13
Ai=2
Ai=3
Bi=14
Ai=4
Bi=15
Bi=16
Ai=5
Ai=6
Bi=17
Ai=7
Bi=18
Bi=19
Ai=8
Ai=9

由于持有不同的对象监视器,所以打印结果是乱序的。

内置类与同步-实验2
public class OutClass {
	static class InnerClass1{
		public void method1(InnerClass2 class2) {
			String threadName = Thread.currentThread().getName();
			synchronized (class2) {
				System.out.println(threadName + "进入InnerClass1的method1方法");
				for (int i = 0; i < 5; i++) {
					System.out.println("i=" + i);
					try {
						Thread.sleep(100);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				System.out.println(threadName + "离开InnerClass1的method1方法");
			}
		}
		
		public synchronized void method2() {
			String threadName = Thread.currentThread().getName();
			System.out.println(threadName + "进入InnerClass1的method2方法");
			for (int j = 0; j < 5; j++) {
				System.out.println("j=" + j);
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			System.out.println(threadName + "离开InnerClass1的method2方法");
		}
	}
	
	static class InnerClass2{
		public synchronized void method1() {
			String threadName = Thread.currentThread().getName();
			System.out.println(threadName + "进入InnerClass2的method1方法");
			for (int k = 0; k < 5; k++) {
				System.out.println("k=" + k);
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			System.out.println(threadName + "离开InnerClass2的method1方法");
		}
	}
	
	public static void main(String[] args) {
		final InnerClass1 class1 = new InnerClass1();
		final InnerClass2 class2 = new InnerClass2();
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				class1.method1(class2);
			}
		}, "T1");
		
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				class1.method2();
			}
		}, "T2");
		
		Thread t3 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				class2.method1();
			}
		}, "T3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

控制台打印结果如下:

T2进入InnerClass1的method2方法
T1进入InnerClass1的method1方法
j=0
i=0
j=1
i=1
i=2
j=2
j=3
i=3
i=4
j=4
T2离开InnerClass1的method2方法
T1离开InnerClass1的method1方法
T3进入InnerClass2的method1方法
k=0
k=1
k=2
k=3
k=4
T3离开InnerClass2的method1方法

同步代码块synchronized (class2)对class2上锁后,其他线程只能以同步方式调用class2中的静态同步方法。


对象锁的改变
public class MyService {
	private String lock = "123";
	public void testMethod() {
		try {
			synchronized (lock) {
				System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis());
				lock = "456";
				Thread.sleep(2000);
				System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

public class ThreadA extends Thread {
	private MyService service;
	
	public ThreadA(MyService service) {
		super();
		this.service = service;
	}
	
	@Override
	public void run() {
		service.testMethod();
	}
}

public class ThreadB extends Thread {
	private MyService service;
	
	public ThreadB(MyService service) {
		super();
		this.service = service;
	}
	
	@Override
	public void run() {
		service.testMethod();
	}
}

public class Run1 {
	public static void main(String[] args) throws InterruptedException {
		MyService service = new MyService();
		ThreadA a = new ThreadA(service);
		a.setName("A");
		ThreadB b = new ThreadB(service);
		b.setName("B");
		a.start();
		Thread.sleep(100);
		b.start();
	}
}

k控制台打印结果如下:

A begin 1465980925627
B begin 1465980925727
A end 1465980927627
B end 1465980927727

从打印结果看,A线程和B线程是以异步方式执行的,可见A线程与B线程持有的锁不同。

将以上main方法中的代码做如下修改:

public class Run1 {
	public static void main(String[] args) throws InterruptedException {
		MyService service = new MyService();
		ThreadA a = new ThreadA(service);
		a.setName("A");
		ThreadB b = new ThreadB(service);
		b.setName("B");
		a.start();
		//Thread.sleep(100);
		b.start();
	}
}

此时打印结果如下:

A begin 1465981162126
A end 1465981164127
B begin 1465981164128
B end 1465981166128

可见此时A线程和B线程是以同步方式执行的,A线程和B线程共同争抢的锁是“123”。

PS:只要对象不变,即使对象的属性改变,运行的结果还是同步的。将以上代码做如下修改:

public class MyService {
	private StringBuilder lock = new StringBuilder("123");
	
	public void testMethod() {
		try {
			synchronized (lock) {
				System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis());
				lock.append("456");
				Thread.sleep(2000);
				System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

public class Run1 {
	public static void main(String[] args) throws InterruptedException {
		MyService service = new MyService();
		ThreadA a = new ThreadA(service);
		a.setName("A");
		ThreadB b = new ThreadB(service);
		b.setName("B");
		a.start();
		Thread.sleep(100);
		b.start();
	}
}

此时控制台打印结果如下:

A begin 1465981411980
A end 1465981413980
B begin 1465981413980
B end 1465981415981

可见线程A和线程B是以同步方式执行的。


volatile关键字
关键字volatile与死循环

死循环例子

public class PrintString {
	private boolean isContinuePrint = true;
	public boolean isContinuePrint() {
		return isContinuePrint;
	}
	public void setContinuePrint(boolean isContinuePrint) {
		this.isContinuePrint = isContinuePrint;
	}
	public void printStringMethod() {
		try {
			while (isContinuePrint) {
				System.out.println("printStringMethod is running...threadName=" + Thread.currentThread().getName());
				Thread.sleep(1000);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		PrintString printString = new PrintString();
		printString.printStringMethod();
		System.out.println("停止线程...");
		printString.setContinuePrint(false);
	}
}

控制台打印结果如下:

printStringMethod is running...threadName=main
printStringMethod is running...threadName=main
printStringMethod is running...threadName=main
printStringMethod is running...threadName=main
printStringMethod is running...threadName=main
......

main线程在printString.printStringMethod()中陷入了死循环,后面的printString.setContinuePrint(false)得不到执行。

解决同步死循环

修改上面的代码

public class PrintString implements Runnable {
	private boolean isContinuePrint = true;
	public boolean isContinuePrint() {
		return isContinuePrint;
	}
	public void setContinuePrint(boolean isContinuePrint) {
		this.isContinuePrint = isContinuePrint;
	}
	public void printStringMethod() {
		try {
			while (isContinuePrint) {
				System.out.println("printStringMethod is running...threadName=" + Thread.currentThread().getName());
				Thread.sleep(1000);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	@Override
	public void run() {
		printStringMethod();
	}

	public static void main(String[] args) throws InterruptedException {
		PrintString printString = new PrintString();
		new Thread(printString).start();
		Thread.sleep(5000);
		System.out.println("停止线程...");
		printString.setContinuePrint(false);
	}
}

此时控制台打印结果如下:

printStringMethod is running...threadName=Thread-0
printStringMethod is running...threadName=Thread-0
printStringMethod is running...threadName=Thread-0
printStringMethod is running...threadName=Thread-0
printStringMethod is running...threadName=Thread-0
停止线程...

此时main线程设置isContinuePrint=false,可以使另一个线程停止执行。

++注:《Java多线程编程核心技术》P120讲将上面的代码运行在-server服务器模式中的64bit的JVM上时,会出现死循环。实际测试并未出现死循环,暂未弄清原因。++

解决异步死循环
package com.umgsai.thread22;

public class RunThread extends Thread {
	volatile private boolean isRunning = true;
	public boolean isRunning() {
		return isRunning;
	}
	public void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}
	
	@Override
	public void run() {
		System.out.println("进入run方法...");
		while (isRunning) {
			System.out.println("running....");
			try {
				Thread.sleep(500);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		System.out.println("线程被停止...");
	}
	
	public static void main(String[] args) {
		try {
			RunThread runThread = new RunThread();
			runThread.start();
			Thread.sleep(1000);
			runThread.setRunning(false);
			System.out.println("已将isRunning设置为false");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

控制台打印结果如下:

进入run方法...
running....
running....
已将isRunning设置为false
线程被停止...

使用volatile关键字强制从公共内存中读取变量

使用volatile关键字增加了实例变量在多个线程之间的可见性,但是volatile关键字不支持原子性。

synchronized和volatile的比较

  1. 关键字volatile是线程同步的轻量级实现,性能比synchronized好。volatile只能修饰变量,synchronized可以修饰方法和代码块。
  2. 多线程访问volatile不会发生阻塞,synchronized会出现阻塞。
  3. volatile能保证数据的可见性,但不能保证原子性。synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
  4. 关键字volatile解决的是变量在多个线程之间的可见性,synchronized解决的是多个线程之间访问资源的同步性。
volatile的非原子性
public class VolatileTest extends Thread {

	volatile public static int count;
	private static void addCount(){
		for (int i = 0; i < 100; i++) {
			count++;
		}
		System.out.println("count=" + count);
	}
	
	@Override
	public void run() {
		addCount();
	}
	
	public static void main(String[] args) {
		VolatileTest[] volatileTests = new VolatileTest[100];
		for (int i = 0; i < 100; i++) {
			volatileTests[i] = new VolatileTest();
		}
		for (int i = 0; i < 100; i++) {
			volatileTests[i].start();
		}
	}
}

控制台打印结果如下:

......
count=5332
count=5232
count=5132
count=5032
count=4932
count=4854
count=4732
count=4732

将以上代码中的addCount方法加上synchronized关键字

public class VolatileTest extends Thread {

	volatile public static int count;
	//一定要加static关键字,这样synchronized与static锁的内容就是VolatileTest类了,也就达到同步效果了。
	synchronized private static void addCount(){
		for (int i = 0; i < 100; i++) {
			count++;
		}
		System.out.println("count=" + count);
	}
	
	@Override
	public void run() {
		addCount();
	}
	
	public static void main(String[] args) {
		VolatileTest[] volatileTests = new VolatileTest[100];
		for (int i = 0; i < 100; i++) {
			volatileTests[i] = new VolatileTest();
		}
		for (int i = 0; i < 100; i++) {
			volatileTests[i].start();
		}
	}
}

此时控制台打印结果如下:

......
count=9300
count=9400
count=9500
count=9600
count=9700
count=9800
count=9900
count=10000
使用原子类进行i++操作

i++操作除了使用synchronized关键字同步外,还可以使用AtomicInteger原子类实现。

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest extends Thread {
	private AtomicInteger count = new AtomicInteger(0);
	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println(count.incrementAndGet());
		}
	}
	
	public static void main(String[] args) {
		AtomicIntegerTest atomicIntegerTest = new AtomicIntegerTest();
		Thread t1 = new Thread(atomicIntegerTest);
		Thread t2 = new Thread(atomicIntegerTest);
		Thread t3 = new Thread(atomicIntegerTest);
		Thread t4 = new Thread(atomicIntegerTest);
		Thread t5 = new Thread(atomicIntegerTest);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}

控制台打印结果如下:

......
4992
4993
4994
4995
4996
4997
4998
4999
5000

5个线程成功累加到5000。

原子类也并不完全安全
public class MyService {
	public static AtomicLong atomicLong = new AtomicLong();
	public void addNum() {
		System.out.println(Thread.currentThread().getName() + " 加了100之后是:" + atomicLong.addAndGet(100));
		atomicLong.addAndGet(1);
	}
}

public class MyThread extends Thread {
	private MyService myService;
	public MyThread(MyService myService) {
		this.myService = myService;
	}
	
	@Override
	public void run() {
		myService.addNum();
	}
}

public class Run {
	public static void main(String[] args) {
		try {
			MyService myService = new MyService();
			MyThread[] array = new MyThread[100];
			for (int i = 0; i < array.length; i++) {
				array[i] = new MyThread(myService);
			}
			for (int i = 0; i < array.length; i++) {
				array[i].start();;
			}
			Thread.sleep(1000);
			System.out.println(myService.atomicLong.get());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

控制台打印结果如下:

......
Thread-89 加了100之后是:8987
Thread-96 加了100之后是:9493
Thread-80 加了100之后是:9594
Thread-94 加了100之后是:9695
Thread-97 加了100之后是:9796
Thread-95 加了100之后是:9896
Thread-98 加了100之后是:9998
Thread-99 加了100之后是:10098
10100

累加的结果是正确的,但是打印顺序的错的,这是因为虽然addAndGet方法是原子的,但是方法和方法之间的调用却不是原子的。

对以上代码做如下修改:

public class MyService {
	public static AtomicLong atomicLong = new AtomicLong();
	synchronized public void addNum() {
		System.out.println(Thread.currentThread().getName() + " 加了100之后是:" + atomicLong.addAndGet(100));
		atomicLong.addAndGet(1);
	}
}

此时控制台打印结果如下:

......
Thread-86 加了100之后是:9392
Thread-87 加了100之后是:9493
Thread-88 加了100之后是:9594
Thread-92 加了100之后是:9695
Thread-93 加了100之后是:9796
Thread-94 加了100之后是:9897
Thread-95 加了100之后是:9998
Thread-98 加了100之后是:10099
10100

此时线程以同步方式执行addNum方法,每次先加100再加1.

synchronized代码块有volatile同步的功能

关键字synchronized可以使多个线程访问同一个资源具有同步性,而且还可以将线程工作内存中的私有变量与公共内存中的变量进行同步。

public class Service {
	private boolean isContinueRun = true;
	public void runMethod() {
		while (isContinueRun) {
			
		}
		System.out.println("stop...");
	}
	
	public void stopMethod() {
		isContinueRun = false;
	}
}
public class ThreadA extends Thread {
	private Service service;
	public ThreadA(Service service) {
		this.service = service;
	}
	
	@Override
	public void run() {
		service.runMethod();
	}
}

public class ThreadB extends Thread {
	private Service service;
	public ThreadB(Service service) {
		this.service = service;
	}
	
	@Override
	public void run() {
		service.stopMethod();
	}
}
public class Run {
	public static void main(String[] args) {
		try {
			Service service = new Service();
			ThreadA a = new ThreadA(service);
			a.start();
			Thread.sleep(1000);
			ThreadB b = new ThreadB(service);
			b.start();
			System.out.println("已经发起停止命令了");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

运行main,控制台打印结果如下:

已经发起停止命令了

出现死循环。

对Service类做如下修改:

public class Service {
	private boolean isContinueRun = true;
	public void runMethod() {
		String anyString = new String();
		while (isContinueRun) {
			synchronized (anyString) {
				
			}
		}
		System.out.println("stop...");
	}
	
	public void stopMethod() {
		isContinueRun = false;
	}
}

此时控制台打印结果如下:

已经发起停止命令了
stop...

关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或某一个代码块。它包含两个特征:互斥性和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程都看到由同一个锁保护之前所有的修改结果。

原文地址:https://www.cnblogs.com/umgsai/p/5589945.html