java源码解读 java源码解读的书籍
大家好,今天小编来为大家解答java源码解读这个问题,java源码解读的书籍很多人还不知道,现在让我们一起来看看吧!
java 源代码注释
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class GameTest extends JFrame implements ActionListener{
/*
*新建一个主面板(这个类可能是自定义的,本程序和API中没有)。
*/
MainPanel j=new MainPanel();
JButton jPreview;
JLabel label;
Container container;
JPanel panel;
/**
*主函数
*@param args
*/
public static void main(String[] args){
//运行程序
new GameTest();
}
/**
*构造函数。
*
*/
public GameTest()
{
//新建一个标题为“拼图”的窗口
JFrame fr=new JFrame("拼图");
//获取窗口容器。
container=fr.getContentPane();
//创建菜单条
JMenuBar jMenuBar=new JMenuBar();
//以下初始化菜单,并且设置快捷键和添加监听器。
JMenu jMenuGame=new JMenu("游戏(G)");
jMenuGame.setMnemonic('g');
JMenuItem jMenuItemStart= new JMenuItem("开始(S)");
jMenuItemStart.setMnemonic('s');
jMenuItemStart.addActionListener(this);
JMenuItem jMenuItemExit=new JMenuItem("退出(E)");
jMenuItemExit.setMnemonic('e');
jMenuItemExit.addActionListener(this);
jMenuGame.add(jMenuItemStart);
jMenuGame.add(jMenuItemExit);
//初始化按钮并设置快捷键和添加监听器
JButton jChoice=new JButton("选图(X)");
jChoice.setMnemonic('x');
jChoice.addActionListener(this);
jPreview=new JButton("预览(P)");
jPreview.setMnemonic('p');
jPreview.addActionListener(this);
//将菜单和按钮添加到菜单条中
jMenuBar.add(jMenuGame);
jMenuBar.add(jChoice);
jMenuBar.add(jPreview);
//将菜单条设为该窗口的主菜单
fr.setJMenuBar(jMenuBar);
//将主面板添加到该窗口的容器中。
container.add(j);
//设置大小
fr.setSize(315,360);
fr.setVisible(true);
//设置默认关闭方式。
fr.setDefaultCloseOperation(3);
}
/**
*事件处理函数。
*/
public void actionPerformed(ActionEvent e){
if(e.getActionCommand()=="开始(S)")
{
j.Start();
}
if(e.getActionCommand()=="预览(P)")
{
j.setVisible(false);
panel=new JPanel();
Icon icon=new ImageIcon("pictrue/pic"+"_"+MainPanel.pictureID+".jpg");
label=new JLabel(icon);
label.setBounds(300, 300, 0, 0);
panel.add(label);
panel.setSize(300, 300);
panel.setVisible(true);
this.container.add(panel);
jPreview.setText("返回(P)");
}
if(e.getActionCommand()=="返回(P)")
{
panel.setVisible(false);
j.setVisible(true);
j.repaint();
jPreview.setText("预览(P)");
}
if(e.getActionCommand()=="退出(E)")
{
System.exit(0);
}
if(e.getActionCommand()=="选图(X)")
{
//初始化选择框,并提供选择。
Choice pic= new Choice();
pic.add("七里香");
pic.add("依然范特西");
pic.add("八度空间");
pic.add("十一月的肖邦");
pic.add("魔杰座");
pic.add("叶惠美");
pic.add("我很忙");
int i=JOptionPane.showConfirmDialog(this, pic,"选择图片", JOptionPane.OK_CANCEL_OPTION);
if(i==JOptionPane.YES_OPTION)
{
//选择图片
MainPanel.pictureID=pic.getSelectedIndex()+1;
j.removeAll();
j.reLoadPicture();
j.repaint();
}
}
}
}
如何有效防止Java程序源码被人偷窥
Java程序的源代码很容易被别人偷看只要有一个反编译器任何人都可以分析别人的代码本文讨论如何在不修改原有程序的情况下通过加密技术保护源代码一为什么要加密?
对于传统的C或C++之类的语言来说要在Web上保护源代码是很容易的只要不发布它就可以遗憾的是 Java程序的源代码很容易被别人偷看只要有一个反编译器任何人都可以分析别人的代码 Java的灵活性使得源代码很容易被窃取但与此同时它也使通过加密保护代码变得相对容易我们唯一需要了解的就是Java的ClassLoader对象当然在加密过程中有关Java Cryptography Extension(JCE)的知识也是必不可少的
有几种技术可以模糊 Java类文件使得反编译器处理类文件的效果大打折扣然而修改反编译器使之能够处理这些经过模糊处理的类文件并不是什么难事所以不能简单地依赖模糊技术来保证源代码的安全
我们可以用流行的加密工具加密应用比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)这时最终用户在运行应用之前必须先进行解密但解密之后最终用户就有了一份不加密的类文件这和事先不进行加密没有什么差别
Java运行时装入字节码的机制隐含地意味着可以对字节码进行修改 JVM每次装入类文件时都需要一个称为ClassLoader的对象这个对象负责把新的类装入正在运行的JVM JVM给ClassLoader一个包含了待装入类(比如java lang Object)名字的字符串然后由ClassLoader负责找到类文件装入原始数据并把它转换成一个Class对象
我们可以通过定制ClassLoader在类文件执行之前修改它这种技术的应用非常广泛??在这里它的用途是在类文件装入之时进行解密因此可以看成是一种即时解密器由于解密后的字节码文件永远不会保存到文件系统所以窃密者很难得到解密后的代码
由于把原始字节码转换成Class对象的过程完全由系统负责所以创建定制ClassLoader对象其实并不困难只需先获得原始数据接着就可以进行包含解密在内的任何转换
Java在一定程度上简化了定制ClassLoader的构建在Java中 loadClass的缺省实现仍旧负责处理所有必需的步骤但为了顾及各种定制的类装入过程它还调用一个新的findClass方法
这为我们编写定制的ClassLoader提供了一条捷径减少了麻烦只需覆盖findClass而不是覆盖loadClass这种方法避免了重复所有装入器必需执行的公共步骤因为这一切由loadClass负责
不过本文的定制ClassLoader并不使用这种方法原因很简单如果由默认的ClassLoader先寻找经过加密的类文件它可以找到;但由于类文件已经加密所以它不会认可这个类文件装入过程将失败因此我们必须自己实现loadClass稍微增加了一些工作量
二定制类装入器
每一个运行着的JVM已经拥有一个ClassLoader这个默认的ClassLoader根据CLASSPATH环境变量的值在本地文件系统中寻找合适的字节码文件
应用定制ClassLoader要求对这个过程有较为深入的认识我们首先必须创建一个定制ClassLoader类的实例然后显式地要求它装入另外一个类这就强制JVM把该类以及所有它所需要的类关联到定制的ClassLoader Listing显示了如何用定制ClassLoader装入类文件
【Listing利用定制的ClassLoader装入类文件】
以下是引用片段
//首先创建一个ClassLoader对象 ClassLoader myClassLoader= new myClassLoader();//利用定制ClassLoader对象装入类文件//并把它转换成Class对象 Class myClass= myClassLoader loadClass( mypackage MyClass);//最后创建该类的一个实例 Object newInstance= myClass newInstance();//注意 MyClass所需要的所有其他类都将通过//定制的ClassLoader自动装入
如前所述定制ClassLoader只需先获取类文件的数据然后把字节码传递给运行时系统由后者完成余下的任务
ClassLoader有几个重要的方法创建定制的ClassLoader时我们只需覆盖其中的一个即loadClass提供获取原始类文件数据的代码这个方法有两个参数类的名字以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)如果这个标记是true我们只需在返回JVM之前调用resolveClass
【Listing ClassLoader loadClass()的一个简单实现】
以下是引用片段
public Class loadClass( String name boolean resolve) throws ClassNotFoundException{ try{//我们要创建的Class对象 Class clasz= null;//必需的步骤如果类已经在系统缓冲之中//我们不必再次装入它 clasz= findLoadedClass( name); if(clasz!= null) return clasz;//下面是定制部分 byte classData[]=/*通过某种方法获取字节码数据*/; if(classData!= null){//成功读取字节码数据现在把它转换成一个Class对象 clasz= defineClass( name classData classData length);}//必需的步骤如果上面没有成功//我们尝试用默认的ClassLoader装入它 if(clasz== null) clasz= findSystemClass( name);//必需的步骤如有必要则装入相关的类 if(resolve&& clasz!= null) resolveClass( clasz);//把类返回给调用者 return clasz;} catch( IOException ie){ throw new ClassNotFoundException( ie toString());} catch( GeneralSecurityException gse){ throw new ClassNotFoundException( gse toString());}}
Listing显示了一个简单的loadClass实现代码中的大部分对所有ClassLoader对象来说都一样但有一小部分(已通过注释标记)是特有的在处理过程中 ClassLoader对象要用到其他几个辅助方法
findLoadedClass用来进行检查以便确认被请求的类当前还不存在 loadClass方法应该首先调用它
defineClass获得原始类文件字节码数据之后调用defineClass把它转换成一个Class对象任何loadClass实现都必须调用这个方法
findSystemClass提供默认ClassLoader的支持如果用来寻找类的定制方法不能找到指定的类(或者有意地不用定制方法)则可以调用该方法尝试默认的装入方式这是很有用的特别是从普通的JAR文件装入标准Java类时
resolveClass当JVM想要装入的不仅包括指定的类而且还包括该类引用的所有其他类时它会把loadClass的resolve参数设置成true这时我们必须在返回刚刚装入的Class对象给调用者之前调用resolveClass
三加密解密
Java加密扩展即Java Cryptography Extension简称JCE它是Sun的加密服务软件包含了加密和密匙生成功能 JCE是JCA(Java Cryptography Architecture)的一种扩展
JCE没有规定具体的加密算法但提供了一个框架加密算法的具体实现可以作为服务提供者加入除了JCE框架之外 JCE软件包还包含了SunJCE服务提供者其中包括许多有用的加密算法比如DES(Data Encryption Standard)和Blowfish
为简单计在本文中我们将用DES算法加密和解密字节码下面是用JCE加密和解密数据必须遵循的基本步骤
步骤生成一个安全密匙在加密或解密任何数据之前需要有一个密匙密匙是随同被加密的应用一起发布的一小段数据 Listing显示了如何生成一个密匙【Listing生成一个密匙】
以下是引用片段
// DES算法要求有一个可信任的随机数源 SecureRandom sr= new SecureRandom();//为我们选择的DES算法生成一个KeyGenerator对象 KeyGenerator kg= KeyGenerator getInstance( DES); kg init( sr);//生成密匙 SecretKey key= kg generateKey();//获取密匙数据 byte rawKeyData[]= key getEncoded();/*接下来就可以用密匙进行加密或解密或者把它保存为文件供以后使用*/ doSomething( rawKeyData);步骤加密数据得到密匙之后接下来就可以用它加密数据除了解密的ClassLoader之外一般还要有一个加密待发布应用的独立程序(见Listing)【Listing用密匙加密原始数据】
以下是引用片段
// DES算法要求有一个可信任的随机数源 SecureRandom sr= new SecureRandom(); byte rawKeyData[]=/*用某种方法获得密匙数据*/;//从原始密匙数据创建DESKeySpec对象 DESKeySpec dks= new DESKeySpec( rawKeyData);//创建一个密匙工厂然后用它把DESKeySpec转换成//一个SecretKey对象 SecretKeyFactory keyFactory= SecretKeyFactory getInstance( DES); SecretKey key= keyFactory generateSecret( dks);// Cipher对象实际完成加密操作 Cipher cipher= Cipher getInstance( DES);//用密匙初始化Cipher对象 cipher init( Cipher ENCRYPT_MODE key sr);//现在获取数据并加密 byte data[]=/*用某种方法获取数据*///正式执行加密操作 byte encryptedData[]= cipher doFinal( data);//进一步处理加密后的数据 doSomething( encryptedData);步骤解密数据运行经过加密的应用时 ClassLoader分析并解密类文件操作步骤如Listing所示【Listing用密匙解密数据】
// DES算法要求有一个可信任的随机数源 SecureRandom sr= new SecureRandom(); byte rawKeyData[]=/*用某种方法获取原始密匙数据*/;//从原始密匙数据创建一个DESKeySpec对象 DESKeySpec dks= new DESKeySpec( rawKeyData);//创建一个密匙工厂然后用它把DESKeySpec对象转换成//一个SecretKey对象 SecretKeyFactory keyFactory= SecretKeyFactory getInstance( DES); SecretKey key= keyFactory generateSecret( dks);// Cipher对象实际完成解密操作 Cipher cipher= Cipher getInstance( DES);//用密匙初始化Cipher对象 cipher init( Cipher DECRYPT_MODE key sr);//现在获取数据并解密 byte encryptedData[]=/*获得经过加密的数据*///正式执行解密操作 byte decryptedData[]= cipher doFinal( encryptedData);//进一步处理解密后的数据 doSomething( decryptedData);
四应用实例
前面介绍了如何加密和解密数据要部署一个经过加密的应用步骤如下
步骤创建应用我们的例子包含一个App主类两个辅助类(分别称为Foo和Bar)这个应用没有什么实际功用但只要我们能够加密这个应用加密其他应用也就不在话下
步骤生成一个安全密匙在命令行利用GenerateKey工具(参见GenerateKey java)把密匙写入一个文件% java GenerateKey key data
步骤加密应用在命令行利用EncryptClasses工具(参见EncryptClasses java)加密应用的类% java EncryptClasses key data App class Foo class Bar class
该命令把每一个 class文件替换成它们各自的加密版本
步骤运行经过加密的应用用户通过一个DecryptStart程序运行经过加密的应用 DecryptStart程序如Listing所示【Listing DecryptStart java启动被加密应用的程序】
以下是引用片段
import java io*; import java security*; import java lang reflect*; import javax crypto*; import javax crypto spec*; public class DecryptStart extends ClassLoader{//这些对象在构造函数中设置//以后loadClass()方法将利用它们解密类 private SecretKey key; private Cipher cipher;//构造函数设置解密所需要的对象 public DecryptStart( SecretKey key) throws GeneralSecurityException IOException{ this key= key; String algorithm= DES; SecureRandom sr= new SecureRandom(); System err println( [DecryptStart: creating cipher]); cipher= Cipher getInstance( algorithm); cipher init( Cipher DECRYPT_MODE key sr);}// main过程我们要在这里读入密匙创建DecryptStart的//实例它就是我们的定制ClassLoader//设置好ClassLoader以后我们用它装入应用实例//最后我们通过Java Reflection API调用应用实例的main方法 static public void main( String args[]) throws Exception{ String keyFilename= args[ ]; String appName= args[ ];//这些是传递给应用本身的参数 String realArgs[]= new String[args length ]; System arraycopy( args realArgs args length);//读取密匙 System err println( [DecryptStart: reading key]); byte rawKey[]= Util readFile( keyFilename); DESKeySpec dks= new DESKeySpec( rawKey); SecretKeyFactory keyFactory= SecretKeyFactory getInstance( DES); SecretKey key= keyFactory generateSecret( dks);//创建解密的ClassLoader DecryptStart dr= new DecryptStart( key);//创建应用主类的一个实例//通过ClassLoader装入它 System err println( [DecryptStart: loading+appName+ ]); Class clasz= dr loadClass( appName);//最后通过Reflection API调用应用实例//的main()方法//获取一个对main()的引用 String proto[]= new String[ ]; Class mainArgs[]={(new String[ ]) getClass()}; Method main= clasz getMethod( main mainArgs);//创建一个包含main()方法参数的数组 Object argsArray[]={ realArgs}; System err println( [DecryptStart: running+appName+ main()]);//调用main() main invoke( null argsArray);} public Class loadClass( String name boolean resolve) throws ClassNotFoundException{ try{//我们要创建的Class对象 Class clasz= null;//必需的步骤如果类已经在系统缓冲之中//我们不必再次装入它 clasz= findLoadedClass( name); if(clasz!= null) return clasz;//下面是定制部分 try{//读取经过加密的类文件 byte classData[]= Util readFile( name+ class); if(classData!= null){//解密 byte decryptedClassData[]= cipher doFinal( classData);//再把它转换成一个类 clasz= defineClass( name decryptedClassData decryptedClassData length); System err println( [DecryptStart: decrypting class+name+ ]);}} catch( FileNotFoundException fnfe)//必需的步骤如果上面没有成功//我们尝试用默认的ClassLoader装入它 if(clasz== null) clasz= findSystemClass( name);//必需的步骤如有必要则装入相关的类 if(resolve&& clasz!= null) resolveClass( clasz);//把类返回给调用者 return clasz;} catch( IOException ie){ throw new ClassNotFoundException( ie toString());} catch( GeneralSecurityException gse){ throw new ClassNotFoundException( gse toString());}}}对于未经加密的应用正常执行方式如下% java App arg arg arg
对于经过加密的应用则相应的运行方式为% java DecryptStart key data App arg arg arg
DecryptStart有两个目的一个DecryptStart的实例就是一个实施即时解密操作的定制ClassLoader;同时 DecryptStart还包含一个main过程它创建解密器实例并用它装入和运行应用示例应用App的代码包含在App java Foo java和Bar java内 Util java是一个文件I/O工具本文示例多处用到了它完整的代码请从本文最后下载
五注意事项
我们看到要在不修改源代码的情况下加密一个Java应用是很容易的不过世上没有完全安全的系统本文的加密方式提供了一定程度的源代码保护但对某些攻击来说它是脆弱的
虽然应用本身经过了加密但启动程序DecryptStart没有加密攻击者可以反编译启动程序并修改它把解密后的类文件保存到磁盘降低这种风险的办法之一是对启动程序进行高质量的模糊处理或者启动程序也可以采用直接编译成机器语言的代码使得启动程序具有传统执行文件格式的安全性
另外还要记住的是大多数JVM本身并不安全狡猾的黑客可能会修改JVM从ClassLoader之外获取解密后的代码并保存到磁盘从而绕过本文的加密技术 Java没有为此提供真正有效的补救措施
lishixinzhi/Article/program/Java/hx/201311/25751
java并发包源码怎么读
1.各种同步控制工具的使用
1.1 ReentrantLock
ReentrantLock感觉上是synchronized的增强版,synchronized的特点是使用简单,一切交给JVM去处理,但是功能上是比较薄弱的。在JDK1.5之前,ReentrantLock的性能要好于synchronized,由于对JVM进行了优化,现在的JDK版本中,两者性能是不相上下的。如果是简单的实现,不要刻意去使用ReentrantLock。
相比于synchronized,ReentrantLock在功能上更加丰富,它具有可重入、可中断、可限时、公平锁等特点。
首先我们通过一个例子来说明ReentrantLock最初步的用法:
package test;
import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock= new ReentrantLock(); public static int i= 0;
@Override public void run(){ for(int j= 0; j< 10000000; j++)
{ lock.lock(); try
{
i++;
} finally
{ lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException{
Test test= new Test();
Thread t1= new Thread(test);
Thread t2= new Thread(test);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
有两个线程都对i进行++操作,为了保证线程安全,使用了ReentrantLock,从用法上可以看出,与synchronized相比,ReentrantLock就稍微复杂一点。因为必须在finally中进行解锁操作,如果不在finally解锁,有可能代码出现异常锁没被释放,而synchronized是由JVM来释放锁。
那么ReentrantLock到底有哪些优秀的特点呢?
1.1.1可重入
单线程可以重复进入,但要重复退出
lock.lock();
lock.lock();try{
i++;
}
finally{
lock.unlock();
lock.unlock();
}
由于ReentrantLock是重入锁,所以可以反复得到相同的一把锁,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放(重入锁)。这模仿了synchronized的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized块,就允许线程继续进行,当线程退出第二个(或者后续)synchronized块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个synchronized块时,才释放锁。
public class Child extends Father implements Runnable{ final static Child child= new Child();//为了保证锁唯一
public static void main(String[] args){ for(int i= 0; i< 50; i++){ new Thread(child).start();
}
}
public synchronized void doSomething(){
System.out.println("1child.doSomething()");
doAnotherThing();//调用自己类中其他的synchronized方法
}
private synchronized void doAnotherThing(){ super.doSomething();//调用父类的synchronized方法
System.out.println("3child.doAnotherThing()");
}
@Override
public void run(){
child.doSomething();
}
}class Father{ public synchronized void doSomething(){
System.out.println("2father.doSomething()");
}
}
我们可以看到一个线程进入不同的synchronized方法,是不会释放之前得到的锁的。所以输出还是顺序输出。所以synchronized也是重入锁
输出:
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
...
1.1.2.可中断
与synchronized不同的是,ReentrantLock对中断是有响应的。中断相关知识查看[高并发Java二]多线程基础
普通的lock.lock()是不能响应中断的,lock.lockInterruptibly()能够响应中断。
我们模拟出一个死锁现场,然后用中断来处理死锁
package test;import java.lang.management.ManagementFactory;import java.lang.management.ThreadInfo;import java.lang.management.ThreadMXBean;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock1= new ReentrantLock(); public static ReentrantLock lock2= new ReentrantLock(); int lock; public Test(int lock)
{ this.lock= lock;
}@Override
public void run()
{ try
{ if(lock== 1)
{
lock1.lockInterruptibly(); try
{
Thread.sleep(500);
} catch(Exception e)
{// TODO: handle exception
}
lock2.lockInterruptibly();
} else
{
lock2.lockInterruptibly(); try
{
Thread.sleep(500);
} catch(Exception e)
{// TODO: handle exception
}
lock1.lockInterruptibly();
}
} catch(Exception e)
{// TODO: handle exception
} finally
{ if(lock1.isHeldByCurrentThread())
{
lock1.unlock();
} if(lock2.isHeldByCurrentThread())
{
lock2.unlock();
}
System.out.println(Thread.currentThread().getId()+":线程退出");
}
} public static void main(String[] args) throws InterruptedException{
Test t1= new Test(1);
Test t2= new Test(2);
Thread thread1= new Thread(t1);
Thread thread2= new Thread(t2);
thread1.start();
thread2.start();
Thread.sleep(1000);//DeadlockChecker.check();
} static class DeadlockChecker
{ private final static ThreadMXBean mbean= ManagementFactory
.getThreadMXBean(); final static Runnable deadlockChecker= new Runnable()
{@Override
public void run()
{// TODO Auto-generated method stub
while(true)
{ long[] deadlockedThreadIds= mbean.findDeadlockedThreads(); if(deadlockedThreadIds!= null)
{
ThreadInfo[] threadInfos= mbean.getThreadInfo(deadlockedThreadIds); for(Thread t: Thread.getAllStackTraces().keySet())
{ for(int i= 0; i< threadInfos.length; i++)
{ if(t.getId()== threadInfos[i].getThreadId())
{
t.interrupt();
}
}
}
} try
{
Thread.sleep(5000);
} catch(Exception e)
{// TODO: handle exception
}
}
}
};
public static void check()
{
Thread t= new Thread(deadlockChecker);
t.setDaemon(true);
t.start();
}
}
}
上述代码有可能会发生死锁,线程1得到lock1,线程2得到lock2,然后彼此又想获得对方的锁。
我们用jstack查看运行上述代码后的情况
的确发现了一个死锁。
DeadlockChecker.check();方法用来检测死锁,然后把死锁的线程中断。中断后,线程正常退出。
1.1.3.可限时
超时不能获得锁,就返回false,不会永久等待构成死锁
使用lock.tryLock(long timeout, TimeUnit unit)来实现可限时锁,参数为时间和单位。
举个例子来说明下可限时:
package test;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock= new ReentrantLock();@Override
public void run()
{ try
{ if(lock.tryLock(5, TimeUnit.SECONDS))
{
Thread.sleep(6000);
} else
{
System.out.println("get lock failed");
}
} catch(Exception e)
{
} finally
{ if(lock.isHeldByCurrentThread())
{
lock.unlock();
}
}
}
public static void main(String[] args)
{
Test t= new Test();
Thread t1= new Thread(t);
Thread t2= new Thread(t);
t1.start();
t2.start();
}
}
使用两个线程来争夺一把锁,当某个线程获得锁后,sleep6秒,每个线程都只尝试5秒去获得锁。
所以必定有一个线程无法获得锁。无法获得后就直接退出了。
输出:
get lock failed
1.1.4.公平锁
使用方式:
public ReentrantLock(boolean fair) public static ReentrantLock fairLock= new ReentrantLock(true);
一般意义上的锁是不公平的,不一定先来的线程能先得到锁,后来的线程就后得到锁。不公平的锁可能会产生饥饿现象。
公平锁的意思就是,这个锁能保证线程是先来的先得到锁。虽然公平锁不会产生饥饿现象,但是公平锁的性能会比非公平锁差很多。
1.2 Condition
Condition与ReentrantLock的关系就类似于synchronized与Object.wait()/signal()
await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。
awaitUninterruptibly()方法与await()方法基本相同,但是它并不会再等待过程中响应中断。 singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obejct.notify()方法很类似。
这里就不再详细介绍了。举个例子来说明:
package test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock= new ReentrantLock(); public static Condition condition= lock.newCondition();
@Override public void run(){ try
{ lock.lock();
condition.await();
System.out.println("Thread is going on");
} catch(Exception e)
{
e.printStackTrace();
} finally
{ lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException{
Test t= new Test();
Thread thread= new Thread(t);
thread.start();
Thread.sleep(2000);
lock.lock();
condition.signal(); lock.unlock();
}
}
上述例子很简单,让一个线程await住,让主线程去唤醒它。condition.await()/signal只能在得到锁以后使用。
1.3.Semaphore
对于锁来说,它是互斥的排他的。意思就是,只要我获得了锁,没人能再获得了。
而对于Semaphore来说,它允许多个线程同时进入临界区。可以认为它是一个共享锁,但是共享的额度是有限制的,额度用完了,其他没有拿到额度的线程还是要阻塞在临界区外。当额度为1时,就相等于lock
下面举个例子:
package test;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;public class Test implements Runnable{ final Semaphore semaphore= new Semaphore(5);@Override
public void run()
{ try
{
semaphore.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId()+" done");
} catch(Exception e)
{
e.printStackTrace();
}finally{
semaphore.release();
}
}
public static void main(String[] args) throws InterruptedException{
ExecutorService executorService= Executors.newFixedThreadPool(20); final Test t= new Test(); for(int i= 0; i< 20; i++)
{
executorService.submit(t);
}
}
}
有一个20个线程的线程池,每个线程都去Semaphore的许可,Semaphore的许可只有5个,运行后可以看到,5个一批,一批一批地输出。
当然一个线程也可以一次申请多个许可
public void acquire(int permits) throws InterruptedException
1.4 ReadWriteLock
ReadWriteLock是区分功能的锁。读和写是两种不同的功能,读-读不互斥,读-写互斥,写-写互斥。
这样的设计是并发量提高了,又保证了数据安全。
使用方式:
private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private static Lock readLock= readWriteLock.readLock();
private static Lock writeLock= readWriteLock.writeLock();
详细例子可以查看Java实现生产者消费者问题与读者写者问题,这里就不展开了。
1.5 CountDownLatch
倒数计时器
一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程
,等待所有检查线程全部完工后,再执行
使用方式:
static final CountDownLatch end= new CountDownLatch(10);
end.countDown();
end.await();
示意图:
一个简单的例子:
package test;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Test implements Runnable{ static final CountDownLatch countDownLatch= new CountDownLatch(10); static final Test t= new Test();@Override
public void run()
{ try
{
Thread.sleep(2000);
System.out.println("complete");
countDownLatch.countDown();
} catch(Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException{
ExecutorService executorService= Executors.newFixedThreadPool(10); for(int i= 0; i< 10; i++)
{
executorService.execute(t);
}
countDownLatch.await();
System.out.println("end");
executorService.shutdown();
}
}
主线程必须等待10个线程全部执行完才会输出"end"。
1.6 CyclicBarrier
和CountDownLatch相似,也是等待某些线程都做完以后再执行。与CountDownLatch区别在于这个计数器可以反复使用。比如,假设我们将计数器设置为10。那么凑齐第一批1 0个线程后,计数器就会归零,然后接着凑齐下一批10个线程
使用方式:
public CyclicBarrier(int parties, Runnable barrierAction) barrierAction就是当计数器一次计数完成后,系统会执行的动作await()
示意图:
下面举个例子:
package test;import java.util.concurrent.CyclicBarrier;public class Test implements Runnable{ private String soldier; private final CyclicBarrier cyclic; public Test(String soldier, CyclicBarrier cyclic)
{ this.soldier= soldier; this.cyclic= cyclic;
}@Override
public void run()
{ try
{//等待所有士兵到齐
cyclic.await();
dowork();//等待所有士兵完成工作
cyclic.await();
} catch(Exception e)
{// TODO Auto-generated catch block
e.printStackTrace();
}
} private void dowork()
{// TODO Auto-generated method stub
try
{
Thread.sleep(3000);
} catch(Exception e)
{// TODO: handle exception
}
System.out.println(soldier+": done");
} public static class BarrierRun implements Runnable
{ boolean flag; int n; public BarrierRun(boolean flag, int n)
{ super(); this.flag= flag; this.n= n;
}@Override
public void run()
{ if(flag)
{
System.out.println(n+"个任务完成");
} else
{
System.out.println(n+"个集合完成");
flag= true;
}
}
} public static void main(String[] args)
{ final int n= 10;
Thread[] threads= new Thread[n]; boolean flag= false;
CyclicBarrier barrier= new CyclicBarrier(n, new BarrierRun(flag, n));
System.out.println("集合"); for(int i= 0; i< n; i++)
{
System.out.println(i+"报道");
threads[i]= new Thread(new Test("士兵"+ i, barrier));
threads[i].start();
}
}
}
打印结果:
集合
士兵5: done士兵7: done士兵8: done士兵3: done士兵4: done士兵1: done士兵6: done士兵2: done士兵0: done士兵9: done10个任务完成
1.7 LockSupport
提供线程阻塞原语
和suspend类似
LockSupport.park();
LockSupport.unpark(t1);
与suspend相比不容易引起线程冻结
LockSupport的思想呢,和Semaphore有点相似,内部有一个许可,park的时候拿掉这个许可,unpark的时候申请这个许可。所以如果unpark在park之前,是不会发生线程冻结的。
下面的代码是[高并发Java二]多线程基础中suspend示例代码,在使用suspend时会发生死锁。
而使用LockSupport则不会发生死锁。
另外
park()能够响应中断,但不抛出异常。中断响应的结果是,park()函数的返回,可以从Thread.interrupted()得到中断标志。
在JDK当中有大量地方使用到了park,当然LockSupport的实现也是使用unsafe.park()来实现的。
public static void park(){ unsafe.park(false, 0L);
}
1.8 ReentrantLock的实现
下面来介绍下ReentrantLock的实现,ReentrantLock的实现主要由3部分组成:
CAS状态
等待队列
park()
ReentrantLock的父类中会有一个state变量来表示同步的状态
/**
* The synchronization state.
*/
private volatile int state;
通过CAS操作来设置state来获取锁,如果设置成了1,则将锁的持有者给当前线程
final void lock(){ if(compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread()); else
acquire(1);
}
如果拿锁不成功,则会做一个申请
public final void acquire(int arg){ if(!tryAcquire(arg)&&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先,再去申请下试试看tryAcquire,因为此时可能另一个线程已经释放了锁。
如果还是没有申请到锁,就addWaiter,意思是把自己加到等待队列中去
其间还会有多次尝试去申请锁,如果还是申请不到,就会被挂起
private final boolean parkAndCheckInterrupt(){
LockSupport.park(this); return Thread.interrupted();
}
同理,如果在unlock操作中,就是释放了锁,然后unpark,这里就不具体讲了。
2.并发容器及典型源码分析
2.1ConcurrentHashMap
我们知道HashMap不是一个线程安全的容器,最简单的方式使HashMap变成线程安全就是使用Collections.synchronizedMap,它是对HashMap的一个包装
public static Map m=Collections.synchronizedMap(new HashMap());
同理对于List,Set也提供了相似方法。
但是这种方式只适合于并发量比较小的情况。
我们来看下synchronizedMap的实现
它会将HashMap包装在里面,然后将HashMap的每个操作都加上synchronized。
由于每个方法都是获取同一把锁(mutex),这就意味着,put和remove等操作是互斥的,大大减少了并发量。
下面来看下ConcurrentHashMap是如何实现的
在ConcurrentHashMap内部有一个Segment段,它将大的HashMap切分成若干个段(小的HashMap),然后让数据在每一段上Hash,这样多个线程在不同段上的Hash操作一定是线程安全的,所以只需要同步同一个段上的线程就可以了,这样实现了锁的分离,大大增加了并发量。
在使用ConcurrentHashMap.size时会比较麻烦,因为它要统计每个段的数据和,在这个时候,要把每一个段都加上锁,然后再做数据统计。这个就是把锁分离后的小小弊端,但是size方法应该是不会被高频率调用的方法。
在实现上,不使用synchronized和lock.lock而是尽量使用trylock,同时在HashMap的实现上,也做了一点优化。这里就不提了。
2.2BlockingQueue
BlockingQueue不是一个高性能的容器。但是它是一个非常好的共享数据的容器。是典型的生产者和消费者的实现。
好了,本文到此结束,如果可以帮助到大家,还望关注本站哦!