java线程该怎么用?

Java线程


进程与线程:

进程是系统一个程序执行时的一个实例,系统为其分配资源。进程之间相互独立。每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;如果一个进程想要访问另一个进程的资源,需要使用进程之间的通信方式,比如管道,套接字等方法。
在这里插入图片描述
每个应用打开后都会创建进程,占用系统资源。

一个程序运行后至少有一个进程,一个进程中可以包含多个线程

线程是进程的一个实体,是进程的一条执行路线。一个进程可以有多个线程,同一个进程内的多个线程可以共享资源。

进程的麻烦

如果进程需要频繁创建销毁,耗费资源较大,效率更低,使用线程可以提高应用程序响应速度。


java创建线程类

一:继承Thread类

   public class FirstThread extends Thread {
    
        private String name;
    
        //构造方法,创建名字为name线程
        public FirstThread(String name){
            super(name);
            this.name = name;
        }
    
        @Override
        public void run(){
            for (int i = 1; i <= 5; i++) {
                System.out.println("第"+i+"次执行"+name+"线程");
            }
        }
    
    }

需要重写run方法,完成该线程的执行逻辑代码

创建第二个线程类

public class SecondThread extends Thread {

    private String name;

    public SecondThread(String name){
        super(name);
        this.name = name;
    }

    @Override
    public void run(){
        for (int i = 1; i <= 5; i++) {
            System.out.println("第"+i+"次执行"+name+"线程");
        }
    }
}

测试

 public class Test {
    
        public static void main(String[] args) {
          
            FirstThread first = new FirstThread("first");
            SecondThread second = new SecondThread("second");
            //开启线程
            first.start();
            second.start();
    
        }
    }

会得到如下打印结果

第1次执行first线程

第1次执行second线程

第2次执行first线程

第2次执行second线程

第3次执行first线程

第3次执行second线程

第4次执行first线程

第4次执行second线程

第5次执行first线程

第5次执行second线程

多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。

当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

Thread类

构造方法:
public Thread() :分配一个新的线程对象。
public Thread(String name) :分配一个指定名字的新的线程对象。
public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

Runable接口是创建线程的另一种实现方式

常用方法:
public String getName() :获取当前线程名称。
public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run() :此线程要执行的任务在此处定义代码。
public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

二:实现Runable接口

步骤:

  • 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  • 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正
    的线程对象。
  • 调用线程对象的start()方法来启动线程。
 public class FirstRunable implements Runnable {
    
        @Override
        public void run() {
            for (int i = 1; i <= 5; i++) {
                System.out.println("第"+i+"次执行"+Thread.currentThread().getName()+"线程");
            }
        }
    }
public class SecondRunable implements Runnable {
    @Override
    public void run(){
        for (int i = 1; i <= 5; i++) {
            System.out.println("第"+i+"次执行"+Thread.currentThread().getName()+"线程");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        FirstRunable first = new FirstRunable();
        SecondRunable second = new SecondRunable();
        Thread firstRunable = new Thread(first,"firstRunable");
        Thread secondRunable = new Thread(second,"secondRunable");
        firstRunable.start();
        secondRunable.start();
    }
}

第1次执行firstRunable线程

第1次执行secondRunable线程

第2次执行firstRunable线程

第2次执行secondRunable线程

第3次执行firstRunable线程

第3次执行secondRunable线程

第4次执行firstRunable线程

第4次执行secondRunable线程

第5次执行firstRunable线程

第5次执行secondRunable线程

Thread和Runable方式区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类

线程安全问题

多个线程对同一个资源进行增删改操作时,导致该资源与预期结果不一致,则称为线程不安全。

问题说明:

桌子上有10个蛋糕,多个小孩同时去取蛋糕。

使用Runable接口实现类来模拟蛋糕,多个线程模拟小孩

 public class CakeRunable implements Runnable {
    
        private int cake = 10;
        @Override
        public void run() {
            while(true){
                //有蛋糕就去取
                if(cake>0){
                    try {
                        //模拟取蛋糕的时间
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"取了第"+(11-cake)+"个蛋糕");
                    cake--;
                }
            }
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            CakeRunable cakeRunable = new CakeRunable();
            Thread t1 = new Thread(cakeRunable,"张三");
            Thread t2 = new Thread(cakeRunable,"李四");
            Thread t3 = new Thread(cakeRunable,"傻蛋");
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

傻蛋取了第1个蛋糕

李四取了第1个蛋糕

张三取了第1个蛋糕

傻蛋取了第4个蛋糕

李四取了第4个蛋糕

张三取了第6个蛋糕

李四取了第7个蛋糕

傻蛋取了第7个蛋糕

张三取了第9个蛋糕

李四取了第10个蛋糕

傻蛋取了第10个蛋糕

张三取了第12个蛋糕

可以看到结果十分错误,这是因为多个线程共享资源时出现了线程不安全问题

线程同步

解决线程不安全问题

当有小孩去取蛋糕时,其他小孩不能去取蛋糕,必须等待他取完蛋糕

也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

方式:

  1. 同步代码块。
  2. 同步方法。
  3. 锁机制。

一:同步代码块

synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

synchronized(同步锁){
	需要同步操作的代码
}

同步锁::可以是任意类型,谁拿到锁才拥有访问资源的权限,其他线程只能等待

p

ublic class CakeRunable implements Runnable {
    private int cake = 10;

    Object lock = new Object();
    @Override
    public void run() {
        while(true) {
            //使用同步代码块
            synchronized (lock) {
                //有蛋糕就去取
                if (cake > 0) {
                    try {
                        //模拟取蛋糕的时间
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "取了第" + (11 - cake) + "个蛋糕");
                    cake--;
                }
            }
        }
    }
}

张三取了第1个蛋糕

张三取了第2个蛋糕

傻蛋取了第3个蛋糕

李四取了第4个蛋糕

李四取了第5个蛋糕

李四取了第6个蛋糕

李四取了第7个蛋糕

李四取了第8个蛋糕

李四取了第9个蛋糕

李四取了第10个蛋糕

可以看到蛋糕的数量没有出错,这就达到线程安全

二:同步方法

public class CakeRunable implements Runnable {
    private int cake = 10;

    @Override
    public void run(){
        while(true){
            takeCake();
        }

    }

    public synchronized void takeCake(){

                //有蛋糕就去取
                if (cake > 0) {
                    try {
                        //模拟取蛋糕的时间
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "取了第" + (11 - cake) + "个蛋糕");
                    cake--;
                }
        }

}

张三取了第1个蛋糕

傻蛋取了第2个蛋糕

傻蛋取了第3个蛋糕

李四取了第4个蛋糕

傻蛋取了第5个蛋糕

傻蛋取了第6个蛋糕

傻蛋取了第7个蛋糕

傻蛋取了第8个蛋糕

张三取了第9个蛋糕

傻蛋取了第10个蛋糕

三:锁机制

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,

同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

 public class CakeRunable implements Runnable{
  
       private int cake = 10;
        Lock lock = new ReentrantLock();
    
        @Override
        public void run(){
            while(true){
                lock.lock();
                if(cake>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"取了第"+(11-cake--)+"个蛋糕");
                }
                lock.unlock();
            }
        }
    }

张三取了第1个蛋糕

张三取了第2个蛋糕

张三取了第3个蛋糕

张三取了第4个蛋糕

张三取了第5个蛋糕

傻蛋取了第6个蛋糕

李四取了第7个蛋糕

李四取了第8个蛋糕

李四取了第9个蛋糕

李四取了第10个蛋糕

可以看到即使通过不同方法达到线程安全后,但是多个线程获得资源是随机的,

多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制

等待唤醒机制

在一个线程进行了规定操作后,就进入等待状态wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 **notifyAll()**来唤醒所有的等待线程。

wait/notify 就是线程间的一种协作机制。

  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时
    的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象
    上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中

  2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先
    入座。

  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

    调用wait和notify方法需要注意的细节

  4. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。

  5. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。

  6. wait方法与notify方法必须要在同步代码块或者是同步方法中使用。因为:必须要通过锁对象调用这2个方法。

线程池

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作无需反复创建线程而消耗过多资源。

Java里面线程池的顶级接口是 java.util.concurrent.Executor ,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorService

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在 java.util.concurrent.Executors 线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。

Executors类中有个创建线程池的方法如下:

 public static ExecutorService newFixedThreadPool(int nThreads) 
 返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

public Future<?> submit(Runnable task) 
	:获取线程池中的某一个线程对象,并执行
Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
 public class Test {
        public static void main(String[] args) {
            ExecutorService es = Executors.newFixedThreadPool(3);
            CakeRunable cakeRunable = new CakeRunable();
            es.submit(cakeRunable);
            es.submit(cakeRunable);
            es.submit(cakeRunable);
        }
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java程是Java中的一种机制,用于实现并发编程。线程可以看作是程序执行流的最小单元,能够独立运行并执行任务。Java线程是通过java.lang.Thread类来实现的,可以继承Thread类或实现Runnable接口来创建线程Java线程的运用可以通过以下几个步骤来实现: 1. 创建线程对象 在Java中创建线程对象有两种方式,一种是继承Thread类,另一种是实现Runnable接口。继承Thread类需要重写run()方法,实现Runnable接口需要实现run()方法。 2. 启动线程 创建线程对象后,需要调用线程对象的start()方法来启动线程。调用start()方法后,线程进入就绪状态,等待系统调度执行。 3. 线程执行任务 线程启动后,会自动执行run()方法中的任务。在任务执行过程中,可以通过sleep()方法、yield()方法等来控制线程的执行。 4. 线程结束 线程执行完任务后,会自动退出。在多线程编程中,需要注意线程的结束状态,避免出现线程泄漏或死锁等问题。 以下是一个简单的Java线程示例代码,通过继承Thread类来创建线程: ```java public class SimpleThread extends Thread { public void run() { System.out.println("线程开始执行!"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程执行完毕!"); } public static void main(String[] args) { SimpleThread thread = new SimpleThread(); thread.start(); System.out.println("主线程执行完毕!"); } } ``` 在上述代码中,我们创建了一个继承自Thread类的SimpleThread类,并在run()方法中定义了线程要执行的任务。在main()方法中,我们创建了一个SimpleThread对象,并调用它的start()方法来启动线程。在start()方法被调用后,线程会自动调用run()方法来执行任务。 除了继承Thread类外,我们还可以通过实现Runnable接口来创建线程。以下是一个实现Runnable接口的示例代码: ```java public class SimpleRunnable implements Runnable { public void run() { System.out.println("线程开始执行!"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程执行完毕!"); } public static void main(String[] args) { SimpleRunnable runnable = new SimpleRunnable(); Thread thread = new Thread(runnable); thread.start(); System.out.println("主线程执行完毕!"); } } ``` 在上述代码中,我们创建了一个实现了Runnable接口的SimpleRunnable类,并在run()方法中定义了线程要执行的任务。在main()方法中,我们创建了一个SimpleRunnable对象,并将它作为参数传递给Thread类的构造方法来创建一个新的线程。最后,我们调用线程的start()方法来启动线程。 以上就是Java线程的简单运用,当然Java线程的使用还涉及到线程同步、线程池等高级特性,需要进一步学习和实践。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值