我们还可以直接将bank.addMoney方法定义为同步方法,这样我们就不需要同步代码块,不用创建同步对象锁了。他的写法更简洁。同步函数需要被对象调用,所以同步函数使用的锁就是this对象。在下面的实例中this指向的是唯一的customer对象,所以锁是相同的对象。
public class MultipleThread {
public static void main(String[] args) {
Customer customer = new Customer();
Thread t1 = new Thread(customer);
Thread t2 = new Thread(customer);
Thread t3 = new Thread(customer);
t1.start();//客户1跑线程1
t2.start();
t3.start();
}
}
//银行总账类
class Bank {
private int moneySum = 0;
//同步方法
synchronized void addMoney(int n) {
System.out.println(Thread.currentThread().getName() + " save money:");
moneySum = moneySum + n;
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println("sum = " + moneySum);
}
}
//用户类:因为要创建多个用户执行多线程,所以该类实现Runnable
class Customer implements Runnable {
//此对象为多线程共享对象
private Bank bank = new Bank();
//存钱,存三次
@Override
public void run() {
for (int i = 0; i < 3; i++) {
bank.addMoney(100);
}
}
}
/*
Thread-0 save money:
sum = 100
Thread-0 save money:
sum = 200
Thread-0 save money:
sum = 300
Thread-2 save money:
sum = 400
Thread-2 save money:
sum = 500
Thread-2 save money:
sum = 600
Thread-1 save money:
sum = 700
Thread-1 save money:
sum = 800
Thread-1 save money:
sum = 900
*/
synchronized(Bank.class)
{
需要同步的代码(操作共享数据的代码)
}
如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例java多线程死锁,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。懒汉式单例模式下,首次加载singletontest类时,并不会立即创建实例,而是只有通过调用getinstance方法,才会创建该类的实例并返回此实例,每次调用getinstance方法都只会获得同一个实例。ioc技术的本质就是构建对象的技术换句话说就是将一个类实例化成对象的技术,在java里实例化类通过new关键字进行的,每次new一个类都会产生一个新的实例对象,这么做视乎很浪费,有时这种浪费还挺危险,因为在程序开发时候我们常常只需要某个类永远只能产生一个的实例对象这个时候就得使用单例模式,此外在设计模式里还可以通过工厂方式产生对象,使用过spring的人看到上面的文字就知道了,spring里bean的定义就和上面的内容一一对应,scope属性single产生单例对象,prototype产生新对象,bean还可以通过工厂方式产生对象,可以说spring的bean就是制造对象的工具。

饿汉式:
class Single{
private static final Single s = new Single();
private Single(){}
public static Single getInstance(){
return s;
}
}
提示:使用nslock与使用同步方式有点相似,只是使用nslock时显式使用nslock对象作为同步锁,而使用同步代码块时系统显式使用某个对象作为同步监视器,同样都符合”加锁->修改->释放锁”的操作模式,而且使用nslock对象时每个nslock对象对应一个lcaccount对象,一样可以保证对于同一个lcaccount对象,同一时刻只能有一个线程进入临界区.。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:。foundation还提供了nslock,它通过显式定义同步锁对象来实现同步,在这种机制下,同步锁使用nslock对象充当。
class Single {
private static Single s = null;
private Single() {
}
public static Single getInstance() {
if (s == null) {
synchronized (Single.class) {
if (s == null)
s = new Single();
}
}
return s;
}
}
同步可能产生死锁。
一个线程有一个锁,不放自己的锁要到另一个线程执行,而另一个线程也有一个锁不放,两者要相互访问但是都不放自己的锁就会产生死锁问题。当出现死锁程序会卡死。
通常由于同步中嵌套同步产生。
下面实例产生死锁:
class ProductThreadA implements Runnable {
@Override
public void run() {
//这里一定要让线程睡一会儿来模拟处理数据 ,要不然的话死锁的现象不会那么的明显.
//这里就是同步语句块嵌套,首先获得对象锁lockA,然后执行一些代码,随后我们需要对象锁lockB去执行另外一些代码.
synchronized (LockTest.lockA) {
System.out.println("ThreadA lock lockA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LockTest.lockB) {
System.out.println("ThreadA lock lockB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class ProductThreadB implements Runnable {
//我们线程B的拿锁顺序相反,我们首先需要对象锁lockB,然后需要对象锁lockA.
@Override
public void run() {
synchronized (LockTest.lockB) {
System.out.println("ThreadB lock lockB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LockTest.lockA) {
System.out.println("ThreadB lock lockA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class LockTest {
//首先我们先定义两个final的对象锁.可以看做是共有的资源.
static final Object lockA = new Object();
static final Object lockB = new Object();
public static void main(String[] args) {
//运行两个线程
Thread threadA = new Thread(new ProductThreadA());
Thread threadB = new Thread(new ProductThreadB());
threadA.start();
threadB.start();
}
}
/*
ThreadA lock lockA
ThreadB lock lockB
*/
(3)线程2的run)(方法中同步代码块先获取lock2的对象锁,接着获取lock1的对象锁,当然这时lock1的对象锁已经被线程1锁持有,线程2肯定是要等待线程1释放lock1的对象锁的。因为偏向锁使用的是存在竞争才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
所以避免死锁是非常重要的,如果一个线程每次只能获得一个锁,那么就不会产生锁顺序的死锁。尽量保证一个线程每次仅使用一个锁。
如果必须要使用两个锁,下面介绍两种方法来避免死锁
如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。按照上面的例子,两个线程获得锁的时序图如下:

而我们可以改一下获取锁的顺序:

这样一个线程总会等待另一个线程释放同一个锁时候才执行,这就不会出现死锁情况。
当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,那就死锁了。
(3)线程2的run()方法中同步代码块先获取lock2的对象锁,接着获取lock1的对象锁,当然这时lock1的对象锁已经被线程1锁,线程2肯定是要等待线程1释放lock1的对象锁的。由于reentrantlock是重入锁,所以可以反复得到相同的一把锁,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放(重入锁)。线程在进入mutex保护的临界区之前通过lock()获取锁,获取锁之后可以执行临界区内的代码,退出临界区之后通过unlock释放锁。
时序图如下:
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-107572-2.html
你不如直接说人道毁灭好了
含盐