常常在任务管理器当中可以看到进程,系统进程什么的,但是究竟这个是什么东东?
有仔细想过吗?
进程是正在运行的程序,其实不然,正在运行的程序不一定有一个进程,还可以有多个
进程支持,每个应用程序都需要一个或多个进程来支持运行,当应用程序运行时会伴随
进程的启动。
一个进程是一个应用程序的执行路径。多个进程间有什么关系?
一个应用程序中,多个进程是在同一时间内一起执行,即并发执行。并行是表面上的同
时执行,实际上达不到完全的并行,取决于cpu,谁获得cpu分配的时间片谁将执行该进
程,这个都是看cpu心情,随机分配给谁,由于cpu的切换速度很快,所以进程间的运行
看起来像是一起执行的。平时也就习惯说成是并行了。
一个进程可以干很多事情,但是又有谁在支持它做这么多事情呢,其实幕后都是线程的
功劳…………
每个进程由至少一个线程来支持线程的运行,线程也会伴随进程的执行而启动。也分单
一线程和多线程。
每一个线程是一个进程的一个执行路径。好比应用程序和进程的关系。
那么,各个线程之间的关系又是如何?
1、在一个进程中,多个线程是在同一时间内一起执行,即并发执行。
2、线程之间的并行同样和进程之间相同,取决于cpu,谁获得cpu分配的时间片谁将执
行该线程,由于cpu的切换速度很快,所以线程间的运行看起来像是一起执行的。
3、进程中各个线程的工作分工明确,各自独立不影响其他线程,若需要通讯,可以通过
属性或者方法来通讯。(下面会讲)
4、之前一直在使用的main方法,其实就是一个线程,只不过都是单一线程,只有main
线程一个,在main方法中创建的线程启动后将于main线程并行。
其实呢,好比经常玩游戏的人,其实在游戏中创建的每一个你玩的人物,各个玩家,就
是一个线程,各自做自己的事情,毫无关联,可以进行通讯,但是又各自独立,这样可
能好理解一点了……
线程的创建呢,有2种方式:
1、继承Thread
1.写一个类 继承Thread
2.重写run方法
3.创建线程类的对象
4.主线程调用 start方法 启动
多用于两个不通讯的线程。
2、实现Runnable接口
1、创建一个实现类implements Runnable接口
2、重写Run方法
3、通过实现类对象创建线程类对象
eg:
SubThread st = new SubThread();
Thread t = new Thread(st);
4、主线程调用start方法启动线程
多用于两个线程通讯,有共享属性。
线程①类:
public class ZDYthread extends Thread{
public static int res;
private int res1;
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
res++;
try {
Thread.sleep(500);//线程睡眠,0.5s调用一次。
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"..."+res);
//Thread.currentThread().getName()获取当前线程的名字
}
}
public int getRes1()
{return res1;}
public void setRes1(int res1)
{this.res1 = res1;}}
测试类:
public class Demo01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
ZDYthread zdy = new ZDYthread();
zdy.start();//新的线程启动
// zdy.run();//没有启动新线程,只是调用ZDYthread中的普通方法。不会出现并行。属于main线程。
/*
* cpu分配不均,可能将ZDY中for循环的语句并没有全部执行完交给另一个子线程。造成2.和2的情况。
*
*/
ZDYthread zdy1 = new ZDYthread();
zdy1.start();
}
}
线程②类:
public class ZDYRun implements Runnable {
private int res;
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 1000; i++) {
res++;
System.out.println(Thread.currentThread().getName() + "..." + res + " Thread run");
}
}
}
测试类:
public class Main {
public static void main(String[] args) {
ZDYRun zr= new ZDYRun();
Thread t = new Thread(zr);
t.setName("007: ");
t.setPriority(Thread.MAX_PRIORITY);//Windows中优先级为1-10.一般建议使用常用字段Thread.MAX_PRIORITY和Thread.MIN_PRIORITY
t.start();
System.out.println(t.getPriority());
System.out.println(t.getId());
//通过实现Runnable类的对象zr创建线程对象,可以调用同一个实现类对象zr的同一个run方法,产生2个线程时是将run方法复制两份给两个线程
//只拷贝run方法,其他普通属性或方法留有引用。只有一个ZDYRun对象,多个对象才用static。
//同一个对象用同一个属性。调用getter、setter就行。
//调用构造方法 Thread(Runnable target, String name)
Thread t1 = new Thread(zr,"008: ");
t1.setPriority(Thread.MIN_PRIORITY);
t1.start();
System.out.println(t1.getId());
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()
+"..."+(i+1)+" main run");
}
}
}
这里提到了线程的常用方法。
.setName()//通过线程对象调用该方法设置该对象线程的线程名字
.getName()//通过线程对象调用该方法获得该对象线程的线程名字
.currentThread()//静态方法,通过线程类Thread调用该方法获得当前该线程的对象
.setPriority()//通过线程对象调用该方法设置该对象线程的线程优先级大小。
设置线程的优先级,优先级高的有较大可能多的获得时间片执行,但不保证一定这样。
优先级常用字段表示Thread.MAX_PRIORITY代表最高优先级。
Thread.MIN_PRIORITY表示最低的,还有一个默认的优先级NORM_PRIORITY 居中。
.getPriority()//通过线程对象调用该方法获得该对象线程的线程优先级大小
.getId()//获得线程id的唯一标识
.sleep()//静态方法,通过Thread调用来睡眠,睡眠时间内不获得时间片。线程不执
行
.notify()//谁拥有此锁的资源,释放所有权,之前等待的wait中将获得所有权,不然
永远在等待。
.wait()//通过对象调用此方法,在synchronized中使用,和sleep有区别,sleep在睡
眠中仍占有所有权,wait睡眠会释放所有权。
.join()//谁执行join方法,将等待此线程结束后再执行另一个线程,基本不用
虽然说线程和线程之间是各自独立的,但是难免会遇到线程之间的通讯,那么,线程之
间的通讯该怎样进行?
线程之间的通讯通常用线程里的共享属性或者通过setter和getter获得属性来进行通讯。
线程间的通讯分两种:1、子线程之间的通讯 2、子线程和主线程之间的通讯
第一种创建线程的方式Thread:
子线程与子线程之间的通讯:
由于继承Thread创建的线程是通过各自对象创建的,没有直接关系,可以用static修饰
定义成静态属性,多个线程之间通过这个静态属性的值来达到通讯。
例如:
ZDYThread zdy = new ZDYThread();
ZDYThread zdy1 = new ZDYThread();
两个线程都是从ZDYThread中创建的对象,属性定义成静态即可访问同一个属性进行通
讯。
子线程与主线程main的通讯:
可以通过set、get方法生成的属性方法来使用属性得到或者赋值给另一个线程来达到通
讯的目的。
第二种创建线程的方式实现Runnable接口:
子线程与子线程之间的通讯:
实现Runnable接口的对象可以创建一个,通过其创建线程对象可以使用同一个
Runnable对象,除了run方法是拷贝的,其余属性或方法都是共享的。所以定义一个普
通私有方法即可实现子线程与子线程之间的通讯。
子线程与主线程main的通讯:
可以通过set、get方法生成的属性方法来使用属性得到或者赋值给另一个线程来达到通
讯的目的。
线程存在生命周期:
创建态
(经过start启动)就绪态
(调度获得时间片)运行态
(经过阻塞事件)阻塞态
(阻塞事件结束)就绪态
(重新获得时间片)运行态
(没有阻塞事件后)终止态
用一张图解释:
经常面试可能遇到start和run 的区别:
直接用Run方法只会调用Thread的run方法,而不会再启动另一个线程。
start方法是启动线程,包括了run方法。
.start 启动
线程启动起来之后 运行的是run方法的方法内容
run方法运行完之后 线程自动消失
线程并行固然是好事,各自相互独立,但是如果真的在同一时刻访问同一个数据会不会
出现问题呢?好比你和你的男/女朋友同时拿同一张银行卡,卡里有5000元,你要取走
3000,他/她也要取里面的3000,在你取的同时他也取,可能会遇到你取完同时还是
5000他也成功取出3000,所有线程并行有时候也会有不利的一面,不然银行早倒闭
了。。。
这就是经常人们提到的线程同步问题。
多个线程 在一个时间点 访问了同一个资源 导致出现同步问题,例如两个人取钱发生在
同一时刻
解决方法,给资源上锁,同一时间只能有一个线程访问。一旦上了锁 只给一个线程访问
即使线程睡眠了 ,也是抱着这把钥匙睡的。
当一个线程使用对象资源时,另一个不得访问它。释放后才可访问。
用synchronized方法来上锁,代码块执行的时候 , 整个类的对象 只能被一个线程访
问。
上锁有三种方式:
synchronized (资源对象)
synchronized (方法)
synchronized (this)
synchronized锁方法和锁对象this等同,方法由对象调用,最终看对象,和方法没什么
直接关系。
下一节会用代码展示。。。。。
既然有线程同步问题,上锁上的多了,会导致谁都拿不到所有权,科学家吃饭问题就是
典型的例子。解决办法就是解锁咯。。
线程死锁和死循环不同,死循环可以更改,线程死锁不容易发现。
造成线程死锁的原因是上的锁 太多而导致,解决办法就是少上几个锁。