线程的同步
- 多线程出现了安全问题
- 问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
- 解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
Synchronized的使用方法
- 同步代码块:
synchronized (对象){
// 需要被同步的代码;
}
- synchronized还可以放在方法声明中,表示整个方法为同步方法。 例如:
public synchronized void show (String name){
...
}
示例代码1 单例模式懒汉式:
public class ThreadSynchronized {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
输出:
true
示例代码2 代码块实现Runnable:
public class ThreadSynchronized {
public static void main(String[] args) {
Counter c = new Counter();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
System.out.println(t1 == t2);
t1.start();
t2.start();
t3.start();
}
}
public class Counter implements Runnable {
private int num = 0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (Counter.class) {
System.out.printf("num : %d %s %n", num++, Thread.currentThread().getName());
}
}
}
}
输出:
false
num : 0 Thread-0
num : 1 Thread-0
num : 2 Thread-0
num : 3 Thread-0
num : 4 Thread-0
num : 5 Thread-0
num : 6 Thread-0
num : 7 Thread-0
num : 8 Thread-0
num : 9 Thread-0
num : 10 Thread-2
num : 11 Thread-1
num : 12 Thread-1
num : 13 Thread-1
num : 14 Thread-1
num : 15 Thread-1
num : 16 Thread-1
num : 17 Thread-1
num : 18 Thread-1
num : 19 Thread-1
num : 20 Thread-1
num : 21 Thread-2
num : 22 Thread-2
num : 23 Thread-2
num : 24 Thread-2
num : 25 Thread-2
num : 26 Thread-2
num : 27 Thread-2
num : 28 Thread-2
num : 29 Thread-2
示例代码3 代码块继承Thread:
public class ThreadSynchronized {
public static void main(String[] args) {
NCounter nc1 = new NCounter();
NCounter nc2 = new NCounter();
System.out.println(nc1 == nc2);
nc1.start();
nc2.start();
}
}
public class NCounter extends Thread {
private static int num = 9;
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (Counter.class) {
if (num >= 0) {
System.out.printf("num : %d %s %n", num--, getName());
} else {
return;
}
}
}
}
}
输出:
false
num : 9 Thread-0
num : 8 Thread-0
num : 7 Thread-1
num : 6 Thread-1
num : 5 Thread-1
num : 4 Thread-1
num : 3 Thread-1
num : 2 Thread-1
num : 1 Thread-1
num : 0 Thread-1
示例代码4 同步方法 实现Runnable:
public class ThreadSynchronized {
public static void main(String[] args) {
// 同步方法 :方式一
ExponentialCounter ec= new ExponentialCounter();
Thread t1 = new Thread(ec);
Thread t2 = new Thread(ec);
System.out.println(t1 == t2);
t1.start();
t2.start();
}
}
public class ExponentialCounter implements Runnable {
private int num = 2;
private int power = 0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
show();
}
}
public synchronized void show() {
System.out.printf("num : %d power : %d %s %n",(int) Math.pow(num, power),power++, Thread.currentThread().getName());
}
}
输出:
false
num : 1 power : 0 Thread-0
num : 2 power : 1 Thread-0
num : 4 power : 2 Thread-0
num : 8 power : 3 Thread-0
num : 16 power : 4 Thread-0
num : 32 power : 5 Thread-0
num : 64 power : 6 Thread-1
num : 128 power : 7 Thread-1
num : 256 power : 8 Thread-1
num : 512 power : 9 Thread-1
num : 1024 power : 10 Thread-1
num : 2048 power : 11 Thread-1
num : 4096 power : 12 Thread-1
num : 8192 power : 13 Thread-1
num : 16384 power : 14 Thread-1
num : 32768 power : 15 Thread-1
num : 65536 power : 16 Thread-0
num : 131072 power : 17 Thread-0
num : 262144 power : 18 Thread-0
num : 524288 power : 19 Thread-0
示例代码5 同步方法继承Thread:
public class ThreadSynchronized {
public static void main(String[] args) {
// 同步方法 :方式二
QuadraticCounter qc1= new QuadraticCounter();
QuadraticCounter qc2= new QuadraticCounter();
System.out.println(qc1 == qc2);
qc1.start();
qc2.start();
}
}
public class QuadraticCounter extends Thread {
private static int num = 0;
private static int power = 2;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
show();
}
}
public static synchronized void show() {
System.out.printf("num : %d power : %d %s %n", (int) Math.pow(num++, power), power,
Thread.currentThread().getName());
}
}
输出:
false
num : 0 power : 2 Thread-0
num : 1 power : 2 Thread-0
num : 4 power : 2 Thread-0
num : 9 power : 2 Thread-0
num : 16 power : 2 Thread-1
num : 25 power : 2 Thread-1
num : 36 power : 2 Thread-1
num : 49 power : 2 Thread-1
num : 64 power : 2 Thread-1
num : 81 power : 2 Thread-1
num : 100 power : 2 Thread-1
num : 121 power : 2 Thread-1
num : 144 power : 2 Thread-1
num : 169 power : 2 Thread-1
num : 196 power : 2 Thread-0
num : 225 power : 2 Thread-0
num : 256 power : 2 Thread-0
num : 289 power : 2 Thread-0
num : 324 power : 2 Thread-0
num : 361 power : 2 Thread-0
同步机制中的锁
- 同步锁机制:
在《ThinkinginJava》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。 - synchronized的锁是什么?
- 任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
- 同步方法的锁:静态方法(类名.class)、非静态方法(this)。
- 同步代码块:自己指定,很多时候也是指定为this或类名.class
- 注意:
- 必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全。
- 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)。
同步的范围
- 如何找问题,即代码是否存在线程安全?(非常重要)
- 明确哪些代码是多线程运行的代码。
- 明确多个线程是否有共享数据。
- 明确多线程运行代码中是否有多条语句操作共享数据。
- 如何解决呢?(非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。即所有操作共享数据的这些语句都要放在同步范围中。 - 切记:
- 范围太小:没锁住所有有安全问题的代码。
- 范围太大:没发挥多线程的功能。
释放锁的操作
- 当前线程的同步方法、同步代码块执行结束。
- 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
不会释放锁的操作
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()来控制线程
线程的死锁问题
- 死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
- 解决方法
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
死锁示例代码:
public class ThreadDeadlock {
static Object obj1 = new Object();
static Object obj2 = new Object();
public static void main(String[] args) {
new Thread() {
public void run() {
synchronized(obj1) {
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Thread 0 : 0");
synchronized(obj2) {
System.out.println("Thread 0 : 1");
}
}
}
}.start();
new Thread() {
public void run() {
synchronized(obj2) {
System.out.println("Thread 1 : 0");
synchronized(obj1) {
System.out.println("Thread 1 : 1");
}
}
}
}.start();
}
}
出现死锁的输出:
Thread 1 : 0
Thread 0 : 0
Lock(锁)
- 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
示例代码:
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLock {
public static void main(String[] args) {
// TODO Auto-generated method stub
Lock lock = new Lock();
Thread t1 = new Thread(lock);
Thread t2 = new Thread(lock);
System.out.println(t1 == t2);
t1.start();
t2.start();
}
}
class Lock implements Runnable {
private ReentrantLock lock = new ReentrantLock();
private int num = 0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
lock.lock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println(Thread.currentThread().getName()+" num: "+num++);
} catch (Exception e) {
System.out.println(e);
} finally {
lock.unlock();
}
}
}
}
输出:
false
Thread-1 num: 0
Thread-1 num: 1
Thread-1 num: 2
Thread-1 num: 3
Thread-1 num: 4
Thread-1 num: 5
Thread-1 num: 6
Thread-0 num: 7
Thread-0 num: 8
Thread-0 num: 9
Thread-0 num: 10
Thread-1 num: 11
Thread-1 num: 12
Thread-1 num: 13
Thread-0 num: 14
Thread-0 num: 15
Thread-0 num: 16
Thread-0 num: 17
Thread-0 num: 18
Thread-0 num: 19
synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放。
- Lock只有代码块锁,synchronized有代码块锁和方法锁。
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。
优先使用顺序: Lock ——> 同步代码块(已经进入了方法体,分配了相应资源)——>同步方法 (在方法体之外)。