高质量技术免费分享
https://blog.csdn.net/everyonetimeismoney/article/details/94412711
想快速精通多线程?看这里 http://blog.chinaunix.net/uid-540802-id-4431193.html
推荐大家看《Netty实战》
什么是线程
线程,是程序执行流的最小单元。是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可以与同属一个进程的其他线程共享进程所拥有的全部资源,一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。在单个程序中同时运行多个线程完成不同的工作,称为多线程。当自己想了解更多的时候去这看看http://baike.baidu.com/view/1053.htm?fr=aladdin
装多线程的包:java.util.concurrent
关于关键字
volatitle
http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
接口
Lock接口的实现类
ReentrantLock的吞吐量高于synchronized而且还灵活
http://www.tuicool.com/articles/MRj6Jr
各种锁的运用场景
读写锁的运用场景
http://blog.csdn.net/s_kuang/article/details/4275513
使用线程注意的地方
线程的stop()方法已经被废止了,因为它不释放线程获得的锁,并且如果线程出于不一致的状态(受损状态),其他任务可以在这中状态下浏览并修改他们。注意所产生的问题是微妙而难以被发现的。---------------来源<thinkjava> p695,注意是thinkjava,not a thinkpad
多线程的状态
★简单状态
★详细状态
JDK1.5以前的多线程并发
创建线程的方式
JDK1.5以前的方法
//第一种:
/**
* 第一种方式
*/
Thread thread = new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获得当前线程对象的名字
System.out.println(""+Thread.currentThread().getName());
//工作中不建议这么做,this代表new 出来的Thread对象
System.out.println(""+this.getName());
}
}
};
thread.start();
//第二种:
/**
* 第二种方式
*/
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获得当前线程对象的名字
System.out.println(Thread.currentThread().getName());
//工作中不建议这么做,this代表new出来的Runnable接口的实现类,Runnable接口没有getName方法,只有一个run方法
//System.out.println("2:"+this.getName());//这里报错
}
}
});
thread2.start();
运行输出:
Thread-0
Thread-0
Thread-1
Thread-0
Thread-0
Thread-1
Thread-0
Thread-0
Thread-1
Thread-0
Thread-0
Thread-1
Thread-0
这两种基本没什么区别,但是第二种方式,体现得更加面向对象了,面向接口编程。
定时器的应用(Timer)
Timer的用处还是很大的,比如手机中的闹钟,每天3点收邮件等。想实现复杂的需求比如:星期1-5闹钟每天8点闹,星期6-7不闹,可以用quarlz开源的工具来做。
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。Quartz的最新版本为Quartz 2.2.1。
示例代码:
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("bombing");
}
}, 10000);
while (true) {
System.out.println(new Date().getSeconds());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
运行输出:
49
50
51
52
53
54
55
56
57
58
bombing
问题一:请实现有一个炸弹,2秒后炸了一次,过了4秒又有一个炸弹炸了一次,过了2秒又炸一个,就这样反复循环。
示例代码一、
//------这些代码要学好,在android中的大部分程序都会用到下面的思想------
final int x = 0;
class MyTimerTask extends TimerTask{
//int x = 0;//每次都会new一个新的对象,那这里的x的值会一直是1
@Override
public void run() {
count = (count +1)%2;
// TODO Auto-generated method stub
System.out.println("bombing");
new Timer().schedule(new MyTimerTask(), 2000+2000*count);
}
}
new Timer().schedule(new MyTimerTask(), 2000);
//----------------------------------------------------------
示例代码二、
★MyTimerTask1类
import java.util.Timer;
import java.util.TimerTask;
public class MyTimerTask1 extends TimerTask{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("bombing");
MyTimerTask2 myTimerTask2 = new MyTimerTask2();
new Timer().schedule(myTimerTask2, 4000);
}
}
★MyTimerTask2类
import java.util.Timer;
import java.util.TimerTask;
public class MyTimerTask2 extends TimerTask{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("bombing");
MyTimerTask1 myTimerTask1 = new MyTimerTask1();
new Timer().schedule(myTimerTask1, 2000);
}
}
★在main方法中运行,结果和上面的一样
new Timer().schedule(new MyTimerTask1(),0);
while (true) {
System.out.println(new Date().getSeconds());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
线程的同步互斥与通信
★异步的情况
例如访问同一个资源:
例如车站的售票系统,一定是多个线程同时运行售票的吧。
有编号为10000这张票,那么线程thread1正在卖它的时候,其余的线程就不能也卖这张票了。
★同步的情况
另一个就是著名的生产者-消费者问题。类比一下,老爸挣钱,打到银行卡里,儿子花钱。(假设卡里有钱的话就暂时不打钱)
对于老爸老说,如果要打钱,查看一下卡,如果有钱,就暂时不放,并通知儿子已经有钱;如果没钱,当然就打钱了。
对于儿子来说,取钱时如果有钱就取,如果没有就等待,并通知老爸没钱了。
这就是同步了。
线程的互斥
使用synchronized
什么是synchronized?
synchronized (同步的意思)关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法,有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,直接运行。它包括两种用法:synchronized 方法和 synchronized 块。
更详细的解释:召唤传送门---------->http://baike.baidu.com/view/1207212.htm?fr=aladdin
典型的案例:银行取钱、存钱。
★互斥代码块:
synchronized(Object a){
}
上面的这种写法,想要达到互斥的效果,a这个对象必须要保持一致,不一致就达不到互斥效果。(每个对象都有自己的内部锁),
线程的同步
1. 线程的挂起和唤醒
挂起实际上是让线程进入“非可执行”状态下,在这个状态下CPU不会分给线程时间片,进入这个状态可以用来暂停一个线程的运行;在线程挂起后,可以通过重新唤醒线程来使之恢复运行。
挂起的原因可能是如下几种情况:
(1)通过调用sleep()方法使线程进入休眠状态,线程在指定时间内不会运行。
(2)通过调用join()方法使线程挂起,使自己等待另一个线程的结果,直到另一个线程执行完毕为止。
(3)通过调用wait()方法使线程挂起,直到线程得到了notify()和notifyAll()消息,线程才会进入“可执行”状态。
(4)使用suspend挂起线程后,可以通过resume方法唤醒线程。
虽然suspend和resume可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成死锁,因此,这两个方法被标识为 deprecated(抗议)标记,这表明在以后的jdk版本中这两个方法可能被删除,所以尽量不要使用这两个方法来操作线程。
调用sleep()、yield()、suspend()的时候并没有被释放锁
调用wait()的时候释放当前对象的锁
wait()方法表示,放弃当前对资源的占有权,一直等到有线程通知,才会运行后面的代码。
notify()方法表示,当前的线程已经放弃对资源的占有,通知等待的线程来获得对资源的占有权,但是只有一个线程能够从wait状态中恢复,然后继续运行wait()后面的语句。
notifyAll()方法表示,当前的线程已经放弃对资源的占有,通知所有的等待线程从wait()方法后的语句开始运行。
2、面试题:
子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次, 接着再回到主线程又循环100次,如此循环50次,请写出程序。
互斥实现了,就是交替执行弄不出来。
package cn.itcast.heima2;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,
* 接着再回到主线程又循环100次,如此循环50次,请写出程序。
* @author Terry
* @date 2014-6-3
*
*/
public class Question28 {
/**
* @param args
*/
public static void main(String[] args){
// TODO Auto-generated method stub
final Cycle cycle = new Cycle();
for (int i = 0; i < 50; i++) {
/**
* 线程1
*/
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
cycle.cycle100();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
/**
* 线程2
*/
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
cycle.cycle10();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
static class Cycle{
Lock lock1 = new ReentrantLock();//这样写,更具有面向对象的思想
int i = 0 ;
public void cycle100() throws Exception{
lock1.lock();
for (int i = 0; i < 100; i++) {
System.out.print(i+"\t");
}
System.out.println("主线程循环100次完成!");
//lock1.unlock();//一定要关锁要不可能出现死循环,这里不能和wait()方法一起使用,因为wait()会释放锁,锁被释放了一次,在被释放一次的话,拿什么来释放呢?
lock1.unlock();
}
public void cycle10() throws Exception{
lock1.lock();//获得锁对象
for (int i = 0; i < 10; i++) {
System.out.print(i+"\t");
}
System.out.println("子线程循环10次完成!");
lock1.unlock();//一定要关锁要不可能出现死循环
}
}
}
看视频后弄出来的:
package cn.itcast.heima2;
/**
* 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,
* 接着再回到主线程又循环100次,如此循环50次,请写出程序。
* @author Terry
* @date 2014-6-3
*
*/
public class Question28 {
/**
* @param args
*/
public static void main(String[] args){
final Business business = new Business();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
business.sub(i);
}
}
}).start();
for (int i = 0; i < 50; i++) {
business.main(i);
}
}
static class Business{
private boolean bShouldSub = true;
public synchronized void sub(int i){
{
try {
this.wait();
</span> } catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int j = 0; j < 10; j++) {
System.out.println("sub Thread sequnce of " + j + ",loop of" +i);
}
bShouldSub = false;</span>
this.notifyAll();</span>
}
public synchronized void main(int i){
{//这里用while和if的区别,while:线程被唤醒后,会返回检查条件语句,如果不该它执行的话它会继续等待,这//样更严谨一些。而if不返回再检查就直接往下走了,如果不该它执行,它还是会执行
try {
this.wait();</span>
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int j = 0; j < 100; j++) {
System.out.println("main Thread sequnce of " + j + ",loop of" +i);
}
bShouldSub = true;
this.notifyAll();</span>
}
}
}
线程范围内的共享数据(javaEE中用得比较多的)
ThreadLocal实现线程范围的共享变量
什么是线程范围内的共享变量
线程范围内的共享变量是指对同一个变量,几个线程同时对它进行写和读操作,
我们希望的是同一个线程读到的数据就是它自己写进去的数据。
对于这种情况可以采用在读和写操作前面加上synchronized修饰符;
或者是将写的数据放入一个Map的value中,key就是进行这个写操作的线程对象,
这样读的时候每个线程就只能从Map中读到自己对应的value。
还有就是用ThreadLocal,ThreadLocal类似Map,它可以为每个线程装载一个对象,这个对象是和线程绑定的,
ThreadLocal在哪个线程中执行操作,它就会自动选择那个与线程绑定的对象。
最后还有一点,任何涉及公共资源的操作都有可能引起多个线程之间的相互影响,包括run方法,所以我的建议是尽量将对共享资源的操作封装到一个类中,并在run方法中用synchronized修饰那些涉及公共资源的操作。
线程范围内的共享变量有什么用
其应用场景有:
1.订单处理包含一系列操作:减少库存,增加一条流水总账,修改总账,这几个操作要在同一个事务中完成,通常即同一个线程中进行处理,如果累加公司应收款的操作失败,则应该把前面的操作回滚。负责提交所有操作,这要求这些操作要使用相同的数据连接对象,而这些操作代码分别在不同的模块类中。(如果不线程范围内的数据共享,那当A处理订单失败时,正在回滚事务,这时候B操作完成,提交事务,他们用的又是同一连接对象,就会发生B把A的事务提交了的情况。)
2.银行转账包含一系列操作:把转出账户的余额减少,把转入账户的余额增加,这两个操作要在同一个事务中完成,他们必须使用相同的数据库连接对象,转入和转出操作的代码是在两个不同的模块类中。
示例代码一(未实现线程范围内的变量共享):
package cn.itcast.heima2;
import java.util.Random;
public class ThreadScopeShareData {
private static int data = 0;
/**
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
data = new Random().nextInt();
System.out.println(Thread.currentThread().getName()+"has put data :" + data);
new A().get();
new B().get();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
static class A{
public void get(){
System.out.println("A from " + Thread.currentThread().getName()+"has put data :" + data);
}
}
static class B{
public void get(){
System.out.println("B from " + Thread.currentThread().getName()+"has put data :" + data);
}
}
}
运行输出:
Thread-1has put data :821774126
Thread-3has put data :1260205626
Thread-4has put data :821774126
Thread-2has put data :-587545771
Thread-0has put data :-587545771
A from Thread-2has put data :1260205626
A from Thread-4has put data :1260205626
A from Thread-0has put data :1260205626
A from Thread-1has put data :1260205626
A from Thread-3has put data :1260205626
B from Thread-3has put data :1260205626
B from Thread-1has put data :1260205626
B from Thread-0has put data :1260205626
B from Thread-2has put data :1260205626
B from Thread-4has put data :1260205626
可以看出线程0-4,装得有不同的数据,但是他们用不同的对象得到的全是一样的数据。这不是想要的结果。
用Map集合实现线程范围内的数据共享
package cn.itcast.heima2;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class ThreadScopeShareData {
private static int data = 0;
private static Map<Thread, Integer> map = new HashMap<Thread, Integer>();
/**
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int data = new Random().nextInt();
map.put(Thread.currentThread(), data);
System.out.println(Thread.currentThread().getName()+"has put data :" + data);
new A().get();
new B().get();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
static class A{
public void get(){
System.out.println("A from " + Thread.currentThread().getName()+"has put data :" + map.get(Thread.currentThread()));
}
}
static class B{
public void get(){
System.out.println("B from " + Thread.currentThread().getName()+"has put data :" + map.get(Thread.currentThread()));
}
}
}
运行输出:
Thread-3has put data :-1592921848
Thread-2has put data :-1773769156
Thread-4has put data :-508881693
Thread-1has put data :-267320681
A from Thread-4has put data :-508881693
A from Thread-2has put data :-1773769156
A from Thread-3has put data :-1592921848
A from Thread-1has put data :-267320681
B from Thread-4has put data :-508881693
B from Thread-2has put data :-1773769156
B from Thread-1has put data :-267320681
B from Thread-3has put data :-1592921848
Thread-0has put data :1639540841
A from Thread-0has put data :1639540841
B from Thread-0has put data :1639540841
这是想要的结果。
用ThreadLocal实现线程范围内的变量共享
★示例代码:
package cn.itcast.heima2;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class ThreadScopeShareData {
private static int data = 0;
private static Map<Thread, Integer> map = new HashMap<Thread, Integer>();
private static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();//其里面的实现原理类似于用map
/**
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int data = new Random().nextInt();
//map.put(Thread.currentThread(), data);
threadLocal.set(data);
System.out.println(Thread.currentThread().getName()+"has put data :" + data);
new A().get();
new B().get();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
static class A{
public void get(){
System.out.println("A from " + Thread.currentThread().getName()+"has put data :" + threadLocal.get());
}
}
static class B{
public void get(){
System.out.println("B from " + Thread.currentThread().getName()+"has put data :" + threadLocal.get());
}
}
}
运行的效果,和用map的一样。
ThreadLocal里面只能装一个变量,但是如果我要实现一个线程范围内共享多个变量,怎么弄呢?可以用对象来实现。
示例代码:
package cn.itcast.heima2;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class ThreadScopeShareData {
private static int data = 0;
private static Map<Thread, Integer> map = new HashMap<Thread, Integer>();
private static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();//其里面的实现原理类似于用map
private static ThreadLocal<Student> threadLocalStudent = new ThreadLocal<Student>();//其里面的实现原理类似于用map
/**
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int data = new Random().nextInt();
//map.put(Thread.currentThread(), data);
Student st = new Student();
st.setAge(data);
st.setName(String.valueOf(data));
threadLocalStudent.set(st);
System.out.println(Thread.currentThread().getName()+"has put data :" + data);
new A().get();
new B().get();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
static class A{
public void get(){
System.out.println("A from " + Thread.currentThread().getName()+"has put data :" + "name:"+threadLocalStudent.get().getName()+","+"age:"+threadLocalStudent.get().getAge());
}
}
static class B{
public void get(){
System.out.println("B from " + Thread.currentThread().getName()+"has put data :" + "name:"+threadLocalStudent.get().getName()+","+"age:"+threadLocalStudent.get().getAge());
}
}
static class Student{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
下面有中更优雅的写法:用到了单例设计模式,还解释了饥寒模式和饱寒模式。
package cn.itcast.heima2;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class ThreadScopeShareData {
private static int data = 0;
private static Map<Thread, Integer> map = new HashMap<Thread, Integer>();
private static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();//其里面的实现原理类似于用map
private static ThreadLocal<Student> threadLocalStudent = new ThreadLocal<Student>();//其里面的实现原理类似于用map
/**
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int data = new Random().nextInt();
//map.put(Thread.currentThread(), data);
//Student st = new Student();
//st.setAge(data);
//st.setName(String.valueOf(data));
//threadLocalStudent.set(st);
Student st = Student.getInstance();
st.setName(String.valueOf(data));
st.setAge(data);
System.out.println(Thread.currentThread().getName()+"has put data :" + data);
new A().get();
new B().get();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
static class A{
public void get(){
Student st = Student.getInstance();
System.out.println("A from " + Thread.currentThread().getName()+"has get data :" + "name:"+st.getName()+","+"age:"+st.getAge());
}
}
static class B{
public void get(){
//Student st = Student.getInstance();
// Student.getInstance().getAge();
// System.out.println("B from " + Thread.currentThread().getName()+"has put data :" + "name:"+st.getName()+","+"age:"+st.getAge());
System.out.println("B from " + Thread.currentThread().getName()+"has get data :" + "name:"+Student.getInstance().getName()+","+"age:"+Student.getInstance().getAge());
}
}
}
class Student{
// --------------------- 单例设计模式----------------------
// ★饱寒模式的写法★对象一开始就存在,不管你调不调用,实例对象都在,永远不调用,对象也存在
// private Student(){}//构造函数私有化,使外边不能创建其实例对象
// public static Student getInstance(){//这方法为什么要用static的?因为此类的构造方法私有化了,外面不能创建此对象,就无法调用其里面的非静态方法,而静态方法是优先在内存中加载的。
// return instance;
// }
// private static Student instance = new Student();//如果这里不写static呢?可以吗?不可以,静态方法调用静态变量
// ★饥寒模式的写法★只有第一次用的时候被创建,其他时候就直接用创建好的,如果一直不调用,就一直不创建。
// private Student(){}//构造函数私有化,使外边不能创建其实例对象
// public static synchronized Student getInstance(){//这里为什么要加上互斥代码synchronized?因为这里的代码,是多线程在调用它,而当第一个线程进来了,发现没有对象就会创建对象赋值给变量instance,但是当它刚创建好对象,还没有赋值的时候,另一个线程也进来了,发现变量是空的,那它也会去创建对象,这时候内存中就出现了2个对象,虽然用到的永远是后创建的对象,但是也掩盖不了有2个对象的事实,浪费内存。
// if(instance == null){
// instance = new Student();
// return instance;
// }
// return instance;
// }
// private static Student instance = null;//如果这里不写static呢?可以吗?我认为可以吧,但是有可能是错的;
// --------------------- 单例设计模式----------------------
// --------------------- 优雅的实现线程范围内的多个变量共享----------------------
// --------------------自己的想法begin,这样的想法不好,外部用起来不方便,外部还要考虑空的情况-----------------------------
// private Student(){}
// public static Student getInstance1(){//这里为什么不用synchronized?
// st1 = (Student)threadLocal.get();//取到与当前线程相关的Student
// if(st1 == null){
// return null;//这样的想法不好,外部用起来不方便,外部还要考虑空的情况
// }
// return st1;
// }
// public void setInstance(Student st){
// threadLocal.set(st);
// }
// private static Student st1 = null;
// private static ThreadLocal<Object> threadLocal1 = new ThreadLocal<Object>();
// --------------------自己的想法end-----------------------------
// --------------------张孝祥老师的想法begin-----------------------------
private Student(){}
public static Student getInstance(){//这里为什么不用synchronized?因为ThreadLocal(这里得到的是与当先线程相关的类,2个线程得到的东西不一样),明白说不出来。怕自己以后忘了,提示自己可以去看次对象的set方法,看JDK的源代码,而不是帮助文档,看set方法的实现。
st = threadLocal.get();//线程1得线程1的,线程2的线程2的
if(st == null){
st = new Student();
threadLocal.set(st);
return threadLocal.get();//外部不用考虑,null还是不null的情况
}
return st;
}
private static Student st = null;
private static ThreadLocal<Student> threadLocal = new ThreadLocal<Student>();
// --------------------张孝祥老师的想法end-----------------------------
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// --------------------- 优雅的实现线程范围内的多个变量共享----------------------
}
有了垃圾回收机制,可以不用考虑用过ThreadLocal后,其中的map集合中遗留的数据,它们会被垃圾回收机制回收。在c++中有些类要调用delete方法才会被销毁,如果程序员忘了调用,那这个类会永远存在,对象的其他部分也不会得到清理,就会发生内存泄露。JAVA好像也会发生内存泄露,好像是hashcode和map使用产生的吧,以前看到过,具体细节不记得了。
当线程在临死前,发送通知,要接受这个通知,要做出来很复杂,不探讨
com.sun.包下的文件主要是提供给要和JVM紧密打交道程序用的,普通程序一般用java.包里面的类。
多个线程访问共享对象和共享数据的方式
★如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如:卖票系统就可以这么做。
卖票系统的示例代码:
package cn.itcast.heima2;
public class InterviewQuestions {
/**
* @param args
*/
public static void main(String[] args) {
// 模拟多个窗口卖100张票
// TODO Auto-generated method stub
Runnable rb = new Runnable() {
A a = new A();
@Override
public void run() {
// TODO Auto-generated method stub
a.setJ(100);
a.decrement();
}
};
new Thread(rb).start();
new Thread(rb).start();
}
}
class A{
private int j = 0;
public int getJ() {
return j;
}
public void setJ(int j) {
this.j = j;
}
public synchronized void increment(){
j++;
}
public synchronized void decrement(){
j--;
}
}
★如果每个线程执行的代码不同,这时候需要用不同的Runnable对象来实现。
方式一:
package cn.itcast.heima2;
public class InterviewQuestions {
/**
* @param args
*/
public static void main(String[] args) {
final A a = new A();
// TODO Auto-generated method stub
//负责减的线程
Runnable rbDecrement = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
a.decrement();
}
};
//负责加的线程
Runnable rbIncrement = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
a.increment();
}
};
new Thread(rbDecrement).start();
new Thread(rbIncrement).start();
}
}
class A{
private int j = 0;
public int getJ() {
return j;
}
public void setJ(int j) {
this.j = j;
}
public synchronized void increment(){
j++;
}
public synchronized void decrement(){
j--;
}
}
方式二、还可以用方法传参的方式实现
package cn.itcast.heima2;
public class InterviewQuestions {
/**
* @param args
*/
public static void main(String[] args) {
A a = new A();
MyRunnable1 b = new MyRunnable1(a);
MyRunnable2 c = new MyRunnable2(a);
new Thread(b).start();//这2个线程都是调用的同一个对象a
new Thread(c).start();//这2个线程都是调用的同一个对象a
while (true) {
System.out.println(a.getJ());
}
}
}
class A{
private int j = 0;
public int getJ() {
return j;
}
public void setJ(int j) {
this.j = j;
}
public synchronized void increment(){
j++;
}
public synchronized void decrement(){
j--;
}
}
class MyRunnable1 implements Runnable{
private A a ;
public MyRunnable1(A a){
this.a = a;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
a.decrement();
}
}
}
class MyRunnable2 implements Runnable{
private A a ;
public MyRunnable2(A a){
this.a = a;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
a.increment();
}
}
}
问题:一个外部类里面,有2个内部类,这2个内部类如何共享数据?
2个内部类用一个外部类的成员变量,就行了。
思考题:
题一、
/**
* 思考题
*/
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
System.out.println("Runnable:"+Thread.currentThread().getName());
}
}
}){
public void run() {
while (true) {
System.out.println("Thread:"+Thread.currentThread().getName());
}
};
}.start();
}
上面代码运行输出什么?结构大致是这样new Threan(Runnable的代码){Thread的代码}
答:输出
Thread:Thread-0
Thread:Thread-0
Thread:Thread-0
Thread:Thread-0
因为Thread是一个对象,当他运行的时候,它会先找自己的run方法,如果没内容,它才会去找父类的run方法,也就是Runnable中的代码,而上面它自己的run方法中有代码,所以输出Thread的代码。把上面Thread它自己的run方法删除就会运行Runnable的方法。
题二、多线程会提高程序的运行效率吗?为什么会有多线程下载?
不会,反而会影响运行效率。因为CPU在多个线程之间切换的时候会消耗一定的时间,比单独在一个线程上运行还慢一些。多线程下载,其实你的计算机没快,只是抢占了服务器的资源,好比说你一个人去服务器下载东西,服务器只给你分配20M的流量,但是你4个人去呢?
题三、为什么在静态方法中不能new一个内部类的实例对象?
内部类的一个重要特点就是访问外部类的成员变量,而成员变量是在类身上的,只有对象创建完了才为成员变量分配空间,而内部类能访问说明外部类存在,但是静态方法,可以先与外部内存在,所以相互矛盾。原理相同------>内部类中不能有静态方法。
因为,静态方法是先与外部类被创建的,而内部类是只有外部类创建了(有了外部类,才有内部类),用先有的静态方法创建不存在的类,那是不行的。
练习题
问题一、让下面的a方法和b方法同步
package cn.itcast.heima2;
public class Outputer {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
final Outputer a = new Outputer();
new Thread(){
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a.b("zhangxiaoxiang");
}
};
}.start();
new Thread(){
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a("lihuoming");
}
};
}.start();
}
public void b(String a){
synchronized (this)
{
for (Character b : a.toCharArray()) {
System.out.print(b);
}
System.out.println();
}
}
public synchronized static void a(String a){
for (Character b : a.toCharArray()) {
System.out.print(b);
}
System.out.println();
}
}
答案:
package cn.itcast.heima2;
public class Outputer {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
final Outputer a = new Outputer();
new Thread(){
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a.b("zhangxiaoxiang");
}
};
}.start();
new Thread(){
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a("lihuoming");
}
};
}.start();
}
public void b(String a){
synchronized (Outputer.class)//这里改成this.getClass()也可以
{
for (Character b : a.toCharArray()) {
System.out.print(b);
}
System.out.println();
}
}
public synchronized static void a(String a){
for (Character b : a.toCharArray()) {
System.out.print(b);
}
System.out.println();
}
}
他们用了相同的字节码,也可以用与Lock接口相关的实现类来完成
JDK1.5以后的线程并发库
了解java.util.concurrent(并发).atomic(原子,核)包;
AtomicInteger(解决多线程访问整数的问题):当多个线程访问同一个对象的同一成员变量整数的时候用
AtomicIntegerFieldUpdater,操作类里面的Integer字段
|
|
|
线程池(Excutors)
线程池的概念与Excutors类的应用
★创建固定大小的线程池
Executors.newFixedThreadPool(3);//有10个线程任务,但是只有3个线程运行。
★创建缓存线程池
Executors.newCachedThreadPool();//当有3任务的时候先有3个线程运行,突然又来了6个任务,就会有9个线程运行,当8个线程任务完了的时候,过一会它们收回这8个线程,
★创建单一线程池(如何实现线程死后,重新启动?),用Executor.newSingleThreadExcutor()(线程死了,重新找个替补它)
线程池的代码示例一:
package cn.itcast.heima2;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService threadPool = Executors.newFixedThreadPool(3);
for(int i = 1; i <= 10 ; i++){
final int task = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1;i <3 ;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"is looping of " + i + " for task " + task);
}
}
});
}
}
}
运行结果,只看到有三个线程在分别执行任务,10次任务用了4轮。并且任务完了后,线程没死,程序没有结束。
关闭线程池
shutdown与shutdownNow的比较
shutdown:一般线程池的所有任务完成后,线程是不会死的,程序也不会结束。但是你用了shutdown方法后,线程池的所有任务完成后,线程会死,程序结束。
shutdownNow:调用这个方法后,线程会在,完成第一轮任务后死亡。 比如:池子里有10个任务,但是只有3个线程在完成这十个任务。调用这方法会发生,线程池只做了3次任务,然后程序结束。
用线程池启动定时器(Executors.newScheduledThreadPool(3).scheduleWithFixedDelay)
Scheduled:
adj. 规定价格的;预定的;排定的;严格按时间表生活的
vt. 将…列入计划(或时间)表
示例代码:
//线程池中有3个线程创建,(固定执行线程)
Executors.newScheduledThreadPool(3).scheduleWithFixedDelay(
new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("bombing");
}
},
1, //等待多少秒后第一次执行
2,//每隔2秒(由于下面给的是秒), 执行一次
TimeUnit.SECONDS);//设置单位
Callable(可调用)和Future(翻译:未来,我理解为返回)
作用:
启动一个线程,线程可以返回结果,我们可以接受一个线程执行完的结果
一个线程调用(submit)Callable接口,然后返回一个Future对象.
示例代码:
package cn.itcast.heima2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableAndFuture {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//不用得到返回结果的话最好用excute方法
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return "hello";
}
};
Future<String> future = threadPool.submit(callable);//线程调用callable
try {
System.out.println(future.get());//如果future,没有拿到结果的话会一直等
System.out.println(threadPool.submit(callable).get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行输出:
hello
hello
Completion (完成)Service
用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象。(可以用来做QQ种菜的游戏,那块地的蔬菜先熟,就收哪块地的蔬菜。)
示例代码:
package cn.itcast.heima2;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableAndFuture<V> {
/**
* @param args
*/
public static void main(String[] args) {
ExecutorService threadPool2 = Executors.newFixedThreadPool(10);
//如果一个接口不知道new 什么的时候,就看帮助文档new 他的实现类
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool2);
for(int i = 1;i<10;i++){
final int seq = i;
completionService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
Thread.sleep(new Random().nextInt(5000));
return seq;
}
});
}
for (int i = 0 ; i < 10; i ++) {
try {
System.out.println(completionService.take().get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Lock
Lock比传统线程模型中的synchronized方式更加的面向对象,与生活中的锁类似,锁本身也是一个对象。两个线程执行的代码片段要实现同步互斥的效果,他们必须用同一个Lock对象。锁是上在被线程操作的资源类的内部方法中而不是线程代码中。
示例代码:
package cn.itcast.heima2;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Locked {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
final Locked a = new Locked();
final Lock lock = new ReentrantLock();
new Thread(){
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a.b("zhangxiaoxiang",lock);
}
};
}.start();
new Thread(){
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a("lihuoming",lock);
}
};
}.start();
}
//用Lock实现
public void b(String a,Lock lock){
//synchronized (Outputer.class)//这里改成this.getClass()也可以
// {
lock.lock();
for (Character b : a.toCharArray()) {
System.out.print(b);
}
System.out.println();
lock.unlock();
// }
}
//用Lock实现
public static void a(String a,Lock lock){
lock.lock();
try {
for (Character b : a.toCharArray()) {
System.out.print(b);
}
System.out.println();
} catch (Exception e) {
e.printStackTrace();
}finally{//这样的写法思维更严谨,这比b方法中的写法还要好
lock.unlock();
}
}
// public synchronized static void a(String a){
// for (Character b : a.toCharArray()) {
// System.out.print(b);
// }
// System.out.println();
// }
}
读写锁
读写锁分为读锁和写锁。多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。这是由JVM自己控制的,只要上好相应的锁即可。如果代码只读数据,可以很多人同时读,但不能同时写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁。
示例代码:
package cn.itcast.heima2;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLock {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
final Queue3 queue3 = new Queue3();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
queue3.get();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
queue3.put((long)(Math.random()*1000));
}
}
}).start();
}
}
}
class Queue3{
private Object data = null;
java.util.concurrent.locks.ReadWriteLock rwl = new ReentrantReadWriteLock();
public void get(){
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"be ready to read data!");
Thread.sleep((long)(Math.random()*1000));
System.out.println(Thread.currentThread().getName()+"have ready to read data:" + data);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
rwl.readLock().unlock();
}
}
public void put(Object data){
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"be ready to write data!");
Thread.sleep((long)(Math.random()*1000));
this.data = data;
System.out.println(Thread.currentThread().getName()+"have ready to write data:" + data);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
}
}
★问题:User user = session.load(id,User.class);和User user = session.get(id,User.class);的区别是什么?
session.load(id,User.class)先在一级缓存中找,没有的话再在二级缓存中找,还没有的话就去数据库中找,再没有就报错。它返回的是实体User对象的代理
session.get(id,User.class)先在一级缓存中找,没有的话就直接去数据库中找,还没有的话返回null。
一个面试题:写一个缓存内!
示例代码:
package cn.itcast.heima2;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {
private Map<String,Object> data = new HashMap<String,Object>();
private Object realyData = new Object();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
/**
* 简单的缓冲系统(多个线程来差数据,可读,可写)
* @param key
* @return
*/
public Object getData1(String key){
realyData = data.get(key);
if (realyData == null) {
realyData ="去数据库取东西";
}
return realyData;
}
/**
* 思维小严谨的缓冲系统(多个线程来差数据,可读,可写)
* @param key
* @return
*/
public synchronized Object getData2(String key){
realyData = data.get(key);
if (realyData == null) {
realyData ="去数据库取东西";
}
return realyData;
}
/**
* 思想最好的缓冲系统(多个线程来差数据,可读,可写)用读写锁
* @param key
* @return
*/
public Object getData3(String key){
rwl.readLock().lock();
try{
realyData = data.get(key); //可以多个人同时读一个数据。
if (realyData == null) { //只能一个人写数据,写的过程中,其他人不能读,也不能写
rwl.readLock().unlock();
rwl.writeLock().lock();//当有3个线程发现缓冲中没有数据的时候,3个线程都到这里,然后其中一个线程获得写锁
//进去开始写数据,而另2个线程在这等待,当写数据的那个线程写完数据,他们其中一个线
//获得写锁,然后写数据
try {
if(realyData == null){ //如果这里不做判断的话,当第一个线程写好数据后,由于这里没有判断,那另外2个线程还
//会重复第一个线程的操作,连接数据库浪费系统资源
realyData ="去数据库取东西";
}
rwl.readLock().lock();
} catch (Exception e) {
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
}
}catch (Exception e) {
e.printStackTrace();
}finally{
rwl.readLock().unlock();
}
return realyData;
}
}
JDK读写锁接口的实例对象ReentrantReadWriteLock类中的一个示例:
示例用法。下面的代码展示了如何利用重入来执行升级缓存后的锁降级(为简单起见,省略了异常处理):
class CachedData {
Object data;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
// Recheck state because another thread might have acquired
// write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock(); //这里很深,更新锁,降级
rwl.writeLock().unlock(); // Unlock write, still hold read
}
use(data);
rwl.readLock().unlock();
}
}
Lock(锁)&Condition(条件)实现线程同步通信
Condition
将 Object
监视器方法(wait
、notify
和notifyAll
)分解成截然不同的对象,以便通过将这些对象与任意Lock
实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock
替代了synchronized
方法和语句的使用,Condition
替代了 Object 监视器方法的使用。
Condition 实例实质上被绑定到一个锁上。要为特定Lock
实例获得Condition
实例,请使用其newCondition()
方法。
示例代码:
package cn.itcast.heima2;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 子线程循环10次,又一子线程循环20次,接着主线程循环30次,接着又回到子线程循环10次,另一子线程
* 循环20次,接着再回到主线程又循环30次,如此循环50次,请写出程序。
* @author Terry
* @date 2014-6-3
*
*/
public class ConditionTest {
/**
* @param args
*/
public static void main(String[] args){
final Cycle cycle = new Cycle();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
for (int i = 0; i < 50; i++) {
cycle.cycle10();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
for (int i = 0; i < 50; i++) {
cycle.cycle20();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
for (int i = 0; i < 50; i++) {
cycle.cycle30();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
for (int i = 0; i < 50; i++) {
//a.start();//这里是线程启动了50次,本来就是启动的,再启动当然报错
}
}
static class Cycle{
int isMe = 1;
/**
* 用Condition实现的(Condition要和Lock绑定)
* @throws Exception
*/
Lock lock = new ReentrantLock();
Condition one = lock.newCondition();
Condition two = lock.newCondition();
Condition three = lock.newCondition();
Object objOne = new Object();
public void cycle20() throws Exception{
lock.lock();
while(isMe !=2){
two.await();//把这里换成用synchronized,不行
//lock.wait();//这里应该用Condition condition = lock.newCondition();condition.await();
}
for (int i = 0; i < 20; i++) {
System.out.print(i+"\t");
}
isMe = 3;
three.signal();
System.out.println("主线程循环20次完成!");
lock.unlock();
//lock1.unlock();//一定要关锁要不可能出现死循环,unlock()不能和wait()方法一起使用,因为wait()会释放锁,锁被释放
//了一次,在被释放一次的话,拿什么来释放呢?
}
public void cycle10() throws Exception{
lock.lock();
while(isMe != 1){
//lock.wait();//这里应该用Condition condition = lock.newCondition();condition.await();
one.await();
}
for (int i = 0; i < 10; i++) {
System.out.print(i+"\t");
}
System.out.println("子线程循环10次完成!");
isMe = 2;
//lock.notify();
two.signal();
lock.unlock();
}
public void cycle30() throws Exception{
lock.lock();
while(isMe != 3){
//lock.wait();//这里应该用Condition condition = lock.newCondition();condition.await();
three.await();
}
for (int i = 0; i < 30; i++) {
System.out.print(i+"\t");
}
System.out.println("子线程循环30次完成!");
isMe = 1;
//lock.notify();
one.signal();
lock.unlock();
}
}
}
JDK1.5提供的工具
Semaphore(信号)
Semaphore实现的功能类似于,厕所有5个坑,假如有10个人要上厕所,同时只能有5个人能够占用,当5个人中的任何一个人离开后,其中在等待的另外5个人中的一个人占用。详细用的时候看帮助文档。面试题第二题有用到。
Cycllc(循环)Barrier(障碍物)
表示大家彼此等待,大家集合好后才开始出发,然后分散活动后又在指定地点集合碰面。(就像公司员工集合去郊游一样,先指定集合地点,等人都齐了,然后出发)。详细用的时候看帮助文档。
CountDownLatch
犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数器达到0时,则所有等待者或单个等待者开始执行。详细用的时候看帮助文档。
Exchanger
用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个人先拿出数据人后再那等待者,等第二个人也拿出数据的时候,他们两就交换数据。(买卖毒粉的,一个用钱交换毒粉,一个用毒粉交换钱)详细用的时候看帮助文档。
SynchronousQueue<E>
一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行peek,因为仅在试图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)插入元素;也不能迭代队列,因为其中没有元素可用于迭代。队列的头 是尝试添加到队列中的首个已排队插入线程的元素;如果没有这样的已排队线程,则没有可用于移除的元素并且poll() 将会返回 null。对于其他Collection 方法(例如contains),SynchronousQueue 作为一个空 collection。此队列不允许null 元素。
可阻塞的队列
阻塞队列的作用:队列是先进先出的
当试图向队列中添加元素而队列已满,或是从队列中移出元素而队列是空的时候,阻塞队列导致线程阻塞,在协调多个线程之间的合作时,阻塞队列是一个有用的工具。
作为一个示例,假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
(ArrayBlockingQueue 类提供了这项功能,因此没有理由去实现这个示例类。)
示例代码:
package cn.itcast.heima2;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 子线程循环10次,又一子线程循环20次,接着主线程循环30次,接着又回到子线程循环10次,另一子线程
* 循环20次,接着再回到主线程又循环30次,如此循环50次,请写出程序。要求用阻塞队列的方式实现
* @author Terry
* @date 2014-6-3
*
*/
public class ConditionTest {
/**
* @param args
*/
public static void main(String[] args){
final Cycle cycle = new Cycle();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
for (int i = 0; i < 50; i++) {
cycle.cycle10();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
for (int i = 0; i < 50; i++) {
cycle.cycle20();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
for (int i = 0; i < 50; i++) {
cycle.cycle30();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
for (int i = 0; i < 50; i++) {
//a.start();//这里是线程启动了50次,本来就是启动的,再启动当然报错
}
}
static class Cycle{
/**
* 用阻塞队列的方式实现(代码简洁多了,用的东西也少了)
*/
BlockingQueue<Integer> blockingQueue1 = new ArrayBlockingQueue<Integer>(1);
BlockingQueue<Integer> blockingQueue2 = new ArrayBlockingQueue<Integer>(1);
BlockingQueue<Integer> blockingQueue3 = new ArrayBlockingQueue<Integer>(1);
//----------------------------比较--------------------------------
{//这是匿名构造函数的写法,它将在所有构造函数前被执行,new 几个Cycle对象就执行几次
try {
blockingQueue2.put(1);
//blockingQueue2.put(2);//这里发生线程死锁,运行没有反应
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
{
try {
blockingQueue3.put(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//----------------------------比较--------------------------------
public void cycle20() throws Exception{
blockingQueue2.put(2);
for (int i = 0; i < 20; i++) {
System.out.print(i+"\t");
}
System.out.println("主线程循环20次完成!");
blockingQueue3.take();
}
public void cycle10() throws Exception{
blockingQueue1.put(23);
for (int i = 0; i < 10; i++) {
System.out.print(i+"\t");
}
System.out.println("子线程循环10次完成!");
blockingQueue2.take();
}
public void cycle30() throws Exception{
blockingQueue3.put(1777);
for (int i = 0; i < 30; i++) {
System.out.print(i+"\t");
}
System.out.println("子线程循环30次完成!");
blockingQueue1.take();
}
// int isMe = 1;
// /**
// * 用Condition实现的(Condition要和Lock绑定)
// * @throws Exception
// */
// Lock lock = new ReentrantLock();
// Condition one = lock.newCondition();
// Condition two = lock.newCondition();
// Condition three = lock.newCondition();
// Object objOne = new Object();
//
// public void cycle20() throws Exception{
// lock.lock();
// while(isMe !=2){
// two.await();//把这里换成用synchronized,不行
// //lock.wait();//这里应该用Condition condition = lock.newCondition();condition.await();
// }
// for (int i = 0; i < 20; i++) {
// System.out.print(i+"\t");
// }
// isMe = 3;
// three.signal();
// System.out.println("主线程循环20次完成!");
// lock.unlock();
// //lock1.unlock();//一定要关锁要不可能出现死循环,unlock()不能和wait()方法一起使用,因为wait()会释放锁,锁被释放
// //了一次,在被释放一次的话,拿什么来释放呢?
//
// }
//
// public void cycle10() throws Exception{
// lock.lock();
// while(isMe != 1){
// //lock.wait();//这里应该用Condition condition = lock.newCondition();condition.await();
// one.await();
// }
// for (int i = 0; i < 10; i++) {
// System.out.print(i+"\t");
// }
// System.out.println("子线程循环10次完成!");
// isMe = 2;
// //lock.notify();
// two.signal();
// lock.unlock();
// }
//
//
// public void cycle30() throws Exception{
// lock.lock();
// while(isMe != 3){
// //lock.wait();//这里应该用Condition condition = lock.newCondition();condition.await();
// three.await();
// }
// for (int i = 0; i < 30; i++) {
// System.out.print(i+"\t");
// }
// System.out.println("子线程循环30次完成!");
// isMe = 1;
// //lock.notify();
// one.signal();
// lock.unlock();
// }
}
}
JDK1.5中提供的同步集合在(java.util.concurrent包中)
JDK1.5以前的集合有什么不好的地方
1、线程不安全。
2、不能在用迭代器的时候,删除集合里面的东西。
HashMap和HashSet的区别于联系
看源码,HashSet不能装重复的集合,HashSet里面用的是HashMap
23种常用的设计模式之----------单例设计模式(Singleton Pattern)
什么是单例设计模式?
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
单例设计模式(Singleton Pattern),顾名思义,是指确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。需要注意的是,在系统中只有正有“单一实例”的需求时才可以使用。
单例设计模式的三个要点
(1)、某个类只能有一个实例;
(2)、该类必须自行创建这个实例;;(因为构造方法私有化,外面无法创建该类的实例)
(3)、该类必须自行向整个系统提供这个实例;(因为构造方法私有化,外面无法创建该类的实例,就无法调用该类的方法)(设计要诀之:谁拥有变量,谁就对外提供操作这些变量的方法)
为什么要用单例设计模式
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
用单例设计模式的优缺点(看得不太明白)
优点
一、实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
二、灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。(饥寒写法、饱寒写法)
[4]
缺点
一、开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
二、可能的开发混淆
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
三、对象生存期
不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。
类图
代码实现:
★Singleton类
package com.demo.singleton;
public class Singleton {
private Singleton(){}
// ----------------------------------线程不安全的------------------------------------------
/**
* 饱寒的写法
* ★饱寒模式的写法★对象一开始就存在,不管你调不调用,实例对象都在,永远不调用,对象也存在
* @return
*/
private static Singleton eatColdSingleton = new Singleton();
/**
* 饱寒
* @return
*/
public static Singleton getEatColdInstance(){
return eatColdSingleton;
}
/**
* 饥寒的写法
* ★饥寒模式的写法★只有第一次用的时候被创建,其他时候就直接用创建好的,如果一直不调用,就一直不创建。
* @return
*/
private static Singleton jihanSingleton = null;
/**
* 饥寒线程不安全
* @return
*/
public static Singleton getJihanInstance(){
if(jihanSingleton == null){
jihanSingleton = new Singleton();
}
return jihanSingleton;
}
// ------------------------------------线程安全的------------------------------------------
/**
* 饱寒的写法
* @return
*/
private static Singleton eatColdSingletonSecurity = new Singleton();
/**
* 饱寒
* @return
*/
public static Singleton getEatColdInstanceSecurity(){//都可以取,又不创建所以不加synchronized
return eatColdSingleton;
}
/**
* 饥寒的写法
* @return
*/
private static Singleton jihanSingletonSecurity = null;
/**
* 饥寒线程安全
* @return
*/
public static synchronized Singleton getJihanInstanceSecurity(){
if(jihanSingletonSecurity == null){
jihanSingletonSecurity = new Singleton();
return jihanSingletonSecurity;
}
return jihanSingletonSecurity;
}
}
★Client类
package com.demo.singleton;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Client {
public static void main(String[] args) {
// Singleton test1 = new Singleton();//编译报错
Singleton test2 = Singleton.getJihanInstanceSecurity();
Singleton test3 = Singleton.getJihanInstanceSecurity();
System.out.println(test2.equals(test3));
}
}
运行输出:
true
面试题:
第一道题:
现有的程序代码模拟生产了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用parselong()方法来分头打印这16个日志对象,程序只要4秒即可打完这些日志对象。原始代码如下:
package cn.itcast.heima2;
public class InterfaceQuession1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("begin:" + (System.currentTimeMillis()/1000));
for(int i = 0;i<16;i++){//这行代码不能改动
final String log = ""+(i+1);//这行代码不能改动
{
InterfaceQuession1.parselog(log);
}
}
}
// parselog方法内部的代码不能改动
public static void parselog(String log){
System.out.println(log+":"+(System.currentTimeMillis()/1000));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
答案一:自己写的,真好(用线程池的方式做的)
package cn.itcast.heima2;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 现有的程序代码模拟生产了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用
* parselong()方法来分头打印这16个日志对象,程序只要4秒即可打完这些日志对象。原始代码如下:
*
* @author Terry
* @date 2014-6-6
*
*/
public class InterfaceQuession1 {
static Lock lock = new ReentrantLock();
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("begin:" + (System.currentTimeMillis()/1000));
ExecutorService es= Executors.newFixedThreadPool(4);
try {
for(int i = 0;i<16;i++){//这行代码不能改动
final String log = ""+(i+1);//这行代码不能改动
{
//Executors.newFixedThreadPool(4).execute(new Runnable()//错误的做法,这样会产生16*4个线程
es.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
InterfaceQuession1.parselog(log);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
} finally{
es.shutdown();
}
}
// parselog方法内部的代码不能改动
public static void parselog(String log){
//lock.lock();
System.out.println(Thread.currentThread().getName()+"::"+log+":"+(System.currentTimeMillis()/1000));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//lock.unlock();
}
}
答案二:用队列的方式做的(也是自己做的,看了点视频提示)
package cn.itcast.heima2;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 现有的程序代码模拟生产了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用
* parselong()方法来分头打印这16个日志对象,程序只要4秒即可打完这些日志对象。原始代码如下:
*
* @author Terry
* @date 2014-6-6
*
*/
public class InterfaceQuession1 {
static Lock lock = new ReentrantLock();
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
final BlockingQueue<String> bq = new ArrayBlockingQueue<String>(4);
for (int i = 0; i < 4; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
for (int j = 0; j < 4; j++) {
InterfaceQuession1.parselog(bq.take());
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
System.out.println("begin:" + (System.currentTimeMillis()/1000));
ExecutorService es= Executors.newFixedThreadPool(4);
try {
for(int i = 0;i<16;i++){//这行代码不能改动
final String log = ""+(i+1);//这行代码不能改动
{
try {
bq.put(log);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} finally{
es.shutdown();
}
}
// parselog方法内部的代码不能改动
public static void parselog(String log){
//lock.lock();
System.out.println(Thread.currentThread().getName()+"::"+log+":"+(System.currentTimeMillis()/1000));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//lock.unlock();
}
}
第二道题:原始代码
package cn.itcast.heima2;
/**
* 现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法处理,就
* 好像生产者在不断地产生数据,消费者在不断地消费数据。请将程序改造成有10个线程来消费生产
* 的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要消费
* 一秒才能处理完,程序应该保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完
* 后,下一个消费者才能消费数据,下一个消费者是谁都可以,但是要保证这些消费者线程拿到的数据
* 是有顺序的。原始代码如下:
*
* @author Terry
* @date 2014-6-6
*
*/
public class InterfaceQuession3 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("begin:"+(System.currentTimeMillis()/1000));
for (int i = 0; i < 10; i++) {//这行不能改动
String input = i+"";//这行不能改动
String output = TestDo.doSome(input);
System.out.println(Thread.currentThread().getName()+":"+output);
}
}
}
//不能改动此TestDo类
class TestDo{
public static String doSome(String input){
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
String output = input + "" + (System.currentTimeMillis()/1000);
return output;
}
}
答案一:我想这个答案会恶心死出题的人的,他说的不能动的地方我都没动哈,用了装饰者设计模式(应该是这个吧)
package cn.itcast.heima2;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法处理,就
* 好像生产者在不断地产生数据,消费者在不断地消费数据。请将程序改造成有10个线程来消费生产
* 的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要消费一秒
* 才能处理完,程序应该保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完
* 后,下一个消费者才能消费数据,下一个消费者是谁都可以,但是要保证这些消费者线程拿到的数据
* 是有顺序的。原始代码如下:用SynchronousQueue<E>类应该轻易就解决了,看视频后才知道的
*
* @author Terry
* @date 2014-6-6
*
*/
public class InterfaceQuession3 {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
final BlockingQueue<String> bq = new ArrayBlockingQueue<String>(1);
// TODO Auto-generated method stub
System.out.println("begin:"+(System.currentTimeMillis()/1000));
for (int i = 0; i < 10; i++) {//这行不能改动
String input = i+"";//这行不能改动
final String input1 = input;
new Thread(new Runnable() {
@Override
public void run() {
String output = TestDo1.doSome(input1);
System.out.println(Thread.currentThread().getName()+":"+output);
}
}).start();
}
}
}
//不能改动此TestDo类
class TestDo{
public static String doSome(String input){
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
String output = input + "" + (System.currentTimeMillis()/1000);
return output;
}
}
class TestDo1{
TestDo td = new TestDo();
public synchronized static String doSome(String input){
return TestDo.doSome(input);
}
}
答案二:用阻塞队列加Semaphore的方式实现
package cn.itcast.heima2;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法处理,就
* 好像生产者在不断地产生数据,消费者在不断地消费数据。请将程序改造成有10个线程来消费生产
* 的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要消费
* 一秒才能处理完,程序应该保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完
* 后,下一个消费者才能消费数据,下一个消费者是谁都可以,但是要保证这些消费者线程拿到的数据
* 是有顺序的。原始代码如下:
*
* @author Terry
* @date 2014-6-6
*
*/
public class InterfaceQuession3 {
/**
* @param args
*/
public static void main(String[] args) {
final Semaphore semaphore = new Semaphore(1);
final Lock lock = new ReentrantLock();
final BlockingQueue<String> bq = new ArrayBlockingQueue<String>(1);
// TODO Auto-generated method stub
System.out.println("begin:"+(System.currentTimeMillis()/1000));
for (int i = 0; i < 10; i++) {//这里放在下面的for方法前,和for方法后是有区别的,放在下面的for循环后,要等下面的for循环完了才能执行这个for循环,而下面的for循环执行到地二次的时候会阻塞,线程就堵死了,这里就无法task();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
// semaphore.acquire();
lock.lock();
System.out.println(Thread.currentThread().getName()+":"+TestDo.doSome(bq.take()));
lock.unlock();
// semaphore.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();
}
for (int i = 0; i < 10; i++) {//这行不能改动
String input = i+"";//这行不能改动
// String output = TestDo.doSome(input);
// System.out.println(Thread.currentThread().getName()+":"+output);
try {
bq.put(input);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//不能改动此TestDo类
class TestDo{
public static String doSome(String input){
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
String output = input + "" + (System.currentTimeMillis()/1000);
return output;
}
}
第三道题:代码和需求如下
package cn.itcast.heima2;
import java.util.concurrent.ConcurrentHashMap;
/**
* 现有程序同时启动了4个线程去调用TestDo.doSome(key,value),由于TestDo.doSome(key,value)方法内的代码是先暂停一秒,然后再输
* 以秒为单位的当前时间,所以会打印出4个相同的时间值。如下所示
* 1:2:1402104933
* 3:3:1402104933
* 4:4:1402104933
* 1:1:1402104933
* 现在请修改代码,如果有几个线程调用TestDo.doSome(key,value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥
* 排队输出结果,即当有两个线程的key都是"1"时,它们中的一个要比另外其他线程晚一秒输出结果,要求如下所示
* 1:2:1402104933
* 3:3:1402104933
* 4:4:1402104933
* 1:1:1402104934
* @author Terry
* @date 2014-6-7
*
*/
//不能改动此InterfeceQuession3类
public class InterfeceQuession3 extends Thread{
private TestDo1 testDo1;
private String key;
private String value;
public InterfeceQuession3(String key,String key2,String value){
this.testDo1 = TestDo1.getInstance();
// 常量"1"和"1"是同一对象,下面这行代码就是要用"1"+""的方式产生新的对象,以实现内容没有改变,
// 仍然相等,但对象却不再是同一个的效果,(内容相等,地址不等)
this.key = key + key2;
this.value = value;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
InterfeceQuession3 a = new InterfeceQuession3("1","","1");
InterfeceQuession3 b = new InterfeceQuession3("3","","2");
InterfeceQuession3 c = new InterfeceQuession3("3","","3");
InterfeceQuession3 d = new InterfeceQuession3("4","","4");
System.out.println("begin:"+(System.currentTimeMillis()/1000));
a.start();
b.start();
c.start();
d.start();
}
public void run(){
testDo1.doSome(key, value);
}
}
class TestDo1{
private TestDo1(){}
private static TestDo1 _instance = new TestDo1();
public static TestDo1 getInstance(){
return _instance;
}
public void doSome(Object key,String value){
//以下大括号的内容是需要局部同步的代码,不能改动
{
try {
Thread.sleep(1000);
System.out.println(key + ":" +value +":"+(System.currentTimeMillis()/1000));
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
答:做这道题的时候,这道不能用synchronized(key)来实现,因为他们的内容虽然一样,但是key是2个不同的对象,然后一直没有头绪。经过思考发现想要达到互斥就要有相同的东西,然后去百度搜索了下:两个对象不一样,但是内容一样,如何使他们变得一样;发现了key.hashCode()这一个方法,然后在程序中试了一下
System.out.println(key.hashCode()+":"+key);
//以下大括号的内容是需要局部同步的代码,不能改动
{
...............
}
运行输出:
49:1
52:4
51:3
51:3
看了结果后就知道怎么做了
★答案一:
package cn.itcast.heima2;
import java.util.concurrent.ConcurrentHashMap;
/**
* 现有程序同时启动了4个线程去调用TestDo.doSome(key,value),由于TestDo.doSome(key,value)方法内的代码是先暂停一秒,然后再输
* 以秒为单位的当前时间,所以会打印出4个相同的时间值。如下所示
* 1:2:1402104933
* 3:3:1402104933
* 4:4:1402104933
* 1:1:1402104933
* 现在请修改代码,如果有几个线程调用TestDo.doSome(key,value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥
* 排队输出结果,即当有两个线程的key都是"1"时,它们中的一个要比另外其他线程晚一秒输出结果,要求如下所示
* 1:2:1402104933
* 3:3:1402104933
* 4:4:1402104933
* 1:1:1402104934
* @author Terry
* @date 2014-6-7
*
*/
//不能改动此InterfeceQuession3类
public class InterfeceQuession3 extends Thread{
private TestDo1 testDo1;
private String key;
private String value;
public InterfeceQuession3(String key,String key2,String value){
this.testDo1 = TestDo1.getInstance();
// 常量"1"和"1"是同一对象,下面这行代码就是要用"1"+""的方式产生新的对象,以实现内容没有改变,
// 仍然相等,但对象却不再是同一个的效果,(内容相等,地址不等)
this.key = key + key2;
this.value = value;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
InterfeceQuession3 a = new InterfeceQuession3("1","","1");
InterfeceQuession3 b = new InterfeceQuession3("3","","2");
InterfeceQuession3 c = new InterfeceQuession3("4","","3");
InterfeceQuession3 d = new InterfeceQuession3("1","","4");
System.out.println("begin:"+(System.currentTimeMillis()/1000));
a.start();
b.start();
c.start();
d.start();
}
public void run(){
testDo1.doSome(key, value);
}
}
class TestDo1{
private TestDo1(){}
private static TestDo1 _instance = new TestDo1();
public static TestDo1 getInstance(){
return _instance;
}
public void doSome(Object key,String value){
System.out.println(key.hashCode()+":"+key);
//以下大括号的内容是需要局部同步的代码,不能改动
synchronized((Integer)key.hashCode())
{
try {
Thread.sleep(1000);
System.out.println(key + ":" +value +":"+(System.currentTimeMillis()/1000));
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
★答案二:
package cn.itcast.heima2;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 现有程序同时启动了4个线程去调用TestDo.doSome(key,value),由于TestDo.doSome(key,value)方法内的代码是先暂停一秒,然后再输
* 以秒为单位的当前时间,所以会打印出4个相同的时间值。如下所示
* 1:2:1402104933
* 3:3:1402104933
* 4:4:1402104933
* 1:1:1402104933
* 现在请修改代码,如果有几个线程调用TestDo.doSome(key,value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥
* 排队输出结果,即当有两个线程的key都是"1"时,它们中的一个要比另外其他线程晚一秒输出结果,要求如下所示
* 1:2:1402104933
* 3:3:1402104933
* 4:4:1402104933
* 1:1:1402104934
* @author Terry
* @date 2014-6-7
*
*/
//不能改动此InterfeceQuession3类
public class InterfeceQuession3 extends Thread{
private TestDo1 testDo1;
private String key;
private String value;
public InterfeceQuession3(String key,String key2,String value){
this.testDo1 = TestDo1.getInstance();
// 常量"1"和"1"是同一对象,下面这行代码就是要用"1"+""的方式产生新的对象,以实现内容没有改变,
// 仍然相等,但对象却不再是同一个的效果,(内容相等,地址不等)
this.key = key + key2;
this.value = value;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
InterfeceQuession3 a = new InterfeceQuession3("1","","1");
InterfeceQuession3 b = new InterfeceQuession3("3","","2");
InterfeceQuession3 c = new InterfeceQuession3("4","","3");
InterfeceQuession3 d = new InterfeceQuession3("1","","4");
System.out.println("begin:"+(System.currentTimeMillis()/1000));
a.start();
b.start();
c.start();
d.start();
}
public void run(){
testDo1.doSome(key, value);
}
}
class TestDo1{
private TestDo1(){}
private static TestDo1 _instance = new TestDo1();
public static TestDo1 getInstance(){
return _instance;
}
private CopyOnWriteArrayList<Object> cowal = new CopyOnWriteArrayList<Object>();//这里用ArrayList的话,有时候会出错,具体原因///在:张孝祥老师的------Java多线程与并发库高级应用\19_传智播客_张孝祥_java5同步集合类的应用的21:51秒中看到,看得不是很明白。迭代中不能操作集合。这问//题不容易发现,可以让迭代的时候sleep,问题就容易出现了
public void doSome(Object key,String value){
Object o = key;
//以下大括号的内容是需要局部同步的代码,不能改动
if(!cowal.contains(key)){//如果集合中,没有指定的元素key
cowal.add(o);
}else{//如果集合中,已经有指定的元素了,而且已经用来做互斥了,而这2个对象是不同的,但是内容是相同的,既然已经用前面的那个来做
//互斥对象了,那就找出前面的那个来继续互斥,以达到效果
Iterator iter = cowal.iterator();
while (iter.hasNext()) {
Object object = (Object) iter.next();
if(object.equals(key)){
o = object;//用原来的对象做互斥
}
}
}
synchronized(o){
try {
Thread.sleep(1000);
System.out.println(key + ":" +value +":"+(System.currentTimeMillis()/1000));
} catch (Exception e) {
// TODO: handle exception
}
}
cowal.add(key);
}
}
面试题题三的相关拓展
★下面的代码为什么输出flase和true
public static void main(String[] args) {
String temp1= "1";
String temp2 = "1";
String a1 = temp1 +"";
String a2 = temp2 +"";
String a3 = "1" +"";
String a4 = "1" +"";
System.out.println(a1==a2);
System.out.println(a3==a4);
}
答:这是编译器的原因,因为a1和a2中的temp1和temp2是变量,而编译器不知道它们到底是什么,所有就把他们弄成了新的对象赋值给a1,a2。而a3和a4中的"1"是常量,
String a3 = "1" +"";这样的写法,编译器会认为后面的+""是多余的,它知道你要的是"1",它会自动给你优化成String a3 = "1" ;所以 String a3 = "1" +"";String a4 = "1" +"";等同于
String a3 = "1";String a4 = "1";
hashCode()
★hashCode()与equals()方法相关联
有许多人学了很长时间的Java,但一直不明白hashCode方法的作用,
我来解释一下吧。首先,想要明白hashCode的作用,你必须要先知道Java中的集合。
总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。
你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。
那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。
也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。
哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。
初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。
这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。
如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,
就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
所以,Java对于eqauls方法和hashCode方法是这样规定的:
1、如果两个对象相同,那么它们的hashCode值一定要相同;2、如果两个对象的hashCode相同,它们并不一定相同 上面说的对象相同指的是用eqauls方法比较。
你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。
(还有更深的现在先不管了)
为什么要使用迭代器:
迭代模式是访问集合类的通用方法,只要集合类实现了Iterator接口,就可以用迭代的方式来访问集合类内部的数据,Iterator访问方式把对不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果。
例如,如果没有使用Iterator,遍历一个数组的方法是使用索引:
Ruby代码
- for(int i=0; i<array.length; i++) { ... get(i) ... }
这种方法的缺点就是事先必须知道集合的数据结构,而且当我换了一种集合的话代码不可重用,要修改,比如我用set,就不能通过索引来遍历了。访问代码和集合是紧耦合,无法将访问逻辑从集合类和客户端代码中剥离出来,每一种集合类对应一种访问方式,代码不可重用。
为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合。
每一种集合类返回的Iterator具体类型可能不同,Array可能返回ArrayIterator,Set可能返回SetIterator,Tree 可能返回TreeIterator,但是它们都实现了Iterator接口,因此,客户端不关心到底是哪种Iterator,它只需要获得这个 Iterator接口即可,这就是面向对象的威力。
这就是针对抽象编程的原则:对具体类的依赖性最小。
多线程小记
1、每个对象都有自己的内部锁。(java核心技术一书中看到的)
2、在多线程中,不能用断点调试程序,只有用System.out.println();一行一行的慢慢看来排查问题所在。为什么这样我是不明白的。(写代码发现的)
3、把相关联的方法,放在同一个类身上(高类聚),原则:要用到共同数据的若干方法应该放在同一类身上好维护。
4、当输出的内容很多,但是有想看全的话可以这样,run as--->第二项--->Common--->File:
5、多线程用多线程的集合,一般的用一般的集合
6、以后尽量用JDK1,5以后的多线程,效率好点,性能也优化了