java线程详解

一、基本概念

1、进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
2、线程是指进程中的一个执行流程,一个进程可以运行多个线程。比如java.exe进程可以运行很多线程。线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其它线程一起共享分配给该进程的所有资源。
3、线程“同时”执行只是人的感觉,在线程之间实际上轮换执行。

4、进程在执行过程中拥有独立的内存单元,进程有独立的地址空间。一个进程中的多个线程共享进程的内存块。当有新的线程产生的时候,操作系统不分配新的内存,而是让新线程共享原有的进程块的内存。因此,线程间的通信很容易,极大的提高了程序的运行效率。不同的进程因为处于不同的内存块,因此进程之间的通信相对困难。
5、线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)。

6、线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程包含以下内容:

(1)一个指向当前被执行指令的指令指针。

(2)一个栈。

(3)一个寄存器值的集合,定义了一部分描述正在执行线程的处理器状态的值。

(4)一个私有的数据区。

7、多任务就是同一时刻运行多个程序的能力。每一个任务称为一个线程。线程是进程中负责执行程序的一个执行控制单元,线程负责程序的执行,而一个进程允许有多个控制单元,我们称为多线程。

8、Java编写程序都运行在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的。每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行。
  9、在Java中,每次程序运行至少启动2个线程,而且每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行。每当使用java命令执行一个类时,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动一个进程。java本身具备了垃圾回收机制,所以每个java运行时至少会启动两个线程,一个main线程,另外一个是垃圾回收机制,JVM找到程序的入口点main(),然后运行main()方法,这样就产生了一个线程,这个线程称之为主线程。一旦再创建一个新的线程,就产生一个新的调用栈。当main方法结束后,主线程运行完成,JVM进程也随即退出。

10、线程分为两类:用户线程和守候线程。守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。用户线程是独立存在的,不会因为其他用户线程退出而退出。默认情况下启动的线程是用户线程,通过setDaemon(true)将线程设置成守护线程,这个函数务必在线程启动前进行调用,否则会报java.lang.IllegalThreadStateException异常,启动的线程无法变成守护线程,而是用户线程。

总结:

一个进程里面,至少有一个线程。

多线程的优点:解决了我们需要同时运行多代码的问题。

缺点:线程太多会降低效率。

二、线程的定义

1、在Java中,“线程”指两件不同的事情:

   (1)java.lang.Thread类的一个实例;

   (2)线程的执行。

 2、在 Java程序中,有两种方法创建线程:

   (1)对 Thread 类进行派生并覆盖run方法;

   (2)通过实现Runnable接口创建。

    Java中线程是指使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。

三、线程创建与启动的实例

1、使用析构函数(finalize)进行垃圾回收和主线程进行构造演示多线程

public class Student {
    private String name;

    Student(String name){
        this.name = name;
        System.out.println(name + "被构造了");
    }

    //回收对象的时候,由对象的垃圾回收器调用此方法
    @Override
    protected void finalize() throws Throwable {
        System.out.println(name + "被回收了");
    }
}

测试

public class Test {
    public static void main(String[] args) {
        new Student("胡井号");//匿名对象
        new Student("张三");
        new Student("李四");
        new Student("王五");
        System.gc();//运行垃圾回收器
        System.out.println("执行完毕");
    }
}

2、创建线程的第一种方式:

(1)继承Thread类

(2)重写Thread类里面的run方法

(3)创建线程的子类对象

(4)调用start方法启动线程

public class Dog extends Thread{
    @Override
    public void run() {
     System.out.println(Thread.currentThread().getName());//返回当前正在执行的线程对象的名字
        for (int i = 0;i<100;i++){
            System.out.println("狗在跑");
        }
    }
    public void shout(){
        System.out.println(Thread.currentThread().getName());
        for (int i = 0;i<100;i++){
            System.out.println("汪汪汪");
        }
    }
}

测试

public class Test1 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.start();//调用start方法,开启线程,运行run方法
        dog.shout();//调用shout方法的时候,开启的是主线程(main)
    }
}

局限性:不能再继承别的类了

3、创建线程的第二种方法:

(1)定义类实现Runnable接口()

(2)实现接口里的run方法,也就是将线程要执行的任务封装在run方法中

(3)创建一个线程对象,然后将实现Runnable接口的类作为创建线程对象的构造方法的参数传入

(4)调用线程对象的start方法,启动线程

 

Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。

public class Dog implements Runnable { //实现Runnable接口
    @Override
    public void run() {
        System.out.println("狗在跑~");
    }
    public void shout(){
        System.out.println("汪汪汪~");
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread thread = new Thread(dog);//创建线程对象,把dog对象放到线程中
        thread.start();//开启线程
        dog.shout();
        System.out.println("执行完毕");

    }
}

4、创建线程的第三种方法:匿名内部类启动线程

public class Game {
    public static void begin(){
        new Thread(){ //创建匿名对象
            @Override
            public void run() {
                super.run();
            }
        }.start();//启动线程
    }

    public static void main(String[] args) {
        begin();//调用静态方法
    }
}

5、线程安全问题:

(1)多线程操作共享数据

(2)操作共享数据的代码有多条

解决办法:同步代码块

好处:解决了线程安全问题

弊端:降低了效率,每次都要去判断同步锁(synchronized

买票案例:

public class Ticket implements Runnable{
    private int num = 100;//定义票数
   Object object = new Object();//创建Object对象

    @Override
    public void run() {
        try {
            sale();//调用售票的方法
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void sale() throws InterruptedException {
        while (true) {
            synchronized (object) {//同步锁
                if (num > 0) {
                    Thread.sleep(100);//线程休眠100秒
                    //获得线程对象的名字,然后票数减1
                    System.out.println(Thread.currentThread().getName() + "卖了" + num--);
                } else {
                    break;//不卖票的话需要停止,不然会陷入死循环
                }
            }
        }
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        //Thread的构造函数用的是两个参数的,一个是要开启线程的对象和线程的名字
        Thread thread1 = new Thread(ticket,"张三");
        Thread thread2 = new Thread(ticket,"李四");
        Thread thread3 = new Thread(ticket,"王五");
        Thread thread4 = new Thread(ticket,"赵六");
        Thread thread5 = new Thread(ticket,"胡井号");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();
    }
}

在买票过程中cpu会来回切换要执行的线程,当卖完票的时候,票数还没减的时候,又去执行另一个线程,会造成买到同一张票的线程安全问题,所以用完同步锁后,就可以执行完这个同步代码块的内容后才能去执行其他的线程

同步代码块和同步函数:

同步代码块的锁是任意对象,同步函数的锁是this

 

死锁常见的的情形之一:同步代码块的嵌套

例子:

public class MyLock {
    public static final Object LOCKA = new Object();//创建锁A对象
    public static final Object LOCKB = new Object();//创建锁B对象
}
public class DeathLock implements Runnable {
    private boolean flag;

    public DeathLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) { //如果为true就进入锁A和锁B
            synchronized (MyLock.LOCKA) {
                System.out.println(Thread.currentThread().getName() + "if的LOCKA");
                synchronized (MyLock.LOCKB) {
                    System.out.println(Thread.currentThread().getName() + "if的LOCKB");
                }
            }
        } else { //为false就进入锁B和锁A
            synchronized (MyLock.LOCKB) {
                System.out.println(Thread.currentThread().getName() + "else的LOCKB");
                synchronized (MyLock.LOCKA) {
                  System.out.println(Thread.currentThread().getName() + "else的LOCKA");
                }
            }
        }
    }
}

测试

public class Test {
    public static void main(String[] args) {
        DeathLock d1 = new DeathLock(true);
        DeathLock d2 = new DeathLock(false);

        Thread t1 = new Thread(d1);
        Thread t2 = new Thread(d2);

        t1.start();
        t2.start();
    }
}

6、线程调度:

(1)线程优先级设置

public class Talk implements Runnable{
    private String name;

    public Talk(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0;i<10;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我叫" + name + i);
        }
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Talk t = new Talk("董事长");
        Talk a = new Talk("职员");

        Thread thread1 = new Thread(t);
        Thread thread2 = new Thread(a);

        thread1.setPriority(Thread.MAX_PRIORITY);//调用线程更改优先级的方法(最高级)
        thread2.setPriority(Thread.MIN_PRIORITY);//调用线程更改优先级的方法(最低级)

        thread1.start();
        thread2.start();
    }
}

(2)线程插队

public class Talk implements Runnable{
    @Override
    public void run() {
        System.out.println("我在说话");
    }
}

测试

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Talk t = new Talk();
        Thread thread = new Thread(t);
        thread.start();

        for (int i = 0;i<5;i++){
            System.out.println("我是主线程");
            if (i==3){
                thread.join();//插队
            }
        }
    }
}

(3)线程让步

public class Talk implements Runnable{
    private String name;

    public Talk(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        for (int i = 0;i<10;i++){
            System.out.println("我叫" + name + i);
            Thread.yield();//让步
        }
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Talk t = new Talk("董事长");
        Talk a = new Talk("职员");

        Thread thread1 = new Thread(t);
        Thread thread2 = new Thread(a);

        thread1.start();
        thread2.start();
    }
}

7、生产者消费者问题

资源类

public class Resource {
    private String name;
    private int id = 1;//编号
    private boolean flag = false;//为true时生产,生产完把flag的值改为true时就能消费了,消费完把值改为false

    public synchronized void set(String name){ //同步函数
        if (!flag){ //如果为true
            this.name = name + id;
            System.out.println(Thread.currentThread().getName() + "生产了" + this.name);
            this.flag = true;//把默认的false变成true
            id++;
            notify();//唤醒单个线程
        }else {
            try {
                this.wait();//线程等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void out(){
        if (flag){
            System.out.println(Thread.currentThread().getName() + "消费了" + this.name);
            this.flag = false;
            notifyAll();//唤醒所有线程
        }else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

生产者类

public class Producer implements Runnable{
    private Resource r;//把资源类作为属性

    public Producer(Resource r) {
        this.r = r;
    }
    @Override
    public void run() {
       while (true){
           this.r.set("烤鸭");//调用资源类的set方法
       }
    }
}

消费者类

public class Consumer implements Runnable{
    private Resource r;

    public Consumer(Resource r) {
        this.r = r;
    }
    @Override
    public void run() {
        while (true){
            this.r.out();//调用资源类的out方法
        }
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Resource r = new Resource();//创建资源对象
        Producer p = new Producer(r);//创建生产者对象
        Consumer c = new Consumer(r);//创建消费者对象

        //一个生产者,多个消费者
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);
        Thread t3 = new Thread(c);
        Thread t4 = new Thread(c);
        Thread t5 = new Thread(c);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
根据定义,进程为一个数据结构及能在其上进行的一次操作, 它有两个基本特征, 1:进程是可用于资源的独立单位, 2:进程同时又是一个可独立调度和分派的基本单位, 这两个基本属性使之能够独立运行,也能够并发运行。但是在并发运行的时候,系统还需要执行一系列操作: 1、需要创建进程,并为之分配其所必需的资源。 2、撤销进程,对资源进行回收。 3、进程切换,它需要保留当前进程的CPU环境和设置新选中进程的CPU环境。 为此需要花费不少处理时间。正因为进程拥有资源,所以在并发执行进程的时候, 在创建、撤销和切换,系统需要付出较大的开销,因此,系统中设置的进程不能太多, 进程切换的频率也不能过高,这就限制了并发程度的提高。 为了解决这一问题,于是产生并引入了线程概念。 一个进程中可以包含一个或多个线程,一个线程就是程序内部的一条执行线索。 在单线程中,程序代码按调用顺序依次往下执行,不能实现两段程序代码同时交替运行的效果。如果一个程序中要实现两段程序代码同时交替运行,就需要产生多个线程,并指定每个线程上所要运行的程序代码段,这就是多线程。 程序启动运行时,就自动产生了一个线程,main方法就是在这个线程上运行的,当不再产生新的线程时,程序就是单线程
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值