关于线程安全地懒汉式有以下几种方式:
/**
* 实现线程安全的懒汉式
*/
public class BankTest
{
Bank b1 = null;
Bank b2 = null;
public static void main(String[] args)
{
BankTest bb = new BankTest();
Thread t1 = new Thread(){
@Override
public void run()
{
bb.b1 = Bank.getInstance();
}
};
Thread t2 = new Thread(){
@Override
public void run()
{
bb.b2 = Bank.getInstance();
}
};
t1.start();
t2.start();
try
{
t1.join();
} catch (InterruptedException e)
{
e.getStackTrace();
}
try
{
t2.join();
} catch (InterruptedException e)
{
e.getStackTrace();
}
System.out.println(bb.b1);
System.out.println(bb.b2);
System.out.println(bb.b1 == bb.b2);
}
}
// 方式一:同步方法
//class Bank
//{
// private Bank(){};//私有化构造器
// private static Bank instance = null;//私有的类变量
// //实现线程安全的方式一
// public static synchronized Bank getInstance()//此时的同步监视器为Bank.class
// {
// if(instance == null)//只有当instance是非空指针时,才会创建新的对象
// {
// try
// {
// Thread.sleep(100);
// } catch (InterruptedException e)
// {
// e.getStackTrace();
// }
// instance = new Bank();
// }
// return instance;
// }
//}
// 方式二:同步代码块
//class Bank
//{
// private Bank(){};//私有化构造器
// private static Bank instance = null;//私有的类变量
// //实现线程安全的方式一
// public static Bank getInstance()
// {
// synchronized (Bank.class)
// {
// if(instance == null)//只有当instance是非空指针时,才会创建新的对象
// {
// try
// {
// Thread.sleep(100);
// } catch (InterruptedException e)
// {
// e.getStackTrace();
// }
// instance = new Bank();
// }
// return instance;
// }
// }
//}
// 方式三:加了一层if,相较于方式一和方式二效率更高
//为了避免指令重排,需要将instance声明为volatile。
class Bank
{
private Bank(){};//私有化构造器
//私有的类变量
private static volatile Bank instance = null;
//实现线程安全的方式一
public static Bank getInstance()
{
if(instance == null)
{
synchronized (Bank.class)
{
if (instance == null)//只有当instance是非空指针时,才会创建新的对象
{
try
{
Thread.sleep(100);
} catch (InterruptedException e)
{
e.getStackTrace();
}
instance = new Bank();
}
}
}
return instance;//让较后的线程可以更快地拿到instance对象。
}
}
方式三中,如果没有volatile关键字,可能会出现指令重排,当对象还未完全创建,但是已经提前return了。后面的线程判断时,未完全创建的对象也被判断为非空,对象的创建就会失败。从jdk2开始,分配空间、初始化、调用构造器会在线程的工作存储区一次性完成,然后复制到主存储区。但是需要volatile关键字,避免指令重排。
所以,要在instance的声明处加上volatile关键字。
死锁:
不同的线程分别占用对方的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
诱发死锁的原因:
①互斥条件
②占用且等待
③不可抢夺(或不可抢占)
④循环等待(一直等待)
以上四个条件同时出现就会触发死锁。
解决死锁:
死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件:
针对条件①:互斥基本无法被破坏。因为线程需要通过互斥解决安全问题。
针对条件②:可以考虑一次性申请所有所需的资源,就不存在等待的问题。
针对条件③:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。
针对条件④:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。
除了使用synchronized同步机制处理线程安全的问题之外,还可以使用jdk5.0提供的Lock锁的方式。
步骤:
第一步,创建lock的实例,需要确保多个线程共用同一个实例,需要考虑将此对象声明为static final。**
第二步,执行lock方法,锁定对共享资源的调用。
第三步, *unlock的调用,释放对共享数据的锁定。***
import java.util.concurrent.locks.ReentrantLock;
public class LockTest2
{
public static void main(String\[] args)
{
Window w1 = new Window("窗口一");
Window w2 = new Window("窗口二");
Window w3 = new Window("窗口三");
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread
{
public Window()
{
}
public Window(String name)
{
super(name);
}
private static int ticket = 100;
//第一步,创建lock的实例,需要确保多个线程共用同一个实例,需要考虑将此对象声明为static final
private static final ReentrantLock ll = new ReentrantLock();
@Override
public void run()
{
while(true)
{
try
{
//第二步,执行lock方法,锁定对共享资源的调用
ll.lock();
if(ticket > 0)
{
try
{
Thread.sleep(10);
} catch (InterruptedException e)
{
e.getStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出一张票,票号为" + ticket);
ticket--;
}
else
{
break;
}
}
finally
{
//第三步,unlock的调用,释放对共享数据的锁定
ll.unlock();//防止break之后不关闭锁,要将其放在finally中。
}
}
}
}
synchronized同步的方式与Lock的对比:
synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。
Lock是通过两个方法控制需要被同步的代码,更灵活一些。
Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高。
Thread类的常用方法和生命周期
线程(Thread)的常用结构:
·public Thread( ) :分配一个新的线程对象。
·public Thread(String name) :分配一个指定名字的新的线程对象。
·public Thread(Runnable target) :分配一个指定创建线程的目标对象(实现了Runnable接口的类的对象,并且该对象实现了Runnable中的run方法)。
·public Thread(Runnable target) :分配一个指定创建线程的目标对象并指定名字。
线程(Thread)中的常用方法:
·start( ) :①启动线程。②调用线程的run( )方法。
·run( ) :将线程要执行的操作,声明在run中。
·currentThread( ) :获取当前执行代码对应的线程。
·getName( ) :获取线程的名称。
·setName( ) :设置线程名。
·sleep( ) :(静态方法)调用时,可以使得当前线程睡眠指定的毫秒数。
·yield( ) :(静态方法)一旦执行此方法,就释放CPU的执行权。
·join( ) :在线程a中通过线程b调用join( ),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行。
·isAlive( ) :判断当前线程是否存活。
过时方法:
①stop( ) :强制结束一个线程的执行,直接让其进入死亡状态。(不建议使用)
suspend( ) / resume( ) :暂停 / 恢复 线程的执行。(可能会造成死锁,不建议使用)
线程的优先级:
getPriority( ) :获取线程的优先级。
setPriority( ) :设置线程的优先级。范围:[1,10]。
Thread类内部声明的三个与优先级有关的常量:
——MAX_PRIORITY(10) :最高优先级。
——MIN_PRIORITY(1) :最低优先级。
——NORM_PRIORITY(5):普通优先级,默认情况下main线程具有普通优先级。
注:优先级高并非先执行,而是有更大的概率使用CPU。
线程的生命周期: