首先说一下进程和线程的区别:
进程:指正在运行的程序,当打开一个程序的时候,他会进入内存运行。它最大的特点是它需要内存。
什么是线程:线程是进程中的一个执行单元。
什么是多线程: 一个程序有多个执行路径。
我们在学习一门技术的时候首先要知道这么技术有什么作用,能干什么用,那么我们学习线程有什么用?学习多线程有什么用?我们就抱着这样的问题来讨论以下线程和多线程。
在我们未学线程的时候,我们都知道我们的程序是从main函数开始往下一条一条的按照顺序执行下去,但是现在我们有了线程之后就不再是那样了,我们现在程序可以开辟一条另外的执行路劲,让程序不再是“一条路走到黑”,下面来举个例:
public class TestThread {
public static void main(String[] args) {
NewThread nt = new NewThread();
Thread t = new Thread(nt);
t.start();
int i;
for(i = 0; i < 10; i++ ) {
System.out.println("main..."+i);
}
}
}
public class NewThread implements Runnable {
@Override
public void run() {
for(int j = 0; j < 10; j++) {
System.out.println("new Thread..."+j);
}
}
}
运行结果:
main...0
main...1
main...2
new Thread...0
new Thread...1
new Thread...2
new Thread...3
new Thread...4
new Thread...5
new Thread...6
new Thread...7
new Thread...8
new Thread...9
main...3
main...4
main...5
main...6
main...7
main...8
main...9
注意:每次运行的结果都不一样。因为这是根据CPU给他们分配的时间来决定的,每次都是不一样的。
在前面这个例子中我们可以看到如何去创建一个新的线程,新建一个类,去实现Runnable接口(也可以继承Thread类),然后重写里面的run方法。然后在主线程里面新建Thread类,把实现了Runnable接口的类作为构造器参数传给Thread,然后调用start方法就启动了一个新的线程。
每一个线程都是新开一个栈,这样才能达到多个线程的无序执行。
线程的几种状态:
new状态:新建状态
Runanble状态:运行状态
Blocking状态:阻塞,具有CUP执行资格,也就是说,CPU一有空闲那么就会退出阻塞状态进入运行状态
Waiting:等待状态,唤醒之后有可能进入阻塞状态或者是运行状态
Timed_waiting:休眠状态,也就是调用sleep,在指定的时间内可以醒来。
Terminated:死亡状态
多线程:
什么是多线程:
其实在我们程序中,每次去创建一个线程,调用执行这一系列的操作都需要消耗大量的时间,那么对我们程序的整个性能也会带来很大的损耗,所以我们就出现了多线程技术,多线程技术的原理其实很简单,比如说我们有一个鱼缸,然后把我们的每个线程当成鱼一样拿到鱼缸里面养着,当我们需要的时候就取出来用,当我们用完之后就又放回去,鱼缸其实它就是一个容器。
在我们java中这样的鱼缸已经帮我们制定好了,我们只需要拿来用就可以了。
在java.util.concurrent.Executors;下面有这样一个方法
static ExecutorService | newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程 |
可以看到它是static的,返回值是ExecutorService,在它里面有一个submit方法,把我们写好的线程(实现Runnable接口或者Callable接口的类)作为参数传给他,那么他就会启动。
Collable接口:
在我们使用线程池技术的时候,我们一般会用submit来提交一个线程任务,但是submit需要一个实现了Runnable(也可以是Callable)接口的实现类作为参数,但是我们都知道,实现Runnable接口的run方法没有返回值,没有抛出异常,这就为我们带来了很大的不便。
但是我们前面提到的Callable接口,可以有返回值,也可以抛出异常,但是在使用方面确实带来了一点点麻烦,它在作为submit方法的参数时,那么submit会返回一个Future接口的实现类,然后调用里面的get方法即可拿到返回值。
代码:
public class ThreaPool {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(2);
System.out.println(es);
Future<String> f = es.submit(new ThreadPoolCallable());
String s = f.get();
System.out.println(s);
}
public class ThreadPoolCallable implements Callable<String> {
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return "abc";
}
synchronized关键字:
这是关于使用线程带来的问题,就是代码同步的问题,前面说过,线程之间CPU分配的时间是随机的,那么就可能引来很大的问题,synchronized就是来解决这个问题的。
synchronized(obj){
synchronized(obj){
if( ticket > 0){
try{
Thread.sleep(10);
}catch(Exception ex){}
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}
}
obj是一个对象锁,可以是任意类,那么里面的就是同步代码块,里面需要放的是共享数据,就是假如说你买票,可以通过多个渠道来买(每个通道都是一个线程),那么票是定好的假如只有100张票,那么多个渠道来买这一百张票,就不能有错误,那么同步的作用就是当有个线程进入了同步代码块,其它的线程就不能进入同步代码块,必须等上一个线程出来,释放了同步锁(代码里面的obj),然后其它的线程才能进入访问。
也可以使用Lock接口的里面的lock和unlock来代替synchronized。
死锁:
原理:
前面说过,线程是根据cpu分配的时间来的,那么想一想会不会出现这样一种尴尬的局面,就是线程一进入了A抱着A锁,而线程B进入了B抱着B锁,那么现在线程一需要B锁,线程二需要A锁,这两各自抱着自己的锁谁也不放手,这就形成了死锁。
代码:
public class DeadRunnable implements Runnable {
private int i = 0;
public void run() {
while(true) {
if(i%2 == 0) {
synchronized(Lock_A.lockA) {
System.out.println("if...LockA");
synchronized(Lock_B.lockB) {
System.out.println("if...LockB");
}
}
}else {
synchronized(Lock_B.lockB) {
System.out.println("else...LockB");
synchronized(Lock_A.lockA) {
System.out.println("else...LockA");
}
}
}
i++;
}
}
}
public class Lock_A {
private Lock_A() {
}
final static Lock_A lockA = new Lock_A();
}
public class Lock_B {
private Lock_B() {
}
final static Lock_B lockB = new Lock_B();
}
public class Main {
public static void main(String[] args) {
DeadRunnable dr = new DeadRunnable();
Thread t1 = new Thread(dr);
Thread t2 = new Thread(dr);
t1.start();
t2.start();
}
}