概述
线程是进程中的内容,而每一个应用程序里面至少有一个线程。
1.什么是线程?
线程:是进程中用于控制程序执行的控制单元(执行路径,执行情景)。
2.什么是进程?
进程:是正在执行中的程序,每一个进程在执行时都有一个执行顺序,该顺序是一个执行路径,或叫做一个控制单元。
示例:
- public static void main(String[] args){
- for(int x = 1; x<4000;x++)
- System.out.println(“Hello Word!”);}
在我们启动JVM执行这个程序时,会有一个叫java.exe的进程。
因为进程中至少有一个线程,所以说明该进程中至少有一个线程在负责java程序的执行。
而这个线程运行的代码存放在main方法中,所以该线程也称为主线程。
3.多线程存在的意义
多线程的出现可以让我们程序中的部分能产生同时运行的效果。
例如:
其实如果更细节的说明,在JVM启动时是不止一个线程的,还有负责垃圾回收机制的线程。
如果JVM只有一个线程,没有垃圾回收线程,会有什么后果呢?
主线程在一直运行,在执行过程中堆内存中会存放很多很多垃圾,在主线程执行完后在做垃圾回收动作。可是,如果主线程在执行过程中垃圾太多堆内存放不下了,主线程就会先停下把垃圾先回收,解决之后在往下继续执行,而它在解决垃圾时我们的程序就会停止,这就很不合理。
如果JVM有多个线程,主线程在运行程序,还有一个线程在负责垃圾回收,这样程序就会更优化。
4.如何在程序中自定义线程?
Java给我们提供了对像线程这类事物的描述,该类是Thread类。
该类中定义了,创建线程对象的方法(构造函数),提供了要被线程执行的代码存储的位置(run()方法),还定义了开启线程运行的方法(start())。
Java还给我们提供了一个规则,实现Runnable接口。如果自定义类不继承Thread,也可以实现Runnable接口。并将多线程要运行的代码存放在Runnable的run方法中。
创建线程
方式一:继承Thread类
步骤:
1.定义一个类,继承Thread类
2.复写Thread类中的run方法,方法中写需要在新线程中执行的代码
3.创建Thread类的子类对象,调用线程的start方法
代码实现:
- public class Demo extends Thread{
- public void run(){//复写Thread类中的run方法
- //这里用来存放我们要线程执行的代码
- System.out.println(“我的线程1”);
- }
- }
- public class DemoTest{
- public static void main(String[] args){
- Demo d = new Demo();//创建一个线程
- d.start();//开启线程并执行该线程的run方法
- //d.run();//仅仅是对象调用方法,而线程创建了并没有运行
- //也可以用匿名的方式
- //new Demo().start();
- }
- }
1).为什么要覆盖run方法呢?
我们为什么要开启线程,就是为了要让线程去执行我们定义的某些代码,让这些代码起 到同时运行的效果。
Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行的代码,该存储 功能就是run方法。也就是说Thread类中的run方法是用于存储线程要运行的代码。
所以我们覆盖父类中run方法,在run方法中存放我们要执行的代码。
2).start方法的作用
该方法有两个作用:启动线程,调用并执行run方法。
注意:如直接调用run()方法,则是主线程执行其中代码,不会开启新线程。
方式二:实现Runnable
步骤:
1. 定义类实现Runnable接口
2.覆盖Runnable接口中的run方法,将多线程要运行的代码存入其中。
3.通过Thread类建立线程对象,并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
4.调用Thread对象的start方法开启线程。
代码实现:
- public class Demo implements Runnable{
- public void run(){
- //将线程要运行的代码存放在该run方法中
- System.out.println(“我的线程2”);
- }
- }
- public class DemoTest{
- public static void main(String[] args){
- Demo d = new Demo();//这里不是创建线程
- Thread t = new Thread(d);//这里是创建线程
- t.start();//启动线程
- //匿名的方式
- //new Thread(new Demo()).start();
- }
- }
注意:实现Runnable时这个类还不是线程,创建线程的是Thread类对象或Thread子类对象。
1).为什么要设计Runnable接口?
假设我们有两个类A和B,它们有一些共性代码,我们通过向上的不断抽取,出现了一个父类。可是A类中有一部分代码是需要被多线程所执行,因为java支持的是单继承,它已经继承了一个类了,所以不能在继承Thread类。java支持多实现所以就设计了Runnable接口。
2).Runnable接口应该由哪些类来实现?
Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个名称为run的无参数方法。
3).为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象传递给Thread。让Thread线程去使用该对象调用其run方法。
4).继承Thread类和实现Runnable接口建立线程有什么区别?
1.它们线程代码存放位置不同
继承Thread类:线程代码存放在Thread子类run方法中。
实现Runnable接口:线程代码存放在接口的子类run方法中。
2.局限性
实现Runnable接口避免了单继承的局限性。而且实现Runnable接口这个对象中的数据共享(如果不用静态修饰共享数据是在堆内存中)。
注意:在定义线程时,建议使用实现Runnable接口方式。
线程运行状态
临时状态:有执行资格,没有执行权。
冻结状态:没有执行资格的情况下。
运行状态:有执行资格,有执行权。
sleep方法与wait方法的区别?
sleep():不会释放对象锁,有执行权,时间结束后,自动恢复线程。
wait():释放对象锁,进入等待此对象线程池中,只有针对此对象发出notify方法或notifyAll方法后此线程才进入临时状态准备获得执行权进入运行状态。
注意:其中的stop方法现在已经过时用不了,他被新方法interrupt方法所代替。冻结状态的线程都会在线程池中。
同步
我们在运行多线程代码时发现运行结果每一次都不同,说明多线程具备随机性,因为这是由CPU不断的快速切换造成的。这就有可能会产生问题。
问题产生的原因:
1,多线程代码中有操作共享数据。
2,多条语句操作该共享数据。
当具备这两点时:有一个线程对多条操作共享数据的代码只执行了一部分时,另一个线程就开始参与执行,这就会发生数据错误。
解决方法:当一个线程在执行多条操作共享数据代码时,其它线程即使获取了执行权,也不可以参与操作。
Java对这种解决方式提供了专业的代码——同步。
同步的原理:就是将部分操作功能数据的代码进行加锁(也叫监视器)。
同步的前提:
1.必须要有两个或两个以上的线程。
2.必须是多个线程使用同一个锁。
同步的好处与弊端:
好处:解决了线程的安全问题。
弊端:较为消耗资源,同步嵌套后容易死锁。
什么是死锁?
代码示例:
- class Test implements Runnable
- {
- private boolean flag;//定义一个布尔型变量
- Test(boolean flag)
- {
- this.flag = flag;
- }
- public void run(){
- if(flag){
- //同步代码块的嵌套
- synchronized(DuiXiang.t1){//锁1
- System.out.println("if ----1");
- synchronized (DuiXiang.t2){//锁2
- System.out.println("if----2");
- }
- }
- }
- else{
- synchronized(DuiXiang.t2){//锁2
- System.out.println("else----2");
- synchronized (DuiXiang.t1){//锁1
- System.out.println("else----1");
- }
- }
- }
- }
- }
- class DuiXiang{//为了方便测试,所以把对象单独提取出来
- static Object t1 = new Object();//定义一个静态的Object对象
- static Object t2 = new Object();
- }
- class TestDemo{
- public static void main(String[] args){
- Thread h1 = new Tread(new Tesst(true));//创建线程
- Thread h2 = new Tread(new Tesst(false));
- h1.start();//启动线程
- h2.start();
- }
- }
注意:我们在开发时一定要注意避免死锁。
同步的表现形式:
1. 同步代码块
- synchronized(对象){
- 需要被同步的代码
- }
2. 同步函数
1).一般同步函数
- public synchronized void 方法名(){
- 需要被同步的代码
- }
2).静态同步函数
- public synchronized static void 方法名(){//静态同步函数
- 需要被同步的代码
- }
同步代码快与同步函数有什么不同?
它们的锁不同:
同步代码块:锁是任意对象。
同步函数:一般同步函数是this。静态同步函数是类名.class,是该类的字节码文件对象(涉及示例:单例设计模式的懒汉式)。
如何找安全问题?
1.明确哪些代码是多线程运行代码。
2.明确共享数据。
3.明确多线程运行代码中哪些语句是操作共享数据的。
注意:一定要明白哪里可以用同步哪里不可以用,也不可以把run方法都放到同步里,那样就相当成了单线程。
多线程的一些方法
currentThread():获取当前正在执行的线程对象
getName():获取线程名称
Thread.currentThread().getName():线程名称
设置线程名称:
1. setName();
2. 构造函数
类名(String name){
super(name);//父类有这个构造函数,所以直接调用就行
}
设置线程名称的意义
我们想要知道当前到底是哪一个线程在运行,我们可以获取其名称并进行判断。
wait():等待。//这里会发生异常
notify():唤醒一个。
notifyAll():唤醒线程池中的所有。
上面这三个都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。
为什么这三个操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁。
只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。
不可以对不同锁中的线程进行唤醒。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
ThreadGroup():线程组。一般情况下,是谁开启这个线程的,这个线程就属于哪个组。
toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。一般用的不多。
多线程示例
线程间通信:就是多个线程在操作同一个资源,但操作的动作不同,如:我们有一些数据,a在往里存,b在往出取。
等待唤醒机制示例:
- class XianCheng {
- public static void main(String[] args) {
- Res r = new Res();
- new Thread(new Input(r)).start();//创建线程并启动
- new Thread(new Output(r)).start();
- }
- }
- class Res{
- private String name;//姓名
- private String sex;//性别
- private boolean fal;//默认是false
- public synchronized void set(String name,String sex){
- if(fal)//判断
- try {
- this.wait();//这里会出现异常,所以只能try或抛
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- this.name = name;
- this.sex = sex;
- fal = true;
- this.notify();//唤醒一个线程
- }
- public synchronized void out(){
- if(!fal)//这里是不等于ture,因为上面给付了true
- try {
- this.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(name+"----------"+sex);
- fal = false;
- this.notify();
- }
- }
- //输入姓名 性别
- class Input implements Runnable{
- private Res r;//建立Res的引用,确保拿到的是同一个对象
- Input(Res r){
- this.r = r;
- }
- public void run() {
- int x = 0;
- while(true){
- if(x==0)
- r.set("小丽", "女");
- else
- r.set("Jack", "man");
- x=(x+1)%2;
- }
- }
- }
- //输出姓名 性别
- class Output implements Runnable{
- private Res r;
- Output(Res r){
- this.r = r;
- }
- public void run() {
- while(true)
- r.out();
- }
- }
等待的线程放在哪里呢?
在线程运行时内存中会建立一个线程池,等待线程都存在这个线程池中,当我们notify时唤醒的都是线程池中的线程,通常唤醒第一个被等待的,因为它是按顺序往里存的。
停止线程与守护线程
如何停止线程
1. 定义循环结束标记,让run方法结束,因为线程运行代码一般都是循环,只要控制了循环就可以让run方法结束,也就是线程结束。
2. 使用interrupt方法,该方法是结束线程的冻结状态,使线程回到运行状态中来。
特殊情况:
当线程处于了冻结状态,就不会读取到标记,那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态,这样就可以操作标记让线程结束。
守护线程
setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,java虚拟机退出。该方法必须在启动线程前调用。
守护线程相当于后台线程,前台线程如果结束,后台线程自动结束。
守护线程代码示例:
- class StopThread implements Runnable
- {
- private boolean flag =true;//定义布尔型变量,赋初值true
- public void run()
- {
- while(flag)
- {
- /*
- try{
- wait();//等待
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- */
- System.out.println(Thread.currentThread().getName()+"....run");
- }
- }
- public void changeFlag()
- {
- flag = false;
- }
- }
- class StopThreadDemo
- {
- public static void main(String[] args)
- {
- StopThread st = new StopThread();
- Thread t1 = new Thread(st);//创建线程
- Thread t2 = new Thread(st);
- t1.setDaemon(true);//定义守护线程
- //t2.setDaemon(true);
- t1.start();
- t2.start();
- int num = 0;
- while(true)
- {
- if(num++ == 60)
- {
- st.changeFlag();
- //t1.interrupt();//停止线程
- //t2.interrupt();
- break;
- }
- System.out.println(Thread.currentThread().getName()+"......."+num);
- }
- System.out.println("over");
- }
- }
Join与yield方法示例:
- class Demo implements Runnable
- {
- public void run()
- {
- for(int x=0; x<70; x++)
- {
- //返回该线程的字符串表示形式,包括线程名称、优先级和线程组
- System.out.println(Thread.currentThread().toString()+"....."+x);
- Thread.yield();//调用yield()方法释放执行权
- }
- }
- }
- class JoinDemo
- {
- public static void main(String[] args) throws Exception
- {
- Demo d = new Demo();
- Thread t1 = new Thread(d);//创建线程
- Thread t2 = new Thread(d);
- //t1.join();//这里会有一个异常,可以try或抛
- t1.start();
- //t1.setPriority(Thread.MAX_PRIORITY);//定义线程优先级
- t2.start();
- for(int x=0; x<80; x++)
- {
- //System.out.println("main....."+x);
- }
- System.out.println("over");
- }
- }
join方法:等待该线程终止。
特点:当A线程执行到了B线程的.join()方法时,A线程就会等待。等B线程都执行完,A线程才会执行。Join方法可以用来临时加入线程执行。
yield方法:暂停当前正在执行的线程对象,并执行其他线程。
特点:线程一读到Thread.yield();就会释放执行权,临时释放用到。可以稍微减缓一下线程的运行。
优先级
setPriority():更改线程的优先级。
优先级是1—10。
默认优先级是:5。
MAX_PRIORITY:线程可以具有的最高优先级。10
MIN_PRIORITY:线程可以具有的最低优先级。1
NORM_PRIORITY:分配给线程的默认优先级。5
注意:凡是数据是固定的就定义成常量,凡是数据是共享的就定义成静态。
生产者消费者示例:
- class XianCheng1 {
- public static void main(String[] args) {
- Ress r = new Ress();
- /*
- new hread(new Sheng(r)).start();//创建线程并启动线程
- new Thread(new Xiao(r)).start();
- */
- Sheng s = new Sheng(r);
- Xiao x = new Xiao(r);
- Thread t1 = new Thread(s); //创建线程
- Thread t2 = new Thread(s);
- Thread t3 = new Thread(x);
- Thread t4 = new Thread(x);
- t1.start();//启动线程<p align="left"><span style="color:black;"><span style="font-family:Times New Roman;"> t2.start();</span></span></p><p align="left"><span style="color:black;"><span style="font-family:Times New Roman;"> t3.start();</span></span></p><p align="left"><span style="color:black;"><span style="font-family:Times New Roman;"> t4.start();</span></span></p> }
- }
- class Ress{
- private String name;
- private int count = 1;
- private boolean fa = false;
- public synchronized void set(String name){
- while(fa)
- try {
- wait();//等待
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- this.name = name+"---"+count++;//让count自增
- System.out.println(Thread.currentThread().getName()+"---生产者--"+this.name);
- fa = true;
- notifyAll();//唤醒所有
- }
- public synchronized void out(){
- while(!fa)
- try {
- wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"----------消费者--"+this.name);
- fa = false;
- notifyAll();
- }
- }
- class Sheng implements Runnable{
- private Ress r;//定义对象引用
- Sheng(Ress r){
- this.r = r;
- }
- public void run() {
- while(true)
- r.set("==商品==");
- }
- }
- class Xiao implements Runnable{
- private Ress r;
- Xiao(Ress r){
- this.r = r;
- }
- public void run() {
- while(true)
- r.out();
- }
- }
对于这个示例我们不可以定义if语句了只能定义while语句,为什么呢?
因为是多个消费者和多个生产者,需要让被唤醒的线程再一次判断标记。
为什么定义notifyAll()?
因为需要唤醒对方线程。只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
if语句只适用在只有一个生成者和消费者的时候。
这个问题怎么解决呢?
在JDK1.5中提供了多线程升级解决方案
将同步synchronized替换成显示了Lock操作。
将Object中的wait,notify,notifyAll,替换成了Condition对象。
该对象可以将Lock锁进行获取。
该示例中,实现了本方只唤醒对方的操作。
新特性示例:
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- //一定要导包,否则有错误
- class XianCheng2 {
- public static void main(String[] args) {
- Ress1 r = new Ress1();
- Sheng1 s = new Sheng1(r);
- Xiao1 x = new Xiao1(r);
- Thread t1 = new Thread(s); //创建线程
- Thread t2 = new Thread(s);
- Thread t3 = new Thread(x);
- Thread t4 = new Thread(x);
- t1.start();//启动线程
- t2.start();
- t3.start();
- t4.start();
- }
- }
- class Ress1 {
- private String name;
- private int count = 1;
- private boolean fa = false;
- private Lock lock = new ReentrantLock();//创建Lock的实现类对象。
- private Condition condition_sheng = lock.newCondition();//新Condition实例。
- private Condition condition_xiao = lock.newCondition();
- public void set(String name) throws InterruptedException {
- lock.lock();//获取锁
- try {
- while (fa)
- condition_sheng.await();//生产者等待
- this.name = name + "---" + count++;
- System.out.println(Thread.currentThread().getName()+ "---生产者--" + this.name);
- fa = true;
- condition_xiao.signal();//唤醒消费者
- } finally {//一定执行语句
- lock.unlock();//释放锁
- }
- }
- public void out() throws InterruptedException {
- lock.lock();
- try {
- while (!fa)
- condition_xiao.await();
- System.out.println(Thread.currentThread().getName()+ "----------消费者--" + this.name);
- fa = false;
- condition_sheng.signal();
- } finally {
- lock.unlock();
- }
- }
- }
- class Sheng1 implements Runnable {
- private Ress1 r;
- Sheng1(Ress1 r) {
- this.r = r;
- }
- public void run() {
- while (true)
- try {
- r.set("==商品==");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- class Xiao1 implements Runnable {
- private Ress1 r;
- Xiao1(Ress1 r) {
- this.r = r;
- }
- public void run() {
- while (true)
- try {
- r.out();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
Lock:它是一个接口,实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操
作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition对象。
ReentrantLock:是Lock的实现类。
Lock:替代了synchronized方法和语句的使用。
lock ():获取锁。
unlock():释放锁。
newCondition():返回绑定到此Lock实例的新Condition实例。
Condition:替代了Object监视器方法的使用(wait notify notifyAll),它也是一个接口。
await();等待,会抛异常。
signal();唤醒一个等待线程。
signalAll();唤醒所有等待线程。
注意:要把lock.unlock();放到finally里,释放锁的资源。
新特性好处:一个锁上可以有多个相关的Condition。
总结:
run方法是将自定义代码存储的地方,不可以直接调用。
我们在开发时建议使用创建线程的实现Runnable接口方法,因为Java里面只允许单一继承,但允许实现多个接口。实现Runnable接口方法更加灵活。
多线程的安全问题与多线程的随机性有关,解决方法就是同步。
线程在我们不自定义起名时有默认的名称,Thread-编号,该编号从0开始。
finally里一般是用来释放资源的,join就是在要主线程的CPU执行权。Join有加入的意思。
等待和唤醒必须是同一个锁。